Python Template

This template allows you to code functions using Python 3.9. The skeleton of this template looks like this:
def handle(req):
    return {
        "status_code": 200,
        "body": req.body.decode('utf-8')
    }

This code echoes back your request body message with a 200 status code. Probably not very handy but a good starting point.

Apart from your Python skills, to create a Serverless function you just need to understand how to extract the request information and how to return data.

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.EnvironHeaders object that contains all the request headers. Read the documentation of this type of objects 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 makes it convenient for working with query parameters.
  • path: A string (str) object containing the URL path used to invoke your function. Note that this will be 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 will be 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 at the moment, although this will change in the future. For now, you can use the Python's standard library and the following pre-installed dependencies (this list will grow):
  • requests: Provides a convenient and easy way to make HTTP/1.1 requests.
  • Paho-mqtt: The Eclipse Paho MQTT Python client.

SWX Package

The SWX package allows some operations using the SmartWorks API:
  • Get and revoke an access token.

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 swx package to deal with token request and revocation.
import requests 
from swx.auth.token import get_token 
 
API_URL = 'https://api.swx.altairone.com' 
SPACE = 'examples'   # Change it to your Space name 
 
# We'll use a Thing credentials to request the access token 
COLLECTION = '<A-COLLECTION-NAME>' 
THING_ID = '<A-THING-ID>' 
CLIENT_ID = '<A-CLIENT-ID>' 
CLIENT_SECRET = '<A-CLIENT-SECRET>' 
 
 
def handle(req): 
    # Request token with the Thing's credentials and 'thing.update' scope. 
    # Token will be automatically revoked at the end of the context manager block. 
    with get_token(CLIENT_ID, CLIENT_SECRET, ["thing.update"]) as token: 
        url = f"{API_URL}/spaces/{SPACE}/collections/{COLLECTION}/things/{THING_ID}/properties" 
        requests.put(url, 
                     headers={"Authorization": f"Bearer {token.access_token}"}, 
                     json={"temperature": 27}) 
 
    return { 
        "status_code": 200, 
        "body": "Property updated!" 
    }