Skip to content

Commit a07b1e4

Browse files
authored
feat: Add async and streaming examples (#393)
Adds two new examples to demonstrate asynchronous and streaming capabilities: - `cloud_run_async`: Shows how to create basic asynchronous HTTP and CloudEvent functions. - `cloud_run_streaming_http`: Shows how to create both synchronous and asynchronous streaming HTTP functions. The examples include instructions for running locally with the `functions-framework` CLI and deploying to Cloud Run with `gcloud`.
1 parent 9b37f85 commit a07b1e4

File tree

8 files changed

+273
-0
lines changed

8 files changed

+273
-0
lines changed

examples/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
* [`cloud_run_http`](./cloud_run_http/) - Deploying an HTTP function to [Cloud Run](http://cloud.google.com/run) with the Functions Framework
66
* [`cloud_run_event`](./cloud_run_event/) - Deploying a CloudEvent function to [Cloud Run](http://cloud.google.com/run) with the Functions Framework
77
* [`cloud_run_cloud_events`](cloud_run_cloud_events/) - Deploying a [CloudEvent](https://github.com/cloudevents/sdk-python) function to [Cloud Run](http://cloud.google.com/run) with the Functions Framework
8+
* [`cloud_run_async`](./cloud_run_async/) - Deploying asynchronous HTTP and CloudEvent functions to [Cloud Run](http://cloud.google.com/run) with the Functions Framework
9+
* [`cloud_run_streaming_http`](./cloud_run_streaming_http/) - Deploying streaming HTTP functions to [Cloud Run](http://cloud.google.com/run) with the Functions Framework
810

911
## Development Tools
1012
* [`docker-compose`](./docker-compose) -

examples/cloud_run_async/README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Deploying async functions to Cloud Run
2+
3+
This sample shows how to deploy asynchronous functions to [Cloud Run functions](https://cloud.google.com/functions) with the Functions Framework. It includes examples for both HTTP and CloudEvent functions, which can be found in the `main.py` file.
4+
5+
## Dependencies
6+
7+
Install the dependencies for this example:
8+
9+
```sh
10+
pip install -r requirements.txt
11+
```
12+
13+
## Running locally
14+
15+
### HTTP Function
16+
17+
To run the HTTP function locally, use the `functions-framework` command:
18+
19+
```sh
20+
functions-framework --target=hello_async_http
21+
```
22+
23+
Then, send a request to it from another terminal:
24+
25+
```sh
26+
curl localhost:8080
27+
# Output: Hello, async world!
28+
```
29+
30+
### CloudEvent Function
31+
32+
To run the CloudEvent function, specify the target and set the signature type:
33+
34+
```sh
35+
functions-framework --target=hello_async_cloudevent --signature-type=cloudevent
36+
```
37+
38+
Then, in another terminal, send a sample CloudEvent using the provided script:
39+
40+
```sh
41+
python send_cloud_event.py
42+
```
43+
44+
## Deploying to Cloud Run
45+
46+
You can deploy these functions to Cloud Run using the `gcloud` CLI.
47+
48+
### HTTP Function
49+
50+
```sh
51+
gcloud run deploy async-http-function \
52+
--source . \
53+
--function hello_async_http \
54+
--base-image python312 \
55+
--region <YOUR_REGION>
56+
```
57+
58+
### CloudEvent Function
59+
60+
```sh
61+
gcloud run deploy async-cloudevent-function \
62+
--source . \
63+
--function hello_async_cloudevent \
64+
--base-image python312 \
65+
--region <YOUR_REGION>
66+
```
67+
68+
After deploying, you can invoke the CloudEvent function by sending an HTTP POST request with a CloudEvent payload to its URL.

examples/cloud_run_async/main.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import functions_framework.aio
16+
17+
@functions_framework.aio.http
18+
async def hello_async_http(request):
19+
"""
20+
An async HTTP function.
21+
Args:
22+
request (starlette.requests.Request): The request object.
23+
Returns:
24+
The response text, or an instance of any Starlette response class
25+
(e.g. `starlette.responses.Response`).
26+
"""
27+
return "Hello, async world!"
28+
29+
@functions_framework.aio.cloud_event
30+
async def hello_async_cloudevent(cloud_event):
31+
"""
32+
An async CloudEvent function.
33+
Args:
34+
cloud_event (cloudevents.http.CloudEvent): The CloudEvent object.
35+
"""
36+
print(f"Received event with ID: {cloud_event['id']} and data {cloud_event.data}")
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
functions-framework>=3.9.2,<4.0.0
2+
3+
# For testing
4+
httpx<=0.28.1
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/usr/local/bin/python
2+
3+
# Copyright 2025 Google LLC
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
import asyncio
17+
import httpx
18+
19+
from cloudevents.http import CloudEvent, to_structured
20+
21+
22+
async def main():
23+
attributes = {
24+
"Content-Type": "application/json",
25+
"source": "from-galaxy-far-far-away",
26+
"type": "cloudevent.greet.you",
27+
}
28+
data = {"name": "john"}
29+
30+
event = CloudEvent(attributes, data)
31+
32+
headers, data = to_structured(event)
33+
34+
async with httpx.AsyncClient() as client:
35+
await client.post("http://localhost:8080/", headers=headers, data=data)
36+
37+
if __name__ == "__main__":
38+
asyncio.run(main())
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Deploying streaming functions to Cloud Run
2+
3+
This sample shows how to deploy streaming functions to [Cloud Run](http://cloud.google.com/run) with the Functions Framework. The `main.py` file contains examples for both synchronous and asynchronous streaming.
4+
5+
## Dependencies
6+
7+
Install the dependencies for this example:
8+
9+
```sh
10+
pip install -r requirements.txt
11+
```
12+
13+
## Running locally
14+
15+
### Synchronous Streaming
16+
17+
To run the synchronous streaming function locally:
18+
19+
```sh
20+
functions-framework --target=hello_stream
21+
```
22+
23+
Then, send a request to it from another terminal:
24+
25+
```sh
26+
curl localhost:8080
27+
```
28+
29+
### Asynchronous Streaming
30+
31+
To run the asynchronous streaming function locally:
32+
33+
```sh
34+
functions-framework --target=hello_stream_async
35+
```
36+
37+
Then, send a request to it from another terminal:
38+
39+
```sh
40+
curl localhost:8080
41+
```
42+
43+
## Deploying to Cloud Run
44+
45+
You can deploy these functions to Cloud Run using the `gcloud` CLI.
46+
47+
### Synchronous Streaming
48+
49+
```sh
50+
gcloud run deploy streaming-function \
51+
--source . \
52+
--function hello_stream \
53+
--base-image python312 \
54+
--region <YOUR_REGION>
55+
```
56+
57+
### Asynchronous Streaming
58+
59+
```sh
60+
gcloud run deploy streaming-async-function \
61+
--source . \
62+
--function hello_stream_async \
63+
--base-image python312 \
64+
--region <YOUR_REGION>
65+
```
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import time
16+
import asyncio
17+
import functions_framework
18+
import functions_framework.aio
19+
from starlette.responses import StreamingResponse
20+
21+
# Helper function for the synchronous streaming example.
22+
def slow_numbers(minimum, maximum):
23+
yield '<html><body><ul>'
24+
for number in range(minimum, maximum + 1):
25+
yield '<li>%d</li>' % number
26+
time.sleep(0.5)
27+
yield '</ul></body></html>'
28+
29+
@functions_framework.http
30+
def hello_stream(request):
31+
"""
32+
A synchronous HTTP function that streams a response.
33+
Args:
34+
request (flask.Request): The request object.
35+
Returns:
36+
A generator, which will be streamed as the response.
37+
"""
38+
generator = slow_numbers(1, 10)
39+
return generator, {'Content-Type': 'text/html'}
40+
41+
# Helper function for the asynchronous streaming example.
42+
async def slow_numbers_async(minimum, maximum):
43+
yield '<html><body><ul>'
44+
for number in range(minimum, maximum + 1):
45+
yield '<li>%d</li>' % number
46+
await asyncio.sleep(0.5)
47+
yield '</ul></body></html>'
48+
49+
@functions_framework.aio.http
50+
async def hello_stream_async(request):
51+
"""
52+
An asynchronous HTTP function that streams a response.
53+
Args:
54+
request (starlette.requests.Request): The request object.
55+
Returns:
56+
A starlette.responses.StreamingResponse.
57+
"""
58+
generator = slow_numbers_async(1, 10)
59+
return StreamingResponse(generator, media_type='text/html')
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
functions-framework>=3.9.2,<4.0.0

0 commit comments

Comments
 (0)