From ad4068059e7fe50629de9868c88331f1d15591b4 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Wed, 30 Jul 2025 22:20:47 -0700 Subject: [PATCH 1/5] feat: Add async and streaming examples 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`. --- examples/README.md | 4 +- examples/cloud_run_async/README.md | 58 ++++++++++++++++++ examples/cloud_run_async/main.py | 36 +++++++++++ examples/cloud_run_async/requirements.txt | 5 ++ examples/cloud_run_async/send_cloud_event.py | 39 ++++++++++++ examples/cloud_run_streaming_http/README.md | 56 ++++++++++++++++++ examples/cloud_run_streaming_http/main.py | 59 +++++++++++++++++++ .../cloud_run_streaming_http/requirements.txt | 2 + 8 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 examples/cloud_run_async/README.md create mode 100644 examples/cloud_run_async/main.py create mode 100644 examples/cloud_run_async/requirements.txt create mode 100644 examples/cloud_run_async/send_cloud_event.py create mode 100644 examples/cloud_run_streaming_http/README.md create mode 100644 examples/cloud_run_streaming_http/main.py create mode 100644 examples/cloud_run_streaming_http/requirements.txt diff --git a/examples/README.md b/examples/README.md index 7960a743..34fe3418 100644 --- a/examples/README.md +++ b/examples/README.md @@ -2,9 +2,11 @@ ## Deployment targets ### Cloud Run -* [`cloud_run_http`](./cloud_run_http/) - Deploying an HTTP function to [Cloud Run](http://cloud.google.com/run) with the Functions Framework +* [`cloud_run_http`](./cloud_run_http/) - Deploying an HTTP function to [Cloud Run](http.cloud.google.com/run) with the Functions Framework * [`cloud_run_event`](./cloud_run_event/) - Deploying a CloudEvent function to [Cloud Run](http://cloud.google.com/run) with the Functions Framework * [`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 +* [`cloud_run_async`](./cloud_run_async/) - Deploying asynchronous HTTP and CloudEvent functions to [Cloud Run](http://cloud.google.com/run) with the Functions Framework +* [`cloud_run_streaming_http`](./cloud_run_streaming_http/) - Deploying streaming HTTP functions to [Cloud Run](http://cloud.google.com/run) with the Functions Framework ## Development Tools * [`docker-compose`](./docker-compose) - diff --git a/examples/cloud_run_async/README.md b/examples/cloud_run_async/README.md new file mode 100644 index 00000000..80a80393 --- /dev/null +++ b/examples/cloud_run_async/README.md @@ -0,0 +1,58 @@ +# Deploying async functions to Cloud Run + +This sample shows how to deploy asynchronous functions to [Cloud Run](http://cloud.google.com/run) with the Functions Framework. It includes examples for both HTTP and CloudEvent functions, which can be found in the `main.py` file. + +## Dependencies +Install the dependencies for this example: +```sh +pip install -r requirements.txt +``` + +## Running locally + +### HTTP Function +To run the HTTP function locally, use the `functions-framework` command: +```sh +functions-framework --target=hello_async_http +``` +Then, send a request to it from another terminal: +```sh +curl localhost:8080 +# Output: Hello, async world! +``` + +### CloudEvent Function +To run the CloudEvent function, specify the target and set the signature type: +```sh +functions-framework --target=hello_async_cloudevent --signature-type=cloudevent +``` +Then, in another terminal, send a sample CloudEvent using the provided script: +```sh +python send_cloud_event.py +``` + +## Deploying to Cloud Run +You can deploy these functions to Cloud Run using the `gcloud` CLI. + +### HTTP Function +```sh +gcloud run deploy async-http-function \ + --source . \ + --function hello_async_http \ + --base-image python312 \ + --region \ + --allow-unauthenticated +``` + +### CloudEvent Function +```sh +gcloud run deploy async-cloudevent-function \ + --source . \ + --function hello_async_cloudevent \ + --base-image python312 \ + --region \ + --set-env-vars=FUNCTION_SIGNATURE_TYPE=cloudevent \ + --allow-unauthenticated +``` +After deploying, you can invoke the CloudEvent function by sending an HTTP POST request with a CloudEvent payload to its URL. + diff --git a/examples/cloud_run_async/main.py b/examples/cloud_run_async/main.py new file mode 100644 index 00000000..40062a95 --- /dev/null +++ b/examples/cloud_run_async/main.py @@ -0,0 +1,36 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import functions_framework.aio + +@functions_framework.aio.http +async def hello_async_http(request): + """ + An async HTTP function. + Args: + request (starlette.requests.Request): The request object. + Returns: + The response text, or an instance of any Starlette response class + (e.g. `starlette.responses.Response`). + """ + return "Hello, async world!" + +@functions_framework.aio.cloud_event +async def hello_async_cloudevent(cloud_event): + """ + An async CloudEvent function. + Args: + cloud_event (cloudevents.http.CloudEvent): The CloudEvent object. + """ + print(f"Received event with ID: {cloud_event['id']} and data {cloud_event.data}") diff --git a/examples/cloud_run_async/requirements.txt b/examples/cloud_run_async/requirements.txt new file mode 100644 index 00000000..464a6632 --- /dev/null +++ b/examples/cloud_run_async/requirements.txt @@ -0,0 +1,5 @@ +# Function dependencies +functions-framework + +# For testing +httpx diff --git a/examples/cloud_run_async/send_cloud_event.py b/examples/cloud_run_async/send_cloud_event.py new file mode 100644 index 00000000..a668d01e --- /dev/null +++ b/examples/cloud_run_async/send_cloud_event.py @@ -0,0 +1,39 @@ +#!/usr/local/bin/python + +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import asyncio +import httpx + +from cloudevents.http import CloudEvent, to_structured + +async def main(): + # Create a cloudevent + attributes = { + "Content-Type": "application/json", + "source": "from-galaxy-far-far-away", + "type": "cloudevent.greet.you", + } + data = {"name": "john"} + + event = CloudEvent(attributes, data) + + # Send the event to the local functions-framework server + headers, data = to_structured(event) + + async with httpx.AsyncClient() as client: + await client.post("http://localhost:8080/", headers=headers, data=data) + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/examples/cloud_run_streaming_http/README.md b/examples/cloud_run_streaming_http/README.md new file mode 100644 index 00000000..ee1f85cd --- /dev/null +++ b/examples/cloud_run_streaming_http/README.md @@ -0,0 +1,56 @@ +# Deploying streaming functions to Cloud Run + +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. + +## Dependencies +Install the dependencies for this example: +```sh +pip install -r requirements.txt +``` + +## Running locally + +### Synchronous Streaming +To run the synchronous streaming function locally: +```sh +functions-framework --target=hello_stream +``` +Then, send a request to it from another terminal: +```sh +curl localhost:8080 +``` + +### Asynchronous Streaming +To run the asynchronous streaming function locally: +```sh +functions-framework --target=hello_stream_async +``` +Then, send a request to it from another terminal: +```sh +curl localhost:8080 +``` + +## Deploying to Cloud Run +You can deploy these functions to Cloud Run using the `gcloud` CLI. + +### Synchronous Streaming +```sh +gcloud run deploy streaming-function \ + --source . \ + --function hello_stream \ + --base-image python312 \ + --region \ + --allow-unauthenticated +``` + +### Asynchronous Streaming +```sh +gcloud run deploy streaming-async-function \ + --source . \ + --function hello_stream_async \ + --base-image python312 \ + --region \ + --allow-unauthenticated +``` + +``` \ No newline at end of file diff --git a/examples/cloud_run_streaming_http/main.py b/examples/cloud_run_streaming_http/main.py new file mode 100644 index 00000000..5010b1a3 --- /dev/null +++ b/examples/cloud_run_streaming_http/main.py @@ -0,0 +1,59 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import time +import asyncio +import functions_framework +import functions_framework.aio +from starlette.responses import StreamingResponse + +# Helper function for the synchronous streaming example. +def slow_numbers(minimum, maximum): + yield '
    ' + for number in range(minimum, maximum + 1): + yield '
  • %d
  • ' % number + time.sleep(0.5) + yield '
' + +@functions_framework.http +def hello_stream(request): + """ + A synchronous HTTP function that streams a response. + Args: + request (flask.Request): The request object. + Returns: + A generator, which will be streamed as the response. + """ + generator = slow_numbers(1, 10) + return generator, {'Content-Type': 'text/html'} + +# Helper function for the asynchronous streaming example. +async def slow_numbers_async(minimum, maximum): + yield '
    ' + for number in range(minimum, maximum + 1): + yield '
  • %d
  • ' % number + await asyncio.sleep(0.5) + yield '
' + +@functions_framework.aio.http +async def hello_stream_async(request): + """ + An asynchronous HTTP function that streams a response. + Args: + request (starlette.requests.Request): The request object. + Returns: + A starlette.responses.StreamingResponse. + """ + generator = slow_numbers_async(1, 10) + return StreamingResponse(generator, media_type='text/html') diff --git a/examples/cloud_run_streaming_http/requirements.txt b/examples/cloud_run_streaming_http/requirements.txt new file mode 100644 index 00000000..42f9063b --- /dev/null +++ b/examples/cloud_run_streaming_http/requirements.txt @@ -0,0 +1,2 @@ +# Function dependencies +functions-framework From 6cdcc1fe9a588dd8b81606230a0ccf21c492eeb4 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Thu, 31 Jul 2025 18:44:04 -0700 Subject: [PATCH 2/5] feat: address PR feedback - Add version specifier to functions-framework dependency - Remove --allow-unauthenticated from gcloud commands - Fix typo in Cloud Run URL - Update Cloud Run link to Cloud Functions --- examples/README.md | 2 +- examples/cloud_run_async/README.md | 9 ++++----- examples/cloud_run_streaming_http/README.md | 6 ++---- examples/cloud_run_streaming_http/requirements.txt | 2 +- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/examples/README.md b/examples/README.md index 34fe3418..47b7c398 100644 --- a/examples/README.md +++ b/examples/README.md @@ -2,7 +2,7 @@ ## Deployment targets ### Cloud Run -* [`cloud_run_http`](./cloud_run_http/) - Deploying an HTTP function to [Cloud Run](http.cloud.google.com/run) with the Functions Framework +* [`cloud_run_http`](./cloud_run_http/) - Deploying an HTTP function to [Cloud Run](http://cloud.google.com/run) with the Functions Framework * [`cloud_run_event`](./cloud_run_event/) - Deploying a CloudEvent function to [Cloud Run](http://cloud.google.com/run) with the Functions Framework * [`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 * [`cloud_run_async`](./cloud_run_async/) - Deploying asynchronous HTTP and CloudEvent functions to [Cloud Run](http://cloud.google.com/run) with the Functions Framework diff --git a/examples/cloud_run_async/README.md b/examples/cloud_run_async/README.md index 80a80393..caa301b1 100644 --- a/examples/cloud_run_async/README.md +++ b/examples/cloud_run_async/README.md @@ -1,6 +1,6 @@ # Deploying async functions to Cloud Run -This sample shows how to deploy asynchronous functions to [Cloud Run](http://cloud.google.com/run) with the Functions Framework. It includes examples for both HTTP and CloudEvent functions, which can be found in the `main.py` file. +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. ## Dependencies Install the dependencies for this example: @@ -40,8 +40,7 @@ gcloud run deploy async-http-function \ --source . \ --function hello_async_http \ --base-image python312 \ - --region \ - --allow-unauthenticated + --region ``` ### CloudEvent Function @@ -51,8 +50,8 @@ gcloud run deploy async-cloudevent-function \ --function hello_async_cloudevent \ --base-image python312 \ --region \ - --set-env-vars=FUNCTION_SIGNATURE_TYPE=cloudevent \ - --allow-unauthenticated + --set-env-vars=FUNCTION_SIGNATURE_TYPE=cloudevent ``` After deploying, you can invoke the CloudEvent function by sending an HTTP POST request with a CloudEvent payload to its URL. +``` diff --git a/examples/cloud_run_streaming_http/README.md b/examples/cloud_run_streaming_http/README.md index ee1f85cd..93476105 100644 --- a/examples/cloud_run_streaming_http/README.md +++ b/examples/cloud_run_streaming_http/README.md @@ -39,8 +39,7 @@ gcloud run deploy streaming-function \ --source . \ --function hello_stream \ --base-image python312 \ - --region \ - --allow-unauthenticated + --region ``` ### Asynchronous Streaming @@ -49,8 +48,7 @@ gcloud run deploy streaming-async-function \ --source . \ --function hello_stream_async \ --base-image python312 \ - --region \ - --allow-unauthenticated + --region ``` ``` \ No newline at end of file diff --git a/examples/cloud_run_streaming_http/requirements.txt b/examples/cloud_run_streaming_http/requirements.txt index 42f9063b..60994743 100644 --- a/examples/cloud_run_streaming_http/requirements.txt +++ b/examples/cloud_run_streaming_http/requirements.txt @@ -1,2 +1,2 @@ # Function dependencies -functions-framework +functions-framework>=3.9.2 From e96742cb29580f5b0162a355bbf1f9a98170acaf Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Mon, 4 Aug 2025 11:42:55 -0700 Subject: [PATCH 3/5] Fix deploy instruction. --- examples/cloud_run_async/README.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/examples/cloud_run_async/README.md b/examples/cloud_run_async/README.md index caa301b1..4c569dea 100644 --- a/examples/cloud_run_async/README.md +++ b/examples/cloud_run_async/README.md @@ -3,7 +3,9 @@ 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. ## Dependencies + Install the dependencies for this example: + ```sh pip install -r requirements.txt ``` @@ -11,30 +13,40 @@ pip install -r requirements.txt ## Running locally ### HTTP Function + To run the HTTP function locally, use the `functions-framework` command: + ```sh functions-framework --target=hello_async_http ``` + Then, send a request to it from another terminal: + ```sh curl localhost:8080 # Output: Hello, async world! ``` ### CloudEvent Function + To run the CloudEvent function, specify the target and set the signature type: + ```sh functions-framework --target=hello_async_cloudevent --signature-type=cloudevent ``` + Then, in another terminal, send a sample CloudEvent using the provided script: + ```sh python send_cloud_event.py ``` ## Deploying to Cloud Run + You can deploy these functions to Cloud Run using the `gcloud` CLI. ### HTTP Function + ```sh gcloud run deploy async-http-function \ --source . \ @@ -44,14 +56,16 @@ gcloud run deploy async-http-function \ ``` ### CloudEvent Function + ```sh gcloud run deploy async-cloudevent-function \ --source . \ --function hello_async_cloudevent \ --base-image python312 \ - --region \ - --set-env-vars=FUNCTION_SIGNATURE_TYPE=cloudevent + --region ``` + After deploying, you can invoke the CloudEvent function by sending an HTTP POST request with a CloudEvent payload to its URL. + ``` From 172e33902d6fd1fcac0344dff78454f01f3aed0b Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Mon, 4 Aug 2025 11:43:47 -0700 Subject: [PATCH 4/5] nit. --- examples/cloud_run_async/requirements.txt | 5 ++--- examples/cloud_run_async/send_cloud_event.py | 7 +++---- examples/cloud_run_streaming_http/README.md | 16 +++++++++++++++- .../cloud_run_streaming_http/requirements.txt | 3 +-- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/examples/cloud_run_async/requirements.txt b/examples/cloud_run_async/requirements.txt index 464a6632..b862cce2 100644 --- a/examples/cloud_run_async/requirements.txt +++ b/examples/cloud_run_async/requirements.txt @@ -1,5 +1,4 @@ -# Function dependencies -functions-framework +functions-framework>=3.9.2,<4.0.0 # For testing -httpx +httpx<=0.28.1 diff --git a/examples/cloud_run_async/send_cloud_event.py b/examples/cloud_run_async/send_cloud_event.py index a668d01e..714bc719 100644 --- a/examples/cloud_run_async/send_cloud_event.py +++ b/examples/cloud_run_async/send_cloud_event.py @@ -18,8 +18,8 @@ from cloudevents.http import CloudEvent, to_structured + async def main(): - # Create a cloudevent attributes = { "Content-Type": "application/json", "source": "from-galaxy-far-far-away", @@ -29,11 +29,10 @@ async def main(): event = CloudEvent(attributes, data) - # Send the event to the local functions-framework server headers, data = to_structured(event) - + async with httpx.AsyncClient() as client: await client.post("http://localhost:8080/", headers=headers, data=data) if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file + asyncio.run(main()) diff --git a/examples/cloud_run_streaming_http/README.md b/examples/cloud_run_streaming_http/README.md index 93476105..47537ed4 100644 --- a/examples/cloud_run_streaming_http/README.md +++ b/examples/cloud_run_streaming_http/README.md @@ -3,7 +3,9 @@ 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. ## Dependencies + Install the dependencies for this example: + ```sh pip install -r requirements.txt ``` @@ -11,29 +13,39 @@ pip install -r requirements.txt ## Running locally ### Synchronous Streaming + To run the synchronous streaming function locally: + ```sh functions-framework --target=hello_stream ``` + Then, send a request to it from another terminal: + ```sh curl localhost:8080 ``` ### Asynchronous Streaming + To run the asynchronous streaming function locally: + ```sh functions-framework --target=hello_stream_async ``` + Then, send a request to it from another terminal: + ```sh curl localhost:8080 ``` ## Deploying to Cloud Run + You can deploy these functions to Cloud Run using the `gcloud` CLI. ### Synchronous Streaming + ```sh gcloud run deploy streaming-function \ --source . \ @@ -43,6 +55,7 @@ gcloud run deploy streaming-function \ ``` ### Asynchronous Streaming + ```sh gcloud run deploy streaming-async-function \ --source . \ @@ -51,4 +64,5 @@ gcloud run deploy streaming-async-function \ --region ``` -``` \ No newline at end of file +``` + diff --git a/examples/cloud_run_streaming_http/requirements.txt b/examples/cloud_run_streaming_http/requirements.txt index 60994743..83e77d02 100644 --- a/examples/cloud_run_streaming_http/requirements.txt +++ b/examples/cloud_run_streaming_http/requirements.txt @@ -1,2 +1 @@ -# Function dependencies -functions-framework>=3.9.2 +functions-framework>=3.9.2,<4.0.0 From 594df2d2b9136445deee2b6a1123b2a40f8074d7 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Mon, 4 Aug 2025 11:46:58 -0700 Subject: [PATCH 5/5] fix small typos. --- examples/cloud_run_async/README.md | 3 --- examples/cloud_run_streaming_http/README.md | 3 --- 2 files changed, 6 deletions(-) diff --git a/examples/cloud_run_async/README.md b/examples/cloud_run_async/README.md index 4c569dea..81de2747 100644 --- a/examples/cloud_run_async/README.md +++ b/examples/cloud_run_async/README.md @@ -66,6 +66,3 @@ gcloud run deploy async-cloudevent-function \ ``` After deploying, you can invoke the CloudEvent function by sending an HTTP POST request with a CloudEvent payload to its URL. - -``` - diff --git a/examples/cloud_run_streaming_http/README.md b/examples/cloud_run_streaming_http/README.md index 47537ed4..839b09c7 100644 --- a/examples/cloud_run_streaming_http/README.md +++ b/examples/cloud_run_streaming_http/README.md @@ -63,6 +63,3 @@ gcloud run deploy streaming-async-function \ --base-image python312 \ --region ``` - -``` -