Overview
Python¶
These are the official Python 3 templates maintained by OpenFaaS Ltd.
The python3-http template is recommended for most Python functions. Use python3-http-debian when a dependency requires native compilation — if a package fails to build on Alpine, switch to the Debian variant.
| Template | Base OS | Use when |
|---|---|---|
| python3-http | Alpine | Default choice. Pure Python packages only. |
| python3-http-debian | Debian | Native C extensions required — SQL drivers, Kafka, Pandas, image manipulation. |
| python3-flask | Alpine | Direct access to Flask Response, e.g. for SSE streaming. |
| python3-flask-debian | Debian | Flask Response with native C extensions. |
Note
The python3-http template is the best option for most users, followed by python3-http-debian for when C/C++ based pip modules are required.
All templates use the of-watchdog, Flask for HTTP routing, and Waitress as the production WSGI server.
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.
The event contains data about the request, including:
bodyheadersmethodquerypath
The context contains basic information about the function, including:
hostname
Responses must be returned as a dictionary with the following keys:
statusCode- the HTTP status code to return as a numberbody- the response body, which can be a string, dict, or listheaders- (optional) a dictionary of response headers to return
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)
}
Consuming the request path:
def handle(event, context):
if event.path == '/hello':
return {
"statusCode": 200,
"body": "Hello, World!"
}
return {
"statusCode": 404,
"body": "Not Found"
}
This could be useful if you needed to serve up a static file or metadata to an external tool or service that is integrated with your functions. An example would be a HTTP readiness probe that checks the /healthz endpoint to see if a database is connected.
Accessing request method:
def handle(event, context):
if event.method == 'GET':
return {
"statusCode": 200,
"body": "GET request"
}
else:
return {
"statusCode": 405,
"body": "Method not allowed"
}
Accessing request query string arguments:
def handle(event, context):
return {
"statusCode": 200,
"body": {
"name": event.query['name']
}
}
Accessing request headers:
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
environment:
db_host: "postgresql.default.svc.cluster.local"
secrets:
- db-password
The build_options: libpq shorthand installs the packages needed to compile psycopg2. If you need more control over which packages are installed, you can use build_args instead:
build_args:
ADDITIONAL_PACKAGE: "libpq-dev gcc python3-dev"
The database host is set as an environment variable so it can be changed per deployment without rebuilding the image. The database password is stored as an OpenFaaS secret to keep it out of environment variables and the function image.
Create the secret before deploying the function:
faas-cli secret create db-password --from-literal='passwd'
requirements.txt
psycopg2==2.9.3
Create a database and table to use with the example:
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:
The handler reads the database password from the mounted secret and the host from the db_host environment variable set in stack.yaml. It opens a connection, queries the users table, and returns the results.
import os
import psycopg2
def handle(event, context):
try:
password = read_secret('db-password')
# Connect using the host from the db_host env var
# and the password from the mounted secret.
conn = psycopg2.connect(
dbname='main',
user='postgres',
port=5432,
host=os.getenv('db_host'),
password=password
)
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
}
def read_secret(name):
with open("/var/openfaas/secrets/" + name, "r") as f:
return f.read().strip()
Always read secrets from an OpenFaaS secret at /var/openfaas/secrets/secret-name. The use of environment variables for sensitive values is an anti-pattern — they are 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
Add static files to your function¶
A common use-case for static files is when you want to serve HTML, lookup information from a JSON manifest or render some kind of templates.
With the python templates, static files and folders can just be added to the handler directory and will be copied into the function image.
To read a file e.g data.json back at runtime you can do the following:
def handle(event, context):
if event.path=="/static":
# Get the directory where this handler.py file is located
current_dir = os.path.dirname(os.path.abspath(__file__))
data_file_path = os.path.join(current_dir, 'data.json')
# Read the data.json file
with open(data_file_path, 'r') as file:
data = json.load(file)
return {
"statusCode": 200,
"body": json.dumps(data),
"headers": {
"Content-Type": "application/json"
}
}
else:
return {
"statusCode": 200,
"body": "Hello from OpenFaaS!"
}