Python
Python¶
There are two recommended templates for Python 3 users.
- python3-http - based upon Alpine Linux, small image size, for pure Python only.
- python3-http-debian - based upon Debian Linux, larger image size, required for native C modules as as SQL, Kafka, Pandas, and image manipulation.
Flask is used internally for handling HTTP requests and responses, however it is not exposed to the user, so is only an implementation detail.
The HTTP server used is currently Waitress.
This is an official template maintained by OpenFaaS Ltd.
Downloading the templates¶
Using template pull with the repository's URL:
faas-cli template pull https://github.com/openfaas/python-flask-template
Using the template store, since they are both within the same repository, either command will fetch down both templates.
faas-cli template store pull python3-http
faas-cli template store pull python3-http-debian
Using your stack.yml
file:
configuration:
templates:
- name: python3-http
Python 3 versions¶
The python3-http template supports a dynamic Python version through a build argument.
It is recommended that you put the version within the OpenFaaS YAML file:
functions:
postgres-fn:
lang: python3-http
+ build_args:
+ - PYTHON_VERSION=3.12
The option can also be passed via a CLI flag, however this is not recommended as it is easy to forget or miss it:
faas-cli build --build-arg PYTHON_VERSION=3.11
Event and Context Data¶
The function handler is passed two arguments, event and context.
event contains data about the request, including: - body - headers - method - query - path
context contains basic information about the function, including: - hostname
Response Bodies¶
By default, the template will automatically attempt to set the correct Content-Type header for you based on the type of response.
For example, returning a dict object type will automatically attach the header Content-Type: application/json
and returning a string type will automatically attach the Content-Type: text/html, charset=utf-8
for you.
Accessing Event Data¶
Accessing request body:
def handle(event, context):
return {
"statusCode": 200,
"body": "You said: " + str(event.body)
}
def handle(event, context):
if event.method == 'GET':
return {
"statusCode": 200,
"body": "GET request"
}
else:
return {
"statusCode": 405,
"body": "Method not allowed"
}
def handle(event, context):
return {
"statusCode": 200,
"body": {
"name": event.query['name']
}
}
def handle(event, context):
return {
"statusCode": 200,
"body": {
"content-type-received": event.headers.get('Content-Type')
}
}
Return a JSON body with a Content-Type¶
def handle(event, context):
return {
"statusCode": 200,
"body": {"message": "Hello from OpenFaaS!"},
"headers": {
"Content-Type": "application/json"
}
}
Custom status codes and response bodies¶
Successful response status code and JSON response body
def handle(event, context):
return {
"statusCode": 200,
"body": {
"key": "value"
}
}
Successful response status code and string response body
def handle(event, context):
return {
"statusCode": 201,
"body": "Object successfully created"
}
Failure response status code and JSON error message
def handle(event, context):
return {
"statusCode": 400,
"body": {
"error": "Bad request"
}
}
Custom Response Headers¶
Setting custom response headers
def handle(event, context):
return {
"statusCode": 200,
"body": {
"key": "value"
},
"headers": {
"Location": "https://www.example.com/"
}
}
Native dependencies¶
As explained in the introduction, only the Debian variant of the Python template is suitable for building native dependencies. Why? For one, many libraries are available as pre-compiled wheels, meaning they can be imported without any compilation. Secondly, Alpine Linux requires so many packages to be added to build code that it becomes larger than the Debian base. Thirdly, Alpine Linux is not compatible with many native libraries because it uses its own C library called musl.
If a pre-compiled wheel isn't available for your chosen package, then you can use a build option to add a build toolchain. Build options are an abstracted list of packages to install, grouped together.
functions:
postgres-fn:
lang: python3-http
+ build_options:
+ - libpq
The current list of build_options for the Debian-based template is available in the templates repository in the template.yml file. Pull requests and contributions are welcome, however packages can be specified even when they are not present as a build option.
Alternatively, individual packages within apt can be specified through build_args:
functions:
postgres-fn:
lang: python3-http
+ build_args:
+ ADDITIONAL_PACKAGE: "libpq-dev gcc python3-dev"
Example with Postgresql¶
stack.yml
version: 1.0
provider:
name: openfaas
gateway: http://127.0.0.1:8080
functions:
pgfn:
lang: python3-http-debian
handler: ./pgfn
image: pgfn:latest
build_options:
- libpq
Alternatively you can specify ADDITIONAL_PACKAGE
in the build_args
section for the function.
build_args:
ADDITIONAL_PACKAGE: "libpq-dev gcc python3-dev"
requirements.txt
psycopg2==2.9.3
Create a database and table:
CREATE DATABASE main;
\c main;
CREATE TABLE users (
name TEXT,
);
-- Insert the original Postgresql author's name into the test table:
INSERT INTO users (name) VALUES ('Michael Stonebraker');
handler.py:
import psycopg2
def handle(event, context):
try:
conn = psycopg2.connect("dbname='main' user='postgres' port=5432 host='192.168.1.35' password='passwd'")
except Exception as e:
print("DB error {}".format(e))
return {
"statusCode": 500,
"body": e
}
cur = conn.cursor()
cur.execute("""SELECT * from users;""")
rows = cur.fetchall()
return {
"statusCode": 200,
"body": rows
}
Always read the secret from an OpenFaaS secret at /var/openfaas/secrets/secret-name
. The use of environment variables is an anti-pattern and will be visible via the OpenFaaS API.
Authenticate a function¶
To authenticate a function with a pre-shared secret, or API token, first create a secret, bind that secret to the function, then read it at runtime and validate it.
Create a new pre-shared secret:
openssl rand -base64 32 > python-auth-token.txt
Then create a new secret in OpenFaaS:
faas-cli secret create python-auth-token \
--from-file python-auth-token.txt
Create a new function called python-auth
:
faas-cli new --lang python3-http \
python-auth
Bind the secret to the function:
version: 1.0
provider:
name: openfaas
gateway: http://127.0.0.1:8080
functions:
python-auth:
lang: python3-http-debian
+ secrets:
+ - python-auth-token
Now edit the function's handler.py
file to read the secret and validate it:
def read_secret(name):
with open("/var/openfaas/secrets/" + name) as f:
return f.read().strip()
def handle(event, context):
token = read_secret("python-auth-token")
if not "Authorization" in event.headers:
return {
"statusCode": 401,
"body": "Unauthorized"
}
if event.headers["Authorization"] != "Bearer {}".format(token):
return {
"statusCode": 401,
"body": "Unauthorized"
}
return {
"statusCode": 200,
"body": "Access granted"
}
Deploy the function with faas-cli up
, then invoke it:
curl -i https://127.0.0.1:8080/function/python-auth \
-H "Authorization: Bearer $(cat ./python-auth-token.txt)"
HTTP/2 200
Access granted
How to perform package upgrades at build time¶
The base images for the official OpenFaaS templates come from the Docker Hub, these images are built with automation and should always have the latest apk or apt packages installed.
That said, if you need to upgrade the images sooner, or are using an older image that was mirrored from the Docker Hub, you can add a --build-arg
flag or build_args:
entry in stack.yaml to force an upgrade on each build.
functions:
fn1:
lang: python3-http
build_args:
- UPGRADE_PACKAGES: "true"
faas-cli publish --filter fn1\
--build-arg UPGRADE_PACKAGES=true