Skip to content

Commit 3dfe50a

Browse files
committed
new python middleware
1 parent 9723b8a commit 3dfe50a

File tree

14 files changed

+199
-51
lines changed

14 files changed

+199
-51
lines changed

.github/workflows/invoke-all.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ jobs:
1313
outputs:
1414
languages: ${{ steps.prep-matrix.outputs.languages }}
1515
language_paths: ${{ steps.prep-matrix.outputs.language_paths }}
16+
env:
17+
FUNC_VERSION: 'knative-v1.18.0'
1618
steps:
1719
- name: Checkout code
1820
uses: actions/checkout@v4

python/hello/Procfile

Lines changed: 0 additions & 1 deletion
This file was deleted.

python/hello/README.md

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,73 @@
1-
# Python 'hello' template
1+
# Python HTTP Function
22

3-
Welcome to your new Python function project! The boilerplate function
4-
code can be found in [`func.py`](./func.py). This function will
5-
simply print 'Hello, World!' if a request is received successfuly.
3+
## Introduction
64

7-
## Endpoints
5+
A Python HTTP function built with ASGI protocol support. The implementation
6+
provides a simple HTTP endpoint that responds with "Hello World!" to incoming
7+
requests.
88

9-
Running this function will expose three endpoints.
9+
The function is structured as a class-based implementation with proper lifecycle
10+
management, including configurable startup and shutdown hooks.
1011

11-
* `/` The endpoint for your function.
12-
* `/health/readiness` The endpoint for a readiness health check
13-
* `/health/liveness` The endpoint for a liveness health check
12+
## Recommended Deployment
1413

15-
The health checks can be accessed in your browser at
16-
[http://localhost:8080/health/readiness]() and
17-
[http://localhost:8080/health/liveness]().
14+
> [!NOTE]
15+
> We recommend using the host builder.
16+
> This feature is currently behind a flag because its not available for all the
17+
> languages yet so you will need to enable it.
1818
19-
You can use `func invoke` to send an HTTP request to the function endpoint.
19+
```bash
20+
# Enable the host builder
21+
export FUNC_ENABLE_HOST_BUILDER=1
22+
23+
# Deploy your code to cluster
24+
# Make sure to set the builder to use it
25+
func deploy --builder=host
26+
27+
# Local development and testing
28+
func run --builder=host --container=false
29+
```
30+
31+
## Customization
32+
33+
- This function uses the ASGI (Asynchronous Server Gateway Interface) 3.0
34+
specification therefore its compatible with the signature `handle(scope, receive, send)`
35+
36+
### Lifecycle Management
37+
The function provides lifecycle hooks:
38+
- **start()**: Initialization hook called when function instances are created
39+
- **stop()**: Cleanup hook, ensuring graceful termination and resource cleanup
40+
- **alive()**: Health check exposed at `/health/liveness`
41+
- **ready()**: Readiness check exposed at `/health/readiness`
2042

2143
## Testing
2244

23-
This function project includes a [unit test](./test_func.py). Update this
24-
as you add business logic to your function in order to test its behavior.
45+
The function includes unit tests that verify the HTTP handler behavior.
46+
Tests are located in the `tests/` directory and use pytest with asyncio support.
47+
48+
To run the tests:
2549

26-
```console
27-
python test_func.py
50+
```bash
51+
# Install dependencies (if not already installed)
52+
pip install -e .
53+
54+
# Run tests
55+
pytest
56+
57+
# Run tests with verbose output
58+
pytest -v
2859
```
60+
61+
### Test Structure
62+
63+
Tests are organized in the `tests/` directory with the current test file
64+
`test_func.py` verifying that the ASGI handler returns a proper 200 OK response.
65+
The testing framework uses pytest with asyncio support, configured in `pyproject.toml`.
66+
67+
### Writing New Tests
68+
69+
To add new tests, create files named `test_*.py` in the `tests/` directory.
70+
For async functions, use the `@pytest.mark.asyncio` decorator. Mock ASGI
71+
components by creating mock `scope`, `receive`, and `send` functions as shown
72+
in the existing test. You can also test lifecycle methods like `start()`,
73+
`stop()`, `alive()`, and `ready()` by calling them directly on a function instance.

python/hello/app.sh

Lines changed: 0 additions & 3 deletions
This file was deleted.

python/hello/func.py

Lines changed: 0 additions & 16 deletions
This file was deleted.

python/hello/function/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .func import new
205 Bytes
Binary file not shown.
3.41 KB
Binary file not shown.

python/hello/function/func.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Function
2+
import logging
3+
4+
5+
def new():
6+
""" New is the only method that must be implemented by a Function.
7+
The instance returned can be of any name.
8+
"""
9+
return Function()
10+
11+
12+
class Function:
13+
def __init__(self):
14+
""" The init method is an optional method where initialization can be
15+
performed. See the start method for a startup hook which includes
16+
configuration.
17+
"""
18+
19+
async def handle(self, scope, receive, send):
20+
""" Handle all HTTP requests to this Function other than readiness
21+
and liveness probes."""
22+
23+
logging.info("OK: Request Received")
24+
25+
# echo the request to the calling client
26+
await send({
27+
'type': 'http.response.start',
28+
'status': 200,
29+
'headers': [
30+
[b'content-type', b'text/plain'],
31+
],
32+
})
33+
await send({
34+
'type': 'http.response.body',
35+
'body': 'Hello World!'.encode(),
36+
})
37+
38+
def start(self, cfg):
39+
""" start is an optional method which is called when a new Function
40+
instance is started, such as when scaling up or during an update.
41+
Provided is a dictionary containing all environmental configuration.
42+
Args:
43+
cfg (Dict[str, str]): A dictionary containing environmental config.
44+
In most cases this will be a copy of os.environ, but it is
45+
best practice to use this cfg dict instead of os.environ.
46+
"""
47+
logging.info("Function starting")
48+
49+
def stop(self):
50+
""" stop is an optional method which is called when a function is
51+
stopped, such as when scaled down, updated, or manually canceled. Stop
52+
can block while performing function shutdown/cleanup operations. The
53+
process will eventually be killed if this method blocks beyond the
54+
platform's configured maximum studown timeout.
55+
"""
56+
logging.info("Function stopping")
57+
58+
def alive(self):
59+
""" alive is an optional method for performing a deep check on your
60+
Function's liveness. If removed, the system will assume the function
61+
is ready if the process is running. This is exposed by default at the
62+
path /health/liveness. The optional string return is a message.
63+
"""
64+
return True, "Alive"
65+
66+
def ready(self):
67+
""" ready is an optional method for performing a deep check on your
68+
Function's readiness. If removed, the system will assume the function
69+
is ready if the process is running. This is exposed by default at the
70+
path /health/rediness.
71+
"""
72+
return True, "Ready"

python/hello/pyproject.toml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[project]
2+
name = "function"
3+
description = ""
4+
version = "0.1.0"
5+
requires-python = ">=3.9"
6+
readme = "README.md"
7+
license = "MIT"
8+
dependencies = [
9+
"httpx",
10+
"pytest",
11+
"pytest-asyncio"
12+
]
13+
authors = [
14+
{ name="Your Name", email="you@example.com"},
15+
]
16+
17+
[build-system]
18+
requires = ["hatchling"]
19+
build-backend = "hatchling.build"
20+
21+
[tool.pytest.ini_options]
22+
asyncio_mode = "strict"
23+
asyncio_default_fixture_loop_scope = "function"
24+

0 commit comments

Comments
 (0)