Python Functions allow for the direct deployment of source code as a production service to any Kubernetes cluster with Knative installed. The request handler method signature follows the ASGI (Asynchronous Server Gateway Interface) standard, allowing for integration with any supporting library.
When you create a Python function using func create -l python, you get a standard Python project structure:
❯ func create -l python myfunc
❯ tree myfunc
myfunc/
├── func.yaml # Function configuration
├── pyproject.toml # Python project metadata
├── function/
│ ├── __init__.py
│ └── func.py # Your function implementation
└── tests/
└── test_func.py # Unit tests
The func.yaml file contains build and deployment configuration. For details,
see the func.yaml reference.
Python functions must implement a method new() which returns a new instance
of your Function class:
def new():
"""Factory function that returns a Function instance."""
return Function()
class Function:
"""Your function implementation."""
passYour function class can implement several optional methods:
The main request handler following ASGI protocol. This async method processes all HTTP requests except health checks.
async def handle(self, scope, receive, send):
"""Handle HTTP requests."""
# Process the request
await send({
'type': 'http.response.start',
'status': 200,
'headers': [[b'content-type', b'text/plain']],
})
await send({
'type': 'http.response.body',
'body': b'Hello, World!',
})Called when a function instance starts (e.g., during scaling or updates). Receives configuration as a dictionary.
def start(self, cfg):
"""Initialize function with configuration."""
self.debug = cfg.get('DEBUG', 'false').lower() == 'true'
logging.info("Function initialized")Called when a function instance stops. Use for cleanup operations.
def stop(self):
"""Clean up resources."""
# Close database connections, flush buffers, etc.
logging.info("Function shutting down")Health check methods exposed at /health/liveness and /health/readiness:
def alive(self):
"""Liveness check."""
return True, "Function is alive"
def ready(self):
"""Readiness check."""
if self.database_connected:
return True, "Ready to serve"
return False, "Database not connected"# Build and run on the host (not in a container)
func run --builder=host
# Force rebuild even if no changes detected
func run --buildTest your function with HTTP requests:
# Test the main endpoint
curl http://localhost:8080
# Check health endpoints
curl http://localhost:8080/health/liveness
curl http://localhost:8080/health/readinessCreate a CloudEvent function:
# Create a new CloudEvent function
func create -l python -t cloudevents myeventfuncTest CloudEvent functions using curl with proper headers:
# Invoke with a CloudEvent
curl -X POST http://localhost:8080 \
-H "Ce-Specversion: 1.0" \
-H "Ce-Type: com.example.sampletype" \
-H "Ce-Source: example/source" \
-H "Ce-Id: 1234-5678-9101" \
-H "Ce-Subject: example-subject" \
-H "Content-Type: application/json" \
-d '{"message": "Hello CloudEvent!"}'Also see func invoke which automates this for basic testing.
Python functions use modern Python packaging with pyproject.toml and include
pytest with async support for testing ASGI functions. The generated project
includes example tests in tests/test_func.py that demonstrate how to test the
async handler.
It's best practice to use a virtual environment to isolate your function's dependencies:
# Create a virtual environment (Python 3.3+)
python3 -m venv venv
# Activate the virtual environment
# On Linux/macOS:
source venv/bin/activate
# On Windows:
# venv\Scripts\activate
# Upgrade pip to ensure you have the latest version
python -m pip install --upgrade pip
# Install the function package and its dependencies (including test dependencies)
pip install -e .
# Run tests with pytest
pytest
# Run tests with verbose output
pytest -v
# Run tests with coverage (requires pytest-cov)
pip install pytest-cov
pytest --cov=function --cov-report=term-missing
# When done, deactivate the virtual environment
deactivateNote:
- Python 3 typically comes with
venvmodule built-in - If
python3command is not found, trypythoninstead - The
-m pipsyntax ensures you're using the pip from your virtual environment - Always activate your virtual environment before running tests or installing dependencies
The test file demonstrates how to test ASGI functions by mocking the ASGI interface:
import pytest
from function import new
@pytest.mark.asyncio
async def test_function_handle():
# Create function instance
f = new()
# Mock ASGI scope (request details)
scope = {
'type': 'http',
'method': 'POST',
'path': '/',
'headers': [(b'content-type', b'application/json')],
}
# Mock receive callable (for request body)
async def receive():
return {
'type': 'http.request',
'body': b'{"test": "data"}',
'more_body': False,
}
# Track sent responses
responses = []
# Mock send callable
async def send(message):
responses.append(message)
# Call the handler
await f.handle(scope, receive, send)
# Assert responses
assert len(responses) == 2
assert responses[0]['type'] == 'http.response.start'
assert responses[0]['status'] == 200
assert responses[1]['type'] == 'http.response.body'For CloudEvent functions, include CloudEvent headers in your test scope:
@pytest.mark.asyncio
async def test_cloudevent_handler():
f = new()
# CloudEvent headers
scope = {
'type': 'http',
'method': 'POST',
'path': '/',
'headers': [
(b'ce-specversion', b'1.0'),
(b'ce-type', b'com.example.test'),
(b'ce-source', b'test/unit'),
(b'ce-id', b'test-123'),
(b'content-type', b'application/json'),
],
}
# Test with CloudEvent data
async def receive():
return {
'type': 'http.request',
'body': b'{"message": "test event"}',
'more_body': False,
}
# ... rest of testFor integration testing, you can use httpx with ASGI support:
import httpx
import pytest
from function import new
@pytest.mark.asyncio
async def test_with_http_client():
f = new()
# Create ASGI transport with your function
transport = httpx.ASGITransport(app=f.handle)
# Make HTTP requests
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.get("/")
assert response.status_code == 200
response = await client.post("/", json={"test": "data"})
assert response.status_code == 200async def handle(self, scope, receive, send):
"""Process POST requests with JSON data."""
if scope['method'] == 'POST':
# Receive request body
body = b''
while True:
message = await receive()
if message['type'] == 'http.request':
body += message.get('body', b'')
if not message.get('more_body', False):
break
# Process JSON data
import json
data = json.loads(body)
result = process_data(data)
# Send response
response_body = json.dumps(result).encode()
await send({
'type': 'http.response.start',
'status': 200,
'headers': [[b'content-type', b'application/json']],
})
await send({
'type': 'http.response.body',
'body': response_body,
})class Function:
def start(self, cfg):
"""Configure function from environment."""
self.api_key = cfg.get('API_KEY')
self.cache_ttl = int(cfg.get('CACHE_TTL', '300'))
self.log_level = cfg.get('LOG_LEVEL', 'INFO')
logging.basicConfig(level=self.log_level)For CloudEvent support, parse the headers and body accordingly:
async def handle(self, scope, receive, send):
"""Handle CloudEvents."""
headers = dict(scope['headers'])
# Check if this is a CloudEvent
if b'ce-type' in headers:
event_type = headers[b'ce-type'].decode()
event_source = headers[b'ce-source'].decode()
# Process CloudEvent...# Deploy to a specific registry
func deploy --builder=host --registry docker.io/myuserFor all deploy options, see func deploy --help
Before func v1.18, Python functions used the "parliament" style: a bare func.py
exposing def main(context), a requirements.txt depending on
parliament-functions, and a Procfile (web: python -m parliament .).
These functions still build and deploy with the pack and s2i builders, locally
and remotely. func builds them the pre-v1.18 way, from the function's own Procfile
and requirements.txt, pinning cloudevents below 2.0 during the build (the
parliament stack requires the 1.x line; with the pack builder the pin is written
to a constraints.txt file at the function root). The host builder does not
support them. This support is deprecated and will be removed in a future release;
every build prints a deprecation warning.
The other pre-v1.18 Procfile-based layouts (the old flask and wsgi templates)
are not supported and are rejected with an error.
To migrate, recreate the function with the current layout (func create -l python)
and move your logic into the function package: def main(context) becomes the
ASGI handle (or an instanced new()) described above, where the Flask request
object is replaced by the scope/receive/send interface. Functions that work
with CloudEvents must also adopt the cloudevents 2.x API.