Python Template

This template enables you to code functions using Python 3.10. The handle function defines the HTTP request handler logic. This function must implement the logic that the function should execute each time it is invoked.

def handle(req):
    """
    This function defines the logic of the HTTP request handler.
    
    This example echoes the HTTP request payload by returning a response with
    status code 200 and the same payload used in the request.
    """
    return {
        "status_code": 200,
        "body": req.body.decode('utf-8')
    }

The handle function is the HTTP handler in which your code will run. You can use other functions in your code to make it cleaner, but the handle function is the entry point of your function.

In addition, the handle function can be defined with the async keyword to improve the concurrency of code execution. When a function is defined with async, it runs in an event loop using the asyncio module. Here's an example of an asynchronous function:
import asyncio

async def handle(req):
    """
    This function defines the logic of the HTTP request handler.
    
    This example echoes the HTTP request payload by returning a response with
    status code 200 and the same payload used in the request.
    
    This code runs using the asyncio module, so it can make use of coroutines
    to improve performance when using I/O operations (such as HTTP requests).
    """
    await asyncio.sleep(1)
    return {
        "status_code": 200,
        "body": f"Asynchronous response: {req.body.decode('utf-8')}"
    }

Request Object

The request object (req) is the only parameter passed to your handle function and is an instance of the Event class. It has five properties:
  • method: A string (str) that contains the HTTP method of the request. Its value can be POST, GET, PUT, PATCH or DELETE.
  • body: A bytes object containing the body of the request. If this is plain text, you may want to convert it to a string using req.body.decode('utf-8') to make it easier to work with.
  • headers: This is a werkzeug.Headers instance that contains all the request headers. Read the documentation of this type of object to know how to use it.
  • query: A werkzeug.ImmutableMultiDict object that contains the query parameters you sent in the URL. This is a special kind of dictionary with extended capabilities that make it convenient for working with query parameters.
  • path: A string (str) object containing the URL path used to invoke your function. Note that this is just the path that you append after the /invoke or /async-invoke endpoint.

Response Object

The response object is a dictionary returned by the handle function that is used to build the HTTP response for your request. This dictionary can have the following keys:
  • status_code: An integer (int) representing the status code of the response. It must be a valid status code greater than or equal to 200. Defaults to 200.
  • headers: A dictionary (dict) containing the headers to return.
  • body: The body of the response. If a Content-Type header has not been explicitly set, it will be automatically determined by the body type:
    • If the body is a dict, the Content-Type will be set to application/json.
    • Otherwise, the body will be the string representation of the value and the content type will be set to plain/text.

None of these values is required. In fact, you can omit the return statement to send a 200 empty response.

Libraries

Adding third-party Python dependencies is not allowed. For now, you can use the Python's standard library and the following pre-installed dependencies (this list will grow):
  • iots: A Python client for the Altair® IoT Studio™ API.
  • quart: An async Python web microframework.
  • requests: Provides a convenient and easy way to make HTTP/1.1 requests.
  • azure-storage-blob: The official client library for Microsoft's Azure Blob Storage.
  • backpack: Provides useful utilities for Python.
  • beautifulsoup4: A library that makes it easy to scrape information from web pages.
  • cryptography: A package which provides cryptographic recipes and primitives to Python developers.
  • google-cloud-storage: The official client library for Google Cloud Storage.
  • httpx: A fully featured HTTP client library that supports HTTP/1.1 and HTTP/2 and provides both sync and async APIs.
  • matplotlib: A comprehensive library for creating static, animated, and interactive visualizations.
  • mintotp: A minimal TOTP generator written in Python.
  • numpy: A fundamental package for scientific computing.
  • orm: An async ORM. Only Postgres support is included.
  • paho-mqtt: The Eclipse Paho MQTT Python client.
  • pandas: Provides powerful data structures for data analysis, time series, and statistics.
  • pendulum: Drop-in replacement for the standard datetime class.
  • pyjwt: A Python implementation of RFC 7519 (JSON Web Token).
  • pyodbc: An open source module that makes accessing ODBC databases simple.
  • scipy: An open-source software for mathematics, science, and engineering.
  • scrapy: A high-level web crawling and web scraping framework.
  • sympy: A Python library for symbolic mathematics.

iots Package

The iots package is the client library of the Altair IoT Studio REST API. It allows you to handle authentication and perform operations on the API. Check the library documentation to learn more about it and how to use it.

Note: This package is in a very early stage of development. More features will be added in the future.

Variables

You can access your Variables using the two built-in functions in the Variables package:

  • def get(variable_name: str) -> str: Returns the value of the given variable. If the variable is not found, an empty string is returned.
  • def exists(variable_name: str) -> bool: Returns a boolean value indicating if the given variable is found.
Example:
from function import variables

def handle(req):
    # ...
    if variables.exists("my_password"):
        my_variable = variables.get("my_password")
        # ...
    # ...
.. 

Logging

Anything you write to stderr will be saved as a log entry. You can simply use:
import sys
sys.stderr.write("My log entry!\n")

Although you will probably prefer the logging package. Make sure to set the logging level correctly.

import logging
log = logging.getLogger("my_logger")
log.setLevel(logging.INFO)
log.error("ERROR")
log.info("INFO")
log.debug("DEBUG")  # This line wont' be logged because Logging Level is INFO

Examples

Here are some simple examples to illustrate how you can write your own functions using Python.

Adder
This is a really simple function that gets a comma-separated list of numbers in the request body and return the sum of them (or an error if the input format is invalid).
def handle(req): 
     # Gets function input 
     body = req.body.decode("utf-8") 

     # Parses input as a comma-separated list of numbers 
     numbers = body.split(',') 
 
     try: 
        # Tries to convert all values to numbers 
        numbers = [float(i) for i in numbers] 
        response_msg = sum(numbers) 
        status_code = 200 
     except Exception: 
        # Throws an error if something is not a number 
        response_msg = "Invalid input format!" 
        status_code = 400 

     return { 
         "status_code": status_code, 
         "body": response_msg, 
} 
MQTT Publishers
The following example shows how to set up an MQTT client to publish the payload of the request to the functions/Python topic.
import paho.mqtt.client as mqtt

MQTT_BROKER = "test.mosquitto.org"
MQTT_PORT = 1883


def handle(req):
    body = req.body.decode('utf-8')

    client = mqtt.Client()
    client.connect(MQTT_BROKER, MQTT_PORT)
    msg_info = client.publish("functions/python", body, qos=0)
    msg_info.wait_for_publish()

    return {
        "status_code": 200,
        "body": "Message published!"
    }
Update a Thing's Property
The following example shows how to update a Thing's Property using the iots package to make the request and deal with token request and revocation.
from iots import API
from function import variables

SPACE = "enter-your-space-ID"
THING_UID = "enter-thing-UID"

my_scopes = ["thing.update"]

def handle(req): 
    if not variables.exists("my_client_credentials"):
        return {
            "status_code": 500,
            "body": "Credentials not found"
        }
    
    client_id = variables.get("my_client_credentials")['client_id']
    client_secret = variables.get("my_client_credentials")['client_secret']
        
    with API().set_credentials(client_id, client_secret, my_scopes) as api:
        response = api.spaces(SPACE).things(THING_UID).properties("temperature").update(27)
        
    return { 
        "status_code": 200, 
        "body": "Property updated!" 
    }