diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml deleted file mode 100644 index 940bdf5..0000000 --- a/.github/workflows/conformance.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: Python Conformance CI -on: [push, pull_request] -jobs: - build: - runs-on: ubuntu-18.04 - strategy: - matrix: - python-version: ['3.8', '3.9', '3.10'] - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Setup Python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install the framework - run: python -m pip install -e . - - - name: Setup Go - uses: actions/setup-go@v2 - with: - go-version: '1.16' - - - name: Run HTTP conformance tests - uses: GoogleCloudPlatform/functions-framework-conformance/action@v1.1.0 - with: - version: 'v1.1.0' - functionType: 'http' - useBuildpacks: false - validateMapping: false - cmd: "'functions-framework --source tests/conformance/main.py --target write_http --signature-type http'" - - - name: Run event conformance tests - uses: GoogleCloudPlatform/functions-framework-conformance/action@v1.1.0 - with: - version: 'v1.1.0' - functionType: 'legacyevent' - useBuildpacks: false - validateMapping: true - cmd: "'functions-framework --source tests/conformance/main.py --target write_legacy_event --signature-type event'" - - - name: Run CloudEvents conformance tests - uses: GoogleCloudPlatform/functions-framework-conformance/action@v1.1.0 - with: - version: 'v1.1.0' - functionType: 'cloudevent' - useBuildpacks: false - validateMapping: true - cmd: "'functions-framework --source tests/conformance/main.py --target write_cloud_event --signature-type cloudevent'" - - - name: Run HTTP conformance tests declarative - uses: GoogleCloudPlatform/functions-framework-conformance/action@v1.1.0 - with: - version: 'v1.1.0' - functionType: 'http' - useBuildpacks: false - validateMapping: false - cmd: "'functions-framework --source tests/conformance/main.py --target write_http_declarative'" - - - name: Run CloudEvents conformance tests declarative - uses: GoogleCloudPlatform/functions-framework-conformance/action@v1.1.0 - with: - version: 'v1.1.0' - functionType: 'cloudevent' - useBuildpacks: false - validateMapping: true - cmd: "'functions-framework --source tests/conformance/main.py --target write_cloud_event_declarative'" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7bb0972..7dfe5ec 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -4,9 +4,9 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 - name: Install tox run: python -m pip install tox - name: Lint diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 31d8073..dcb8b7e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,11 +10,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: ref: ${{ github.event.release.tag_name }} - name: Install Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 - name: Install build dependencies run: python -m pip install -U setuptools build wheel - name: Build distributions diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index ea14749..04bdc4a 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -4,14 +4,14 @@ jobs: test: strategy: matrix: - python: ['3.6', '3.7', '3.8', '3.9', '3.10'] + python: ['3.8', '3.9', '3.10'] platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Use Python ${{ matrix.python }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} - name: Install tox diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index cfbbe99..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,190 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [3.0.0] - 2021-11-10 -### Fixed -- refactor: change declarative function signature from `cloudevent` to `cloud_event` ([#167]) - -## [2.4.0-beta.2] - 2021-11-01 -### Fixed -- fix: remove debug statements - -## [2.4.0-beta.1] - 2021-10-29 -### Added -- feat: Support declarative function signatures: `http` and `cloudevent` ([#160]) - -## [2.3.0] - 2021-10-12 -### Added -- feat: add support for Python 3.10 ([#151]) -### Changed -- fix: update event conversion ([#154]) -- fix: Move backwards-compatible logic before function source load ([#152]) -- fix: Add a DummyErrorHandler ([#137]) - -## [2.2.1] - 2021-06-01 -### Changed -- Update GCF Python 3.7 backwards-compatible logging ([#131]) - -## [2.2.0] - 2021-05-24 -### Added -- Relax constraint to `flask<3.0` and `click<9.0` ([#129]) - -## [2.1.3] - 2021-04-23 -### Changed -- Change gunicorn loglevel to error ([#122]) - -### Added -- Add support for background to CloudEvent conversion ([#116]) - -## [2.1.2] - 2021-02-23 -### Added -- Add crash header to 500 responses ([#114]) - -## [2.1.1] - 2021-02-17 -### Fixed -- Add backwards-compatible logging for GCF Python 3.7 ([#107]) -- Document `--dry-run` flag ([#105]) - -## [2.1.0] - 2020-12-23 -### Added -- Support Python 3.9 - -### Fixed -- Execute the source module w/in the app context ([#76]) - -## [2.0.0] - 2020-07-01 -### Added -- Support `cloudevent` signature type ([#55], [#56]) - -## [1.6.0] - 2020-08-19 -### Changed -- Add legacy GCF Python 3.7 behavior ([#77]) - -### Added -- Improve documentation around Dockerfiles ([#70]) - -## [1.5.0] - 2020-07-06 -### Changed -- Framework will consume entire request before responding ([#66]) - -## [1.4.4] - 2020-06-19 -### Fixed -- Improve module loading ([#61]) - -## [1.4.3] - 2020-05-14 -### Fixed -- Load the source file into the correct module name ([#49]) - -## [1.4.2] - 2020-05-13 -### Fixed -- Fix handling of `--debug` flag when gunicorn is not present ([#44]) - -## [1.4.1] - 2020-05-07 -### Fixed -- Fix Windows support ([#38]) - -## [1.4.0] - 2020-05-06 -### Changed -- Use gunicorn as a production HTTP server - -## [1.3.0] - 2020-04-13 -### Added -- Add support for running `python -m functions_framework` ([#31]) - -### Changed -- Move `functions_framework.cli.cli` to `functions_framework._cli._cli` -- Adjust path handling for robots.txt and favicon.ico ([#33]) - -## [1.2.0] - 2020-02-20 -### Added -- Add support for `--host` flag ([#20]) - -## [1.1.1] - 2020-02-06 -### Added -- Add support for `--dry-run` flag ([#14]) - -### Changed -- Make `--debug` a flag instead of a boolean option - -### Fixed -- Better support for CloudEvent functions and error handling - -## [1.0.1] - 2020-01-30 -### Added -- Add Cloud Run Button ([#1]) -- Add README badges ([#2]) -- Add `debug` flag documentation ([#7]) -- Add `watchdog` dependency ([#8]) - -### Fixed -- Fix `--signature-type` typo ([#4]) -- Fix `install_requires` typo ([#12]) - -## [1.0.0] - 2020-01-09 -### Added -- Initial release - -[Unreleased]: https://github.com/GoogleCloudPlatform/functions-framework-python/compare/v3.0.0...HEAD -[3.0.0]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v3.0.0 -[2.4.0-beta.2]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v2.4.0-beta.2 -[2.4.0-beta.1]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v2.4.0-beta.1 -[2.3.0]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v2.3.0 -[2.2.1]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v2.2.1 -[2.2.0]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v2.2.0 -[2.1.3]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v2.1.3 -[2.1.2]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v2.1.2 -[2.1.1]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v2.1.1 -[2.1.0]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v2.1.0 -[2.0.0]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v2.0.0 -[1.6.0]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v1.6.0 -[1.5.0]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v1.5.0 -[1.4.4]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v1.4.4 -[1.4.3]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v1.4.3 -[1.4.2]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v1.4.2 -[1.4.1]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v1.4.1 -[1.4.0]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v1.4.0 -[1.3.0]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v1.3.0 -[1.3.0]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v1.3.0 -[1.2.0]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v1.2.0 -[1.1.1]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v1.1.1 -[1.0.1]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v1.0.1 -[1.0.0]: https://github.com/GoogleCloudPlatform/functions-framework-python/releases/tag/v1.0.0 - -[#167]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/167 -[#160]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/160 -[#154]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/154 -[#152]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/152 -[#151]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/151 -[#137]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/137 -[#131]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/131 -[#129]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/129 -[#122]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/122 -[#116]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/116 -[#114]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/114 -[#107]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/107 -[#105]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/105 -[#77]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/77 -[#76]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/76 -[#70]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/70 -[#66]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/66 -[#61]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/61 -[#56]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/56 -[#55]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/55 -[#49]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/49 -[#44]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/44 -[#38]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/38 -[#33]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/33 -[#31]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/31 -[#20]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/20 -[#14]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/14 -[#12]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/12 -[#8]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/8 -[#7]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/7 -[#4]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/4 -[#2]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/2 -[#1]: https://github.com/GoogleCloudPlatform/functions-framework-python/pull/1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 939e534..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,28 +0,0 @@ -# How to Contribute - -We'd love to accept your patches and contributions to this project. There are -just a few small guidelines you need to follow. - -## Contributor License Agreement - -Contributions to this project must be accompanied by a Contributor License -Agreement. You (or your employer) retain the copyright to your contribution; -this simply gives us permission to use and redistribute your contributions as -part of the project. Head over to to see -your current agreements on file or to sign a new one. - -You generally only need to submit a CLA once, so if you've already submitted one -(even if it was for a different project), you probably don't need to do it -again. - -## Code reviews - -All submissions, including submissions by project members, require review. We -use GitHub pull requests for this purpose. Consult -[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more -information on using pull requests. - -## Community Guidelines - -This project follows [Google's Open Source Community -Guidelines](https://opensource.google.com/conduct/). diff --git a/README.md b/README.md index 42e338a..e0b18b8 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,24 @@ # Functions Framework for Python -[![PyPI version](https://badge.fury.io/py/functions-framework.svg)](https://badge.fury.io/py/functions-framework) +[![PyPI version](https://badge.fury.io/py/ofn-functions-framework.svg)](https://badge.fury.io/py/ofn-functions-framework) -[![Python unit CI][ff_python_unit_img]][ff_python_unit_link] [![Python lint CI][ff_python_lint_img]][ff_python_lint_link] [![Python conformace CI][ff_python_conformance_img]][ff_python_conformance_link] +This project is inspired by [GCP functions-framework-python](https://github.com/GoogleCloudPlatform/functions-framework-python) . -An open source FaaS (Function as a service) framework for writing portable -Python functions. +An open source FaaS (Function as a service) framework for writing portable Python functions. -The Functions Framework lets you write lightweight functions that run in many -different environments, including: +The Functions Framework lets you write lightweight functions that run in many different environments, including: * [OpenFunction](https://github.com/OpenFunction/OpenFunction) * [Knative](https://github.com/knative/)-based environments * [Dapr](https://dapr.io/)-based environments -* [Google Cloud Functions](https://cloud.google.com/functions/) * Your local development machine -* [Cloud Run and Cloud Run for Anthos](https://cloud.google.com/run/) The framework allows you to go from: ```python -def hello(request): +from functions_framework.context.user_context import UserContext + +def hello(context: UserContext): return "Hello world!" ``` @@ -37,21 +35,21 @@ All without needing to worry about writing an HTTP server or complicated request * Spin up a local development server for quick testing * Invoke a function in response to a request -* Automatically unmarshal events conforming to the [CloudEvents](https://cloudevents.io/) spec * Portable between serverless platforms +* Can integrate various data middlewares ## Installation Install the Functions Framework via `pip`: ```sh -pip install functions-framework +pip install ofn-functions-framework ``` Or, for deployment, add the Functions Framework to your `requirements.txt` file: ``` -functions-framework==3.* +ofn-functions-framework==0.2.0 ``` ## Quickstarts @@ -61,30 +59,26 @@ functions-framework==3.* Create an `main.py` file with the following contents: ```python -import functions_framework +from functions_framework.context.user_context import UserContext + -@functions_framework.http -def hello(request): +def hello(context: UserContext): + # context.get_http_request() return "Hello world!" ``` -> Your function is passed a single parameter, `(request)`, which is a Flask [`Request`](http://flask.pocoo.org/docs/1.0/api/#flask.Request) object. +Export Function Context: + +```shell +export FUNC_CONTEXT='{"name":"function_name","version":"v1","triggers":{"http":{"port":8080}}}' +``` Run the following command: ```sh -functions-framework --target hello --debug - * Serving Flask app "hello" (lazy loading) - * Environment: production - WARNING: This is a development server. Do not use it in a production deployment. - Use a production WSGI server instead. - * Debug mode: on - * Running on http://0.0.0.0:8080/ (Press CTRL+C to quit) +ff --source hello_world.py --target hello ``` -(You can also use `functions-framework-python` if you have multiple -language frameworks installed). - Open http://localhost:8080/ in your browser and see *Hello world!*. Or send requests to this function using `curl` from another terminal window: @@ -94,211 +88,7 @@ curl localhost:8080 # Output: Hello world! ``` -### Quickstart: CloudEvent Function - -Create an `main.py` file with the following contents: - -```python -import functions_framework - -@functions_framework.cloud_event -def hello_cloud_event(cloud_event): - print(f"Received event with ID: {cloud_event['id']} and data {cloud_event.data}") -``` - -> Your function is passed a single [CloudEvent](https://github.com/cloudevents/sdk-python/blob/master/cloudevents/sdk/event/v1.py) parameter. - -Run the following command to run `hello_cloud_event` target locally: - -```sh -functions-framework --target=hello_cloud_event -``` - -In a different terminal, `curl` the Functions Framework server: - -```sh -curl -X POST localhost:8080 \ - -H "Content-Type: application/cloudevents+json" \ - -d '{ - "specversion" : "1.0", - "type" : "example.com.cloud.event", - "source" : "https://example.com/cloudevents/pull", - "subject" : "123", - "id" : "A234-1234-1234", - "time" : "2018-04-05T17:31:00Z", - "data" : "hello world" -}' -``` - -Output from the terminal running `functions-framework`: -``` -Received event with ID: A234-1234-1234 and data hello world -``` - -More info on sending [CloudEvents](http://cloudevents.io) payloads, see [`examples/cloud_run_cloud_events`](examples/cloud_run_cloud_events/) instruction. - - -### Quickstart: Error handling - -The framework includes an error handler that is similar to the -[`flask.Flask.errorhandler`](https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.errorhandler) -function, which allows you to handle specific error types with a decorator: - -```python -import functions_framework - - -@functions_framework.errorhandler(ZeroDivisionError) -def handle_zero_division(e): - return "I'm a teapot", 418 - - -def function(request): - 1 / 0 - return "Success", 200 -``` - -This function will catch the `ZeroDivisionError` and return a different -response instead. - -### Quickstart: Pub/Sub emulator -1. Create a `main.py` file with the following contents: - - ```python - def hello(event, context): - print("Received", context.event_id) - ``` - -1. Start the Functions Framework on port 8080: - - ```sh - functions-framework --target=hello --signature-type=event --debug --port=8080 - ``` - -1. In a second terminal, start the Pub/Sub emulator on port 8085. - - ```sh - export PUBSUB_PROJECT_ID=my-project - gcloud beta emulators pubsub start \ - --project=$PUBSUB_PROJECT_ID \ - --host-port=localhost:8085 - ``` - - You should see the following after the Pub/Sub emulator has started successfully: - - ```none - [pubsub] INFO: Server started, listening on 8085 - ``` - -1. In a third terminal, create a Pub/Sub topic and attach a push subscription to the topic, using `http://localhost:8080` as its push endpoint. [Publish](https://cloud.google.com/pubsub/docs/quickstart-client-libraries#publish_messages) some messages to the topic. Observe your function getting triggered by the Pub/Sub messages. - - ```sh - export PUBSUB_PROJECT_ID=my-project - export TOPIC_ID=my-topic - export PUSH_SUBSCRIPTION_ID=my-subscription - $(gcloud beta emulators pubsub env-init) - - git clone https://github.com/googleapis/python-pubsub.git - cd python-pubsub/samples/snippets/ - pip install -r requirements.txt - - python publisher.py $PUBSUB_PROJECT_ID create $TOPIC_ID - python subscriber.py $PUBSUB_PROJECT_ID create-push $TOPIC_ID $PUSH_SUBSCRIPTION_ID http://localhost:8080 - python publisher.py $PUBSUB_PROJECT_ID publish $TOPIC_ID - ``` - - You should see the following after the commands have run successfully: - - ```none - Created topic: projects/my-project/topics/my-topic - - topic: "projects/my-project/topics/my-topic" - push_config { - push_endpoint: "http://localhost:8080" - } - ack_deadline_seconds: 10 - message_retention_duration { - seconds: 604800 - } - . - Endpoint for subscription is: http://localhost:8080 - - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - Published messages to projects/my-project/topics/my-topic. - ``` - - And in the terminal where the Functions Framework is running: - - ```none - * Serving Flask app "hello" (lazy loading) - * Environment: production - WARNING: This is a development server. Do not use it in a production deployment. - Use a production WSGI server instead. - * Debug mode: on - * Running on http://0.0.0.0:8080/ (Press CTRL+C to quit) - * Restarting with fsevents reloader - * Debugger is active! - * Debugger PIN: 911-794-046 - Received 1 - 127.0.0.1 - - [11/Aug/2021 14:42:22] "POST / HTTP/1.1" 200 - - Received 2 - 127.0.0.1 - - [11/Aug/2021 14:42:22] "POST / HTTP/1.1" 200 - - Received 5 - 127.0.0.1 - - [11/Aug/2021 14:42:22] "POST / HTTP/1.1" 200 - - Received 6 - 127.0.0.1 - - [11/Aug/2021 14:42:22] "POST / HTTP/1.1" 200 - - Received 7 - 127.0.0.1 - - [11/Aug/2021 14:42:22] "POST / HTTP/1.1" 200 - - Received 8 - 127.0.0.1 - - [11/Aug/2021 14:42:22] "POST / HTTP/1.1" 200 - - Received 9 - 127.0.0.1 - - [11/Aug/2021 14:42:39] "POST / HTTP/1.1" 200 - - Received 3 - 127.0.0.1 - - [11/Aug/2021 14:42:39] "POST / HTTP/1.1" 200 - - Received 4 - 127.0.0.1 - - [11/Aug/2021 14:42:39] "POST / HTTP/1.1" 200 - - ``` - -For more details on extracting data from a Pub/Sub event, see -https://cloud.google.com/functions/docs/tutorials/pubsub#functions_helloworld_pubsub_tutorial-python - -### Quickstart: Build a Deployable Container - -1. Install [Docker](https://store.docker.com/search?type=edition&offering=community) and the [`pack` tool](https://buildpacks.io/docs/install-pack/). - -1. Build a container from your function using the Functions [buildpacks](https://github.com/GoogleCloudPlatform/buildpacks): - - pack build \ - --builder gcr.io/buildpacks/builder:v1 \ - --env GOOGLE_FUNCTION_SIGNATURE_TYPE=http \ - --env GOOGLE_FUNCTION_TARGET=hello \ - my-first-function - -1. Start the built container: - - docker run --rm -p 8080:8080 my-first-function - # Output: Serving function... - -1. Send requests to this function using `curl` from another terminal window: - - curl localhost:8080 - # Output: Hello World! - -## Run your function on serverless platforms - -### Container environments based on Knative - -The Functions Framework is designed to be compatible with Knative environments. Build and deploy your container to a Knative environment. - -### OpenFunction +## Run your function on OpenFunction ![OpenFunction Platform Overview](https://openfunction.dev/openfunction-0.5-architecture.png) @@ -310,73 +100,19 @@ Asynchronous function introduces Dapr pub/sub to provide a platform-agnostic API More details would be brought up to you in some quickstart samples, stay tuned. -### Google Cloud Functions - -This Functions Framework is based on the [Python Runtime on Google Cloud Functions](https://cloud.google.com/functions/docs/concepts/python-runtime). - -On Cloud Functions, using the Functions Framework is not necessary: you don't need to add it to your `requirements.txt` file. - -After you've written your function, you can simply deploy it from your local machine using the `gcloud` command-line tool. [Check out the Cloud Functions quickstart](https://cloud.google.com/functions/docs/quickstart). - -### Cloud Run/Cloud Run on GKE - -Once you've written your function and added the Functions Framework to your `requirements.txt` file, all that's left is to create a container image. [Check out the Cloud Run quickstart](https://cloud.google.com/run/docs/quickstarts/build-and-deploy) for Python to create a container image and deploy it to Cloud Run. You'll write a `Dockerfile` when you build your container. This `Dockerfile` allows you to specify exactly what goes into your container (including custom binaries, a specific operating system, and more). [Here is an example `Dockerfile` that calls Functions Framework.](https://github.com/GoogleCloudPlatform/functions-framework-python/blob/master/examples/cloud_run_http) - -If you want even more control over the environment, you can [deploy your container image to Cloud Run on GKE](https://cloud.google.com/run/docs/quickstarts/prebuilt-deploy-gke). With Cloud Run on GKE, you can run your function on a GKE cluster, which gives you additional control over the environment (including use of GPU-based instances, longer timeouts and more). - -### Container environments based on Knative - -Cloud Run and Cloud Run on GKE both implement the [Knative Serving API](https://www.knative.dev/docs/). The Functions Framework is designed to be compatible with Knative environments. Just build and deploy your container to a Knative environment. - ## Configure the Functions Framework You can configure the Functions Framework using command-line flags or environment variables. If you specify both, the environment variable will be ignored. -| Command-line flag | Environment variable | Description | -| ------------------ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `--host` | `HOST` | The host on which the Functions Framework listens for requests. Default: `0.0.0.0` | -| `--port` | `PORT` | The port on which the Functions Framework listens for requests. Default: `8080` | -| `--target` | `FUNCTION_TARGET` | The name of the exported function to be invoked in response to requests. Default: `function` | -| `--signature-type` | `FUNCTION_SIGNATURE_TYPE` | The signature used when writing your function. Controls unmarshalling rules and determines which arguments are used to invoke your function. Default: `http`; accepted values: `http`, `event` or `cloudevent` | -| `--source` | `FUNCTION_SOURCE` | The path to the file containing your function. Default: `main.py` (in the current working directory) | -| `--debug` | `DEBUG` | A flag that allows to run functions-framework to run in debug mode, including live reloading. Default: `False` | -| `--dry-run` | `DRY_RUN` | A flag that allows for testing the function build from the configuration without creating a server. Default: `False` | - -## Enable Google Cloud Function Events - -The Functions Framework can unmarshall incoming -Google Cloud Functions [event](https://cloud.google.com/functions/docs/concepts/events-triggers#events) payloads to `event` and `context` objects. -These will be passed as arguments to your function when it receives a request. -Note that your function must use the `event`-style function signature: - -```python -def hello(event, context): - print(event) - print(context) -``` - -To enable automatic unmarshalling, set the function signature type to `event` - using the `--signature-type` command-line flag or the `FUNCTION_SIGNATURE_TYPE` environment variable. By default, the HTTP -signature will be used and automatic event unmarshalling will be disabled. - -For more details on this signature type, see the Google Cloud Functions -documentation on -[background functions](https://cloud.google.com/functions/docs/writing/background#cloud_pubsub_example). - -See the [running example](examples/cloud_run_event). +| Command-line flag | Environment variable | Description | +| ----------------- | -------------------- | ------------------------------------------------------------ | +| `--host` | `HOST` | The host on which the Functions Framework listens for requests. Default: `0.0.0.0` | +| `--port` | `PORT` | The port on which the Functions Framework listens for requests. Default: `8080` | +| `--target` | `FUNCTION_TARGET` | The name of the exported function to be invoked in response to requests. Default: `function` | +| `--source` | `FUNCTION_SOURCE` | The path to the file containing your function. Default: `main.py` (in the current working directory) | +| `--debug` | `DEBUG` | A flag that allows to run functions-framework to run in debug mode, including live reloading. Default: `False` | +| `--dry-run` | `DRY_RUN` | A flag that allows for testing the function build from the configuration without creating a server. Default: `False` | ## Advanced Examples More advanced guides can be found in the [`examples/`](examples/) directory. -You can also find examples on using the CloudEvent Python SDK [here](https://github.com/cloudevents/sdk-python). - -## Contributing - -Contributions to this library are welcome and encouraged. See [CONTRIBUTING](CONTRIBUTING.md) for more information on how to get started. - -[ff_python_unit_img]: https://github.com/GoogleCloudPlatform/functions-framework-python/workflows/Python%20Unit%20CI/badge.svg -[ff_python_unit_link]: https://github.com/GoogleCloudPlatform/functions-framework-python/actions?query=workflow%3A"Python+Unit+CI" -[ff_python_lint_img]: https://github.com/GoogleCloudPlatform/functions-framework-python/workflows/Python%20Lint%20CI/badge.svg -[ff_python_lint_link]: https://github.com/GoogleCloudPlatform/functions-framework-python/actions?query=workflow%3A"Python+Lint+CI" -[ff_python_conformance_img]: https://github.com/GoogleCloudPlatform/functions-framework-python/workflows/Python%20Conformance%20CI/badge.svg -[ff_python_conformance_link]: https://github.com/GoogleCloudPlatform/functions-framework-python/actions?query=workflow%3A"Python+Conformance+CI" diff --git a/docs/async-server.puml b/docs/async-server.puml deleted file mode 100644 index 0d1beff..0000000 --- a/docs/async-server.puml +++ /dev/null @@ -1,59 +0,0 @@ -@startuml Async Server - -box Function Process in Local Environment or Container -control ENTRYPOINT -participant "~__main__" as Main -participant AsyncServer -participant DaprServer -participant gRPCServer [ - Web Server - ---- - ""gprc.server"" -] -end box - -entity "Dapr Sidecar " as DaprSidecar - -== OpenFunction Serving == - -ENTRYPOINT -> Main ** : execute -note over ENTRYPOINT, Main: Pass through __CLI arguments__ and \ncontainer __environment variables__ - -Main -> Main : load user function file -note left: ""function (ctx, data) {}"" - -Main -> AsyncServer ** : create -note over Main, AsyncServer: Hand over __user function__ and __context__ - -AsyncServer -> DaprServer ** : ""new"" -note over AsyncServer, DaprServer: Extract __port__ from __context__ and pass down - -DaprServer -> gRPCServer ** : ""new"" -||| -DaprServer --> DaprSidecar : Waiting till Dapr sidecar started -... -AsyncServer -> DaprServer : register __user function__ as handler \nfor each of __inputs__ in __context__ -DaprServer -> gRPCServer : add routes for Dapr style \nsubscriptions and input bindings - -... - -== OpenFunction Triggering == - -DaprSidecar <-- : sub / input data - -DaprSidecar -> gRPCServer ++ : Dapr request with "data" - -gRPCServer -> gRPCServer ++ : invoke user function - -alt - gRPCServer -> DaprSidecar ++ : publish data or invoke output binding - DaprSidecar --> gRPCServer -- : execution result -end - -return - -return server app response - -... - -@enduml \ No newline at end of file diff --git a/docs/http-binding.puml b/docs/http-binding.puml deleted file mode 100644 index 20b12f1..0000000 --- a/docs/http-binding.puml +++ /dev/null @@ -1,73 +0,0 @@ -@startuml HTTP Binding - -box Function Process in Local Environment or Container -control ENTRYPOINT -participant "~__main__" as Main -participant HTTPServer -participant Server [ - Web Server - ---- - ""Flask/Gunicorn"" -] -participant Middleware -participant "User Function" as UserFunction -participant DaprClient -end box - -entity "Dapr Sidecar " as DaprSidecar - -== OpenFunction Serving == - -ENTRYPOINT -> Main ** : execute -note over ENTRYPOINT, Main: Pass through __CLI arguments__ and \ncontainer __environment variables__ - -Main -> Main : load user fnction file -note left: ""function (request) {}"" - -Main -> HTTPServer ** : create -note over Main, HTTPServer: Hand over __user function__, __function type__ \nand __context__ parsed from env variables - -HTTPServer -> Server ** : new -note over Server: Depend on debug mode - -HTTPServer -> Middleware ** : new -HTTPServer -> Server : use Middleware -note over HTTPServer, Server: Pass context to middleware -||| -HTTPServer -> Server : use others middlewares -||| -HTTPServer -> UserFunction ** : wrap user function -note over HTTPServer, UserFunction: Register as HTTP or CloudEvent Function -HTTPServer -> Server : bind wrapper to "/*" route - -... - -== OpenFunction Invocation == - -[-> Server ++ : HTTP request to "/" - -Server -> UserFunction ++ : execute user function -UserFunction --> Server -- : return execution result "data" - -alt ""runtime"" = ""knative"" and ""outputs"" is not empty - Server -> Middleware ++ : invoke Middleware - - Middleware -> DaprClient ** : new - - loop each OpenFunction Output - Middleware -> DaprClient ++ : send "data" - - DaprClient -> DaprSidecar ++ : invoke binding or publication with "data" - DaprSidecar --> DaprClient -- : return result - - DaprClient --> Middleware -- : forward result - end - - Middleware --> Server -- : return "data" as response -end - -[<- Server -- : send response - -... - -@enduml diff --git a/examples/README.md b/examples/README.md index 7960a74..fe7301c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,11 +1,5 @@ # Python Functions Frameworks Examples -## 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_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 +- [HTTP trigger exmaple](openfunction_http_trigger) -## Development Tools -* [`docker-compose`](./docker-compose) - -* [`skaffold`](./skaffold) - Developing multiple functions on the same host using Minikube and Skaffold +- [DAPR trigger example](openfunction_dapr_trigger) diff --git a/examples/cloud_run_cloud_events/Dockerfile b/examples/cloud_run_cloud_events/Dockerfile deleted file mode 100644 index bc9df89..0000000 --- a/examples/cloud_run_cloud_events/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -# Use the official Python image. -# https://hub.docker.com/_/python -FROM python:3.7-slim - -# Copy local code to the container image. -ENV APP_HOME /app -ENV PYTHONUNBUFFERED TRUE - -WORKDIR $APP_HOME -COPY . . - -# Install production dependencies. -RUN pip install gunicorn cloudevents functions-framework -RUN pip install -r requirements.txt -RUN chmod +x send_cloudevent.py - -# Run the web service on container startup. -CMD ["functions-framework", "--target=hello", "--signature-type=cloudevent"] diff --git a/examples/cloud_run_cloud_events/README.md b/examples/cloud_run_cloud_events/README.md deleted file mode 100644 index 4bf5452..0000000 --- a/examples/cloud_run_cloud_events/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Deploying a CloudEvent Function to Cloud Run with the Functions Framework - -This sample uses the [CloudEvents SDK](https://github.com/cloudevents/sdk-python) to send and receive a [CloudEvent](http://cloudevents.io) on Cloud Run. - -## How to run this locally - -Build the Docker image: - -```commandline -docker build -t cloud_event_example . -``` - -Run the image and bind the correct ports: - -```commandline -docker run --rm -p 8080:8080 -e PORT=8080 cloud_event_example -``` - -Send an event to the container: - -```python -docker run -t cloud_event_example send_cloud_event.py -``` diff --git a/examples/cloud_run_cloud_events/main.py b/examples/cloud_run_cloud_events/main.py deleted file mode 100644 index eb6d6dc..0000000 --- a/examples/cloud_run_cloud_events/main.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2020 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. - -# This sample creates a function using the CloudEvents SDK -# (https://github.com/cloudevents/sdk-python) - - -def hello(cloud_event): - print(f"Received event with ID: {cloud_event['id']} and data {cloud_event.data}") diff --git a/examples/cloud_run_cloud_events/requirements.txt b/examples/cloud_run_cloud_events/requirements.txt deleted file mode 100644 index 0a7427c..0000000 --- a/examples/cloud_run_cloud_events/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -# Optionally include additional dependencies here -cloudevents>=1.2.0 -requests diff --git a/examples/cloud_run_cloud_events/send_cloud_event.py b/examples/cloud_run_cloud_events/send_cloud_event.py deleted file mode 100644 index b523c31..0000000 --- a/examples/cloud_run_cloud_events/send_cloud_event.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/local/bin/python - -# Copyright 2020 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. -from cloudevents.http import CloudEvent, to_structured -import requests - - -# Create a cloudevent using https://github.com/cloudevents/sdk-python -# Note we only need source and type because the cloudevents constructor by -# default will set "specversion" to the most recent cloudevent version (e.g. 1.0) -# and "id" to a generated uuid.uuid4 string. -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 our local docker container listening on port 8080 -headers, data = to_structured(event) -requests.post("http://localhost:8080/", headers=headers, data=data) diff --git a/examples/cloud_run_decorator/Dockerfile b/examples/cloud_run_decorator/Dockerfile deleted file mode 100644 index 717e5a9..0000000 --- a/examples/cloud_run_decorator/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -# Use the official Python image. -# https://hub.docker.com/_/python -FROM python:3.7-slim - -# Copy local code to the container image. -ENV APP_HOME /app -ENV PYTHONUNBUFFERED TRUE - -WORKDIR $APP_HOME -COPY . . - -# Install production dependencies. -RUN pip install functions-framework -RUN pip install -r requirements.txt - -# Run the web service on container startup. -CMD exec functions-framework --target=hello_http diff --git a/examples/cloud_run_decorator/README.md b/examples/cloud_run_decorator/README.md deleted file mode 100644 index ba560b6..0000000 --- a/examples/cloud_run_decorator/README.md +++ /dev/null @@ -1,23 +0,0 @@ -## How to run this locally - -This guide shows how to run `hello_http` target locally. -To test with `hello_cloud_event`, change the target accordingly in Dockerfile. - -Build the Docker image: - -```commandline -docker build -t decorator_example . -``` - -Run the image and bind the correct ports: - -```commandline -docker run --rm -p 8080:8080 -e PORT=8080 decorator_example -``` - -Send requests to this function using `curl` from another terminal window: - -```sh -curl localhost:8080 -# Output: Hello world! -``` \ No newline at end of file diff --git a/examples/cloud_run_decorator/main.py b/examples/cloud_run_decorator/main.py deleted file mode 100644 index 19f96ee..0000000 --- a/examples/cloud_run_decorator/main.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2021 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. - -# This sample creates a function using the CloudEvents SDK -# (https://github.com/cloudevents/sdk-python) -import functions_framework - - -@functions_framework.cloud_event -def hello_cloud_event(cloud_event): - return f"Received event with ID: {cloud_event['id']} and data {cloud_event.data}" - - -@functions_framework.http -def hello_http(request): - return "Hello world!" diff --git a/examples/cloud_run_decorator/requirements.txt b/examples/cloud_run_decorator/requirements.txt deleted file mode 100644 index 33c5f99..0000000 --- a/examples/cloud_run_decorator/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -# Optionally include additional dependencies here diff --git a/examples/cloud_run_event/Dockerfile b/examples/cloud_run_event/Dockerfile deleted file mode 100644 index 7fa0df1..0000000 --- a/examples/cloud_run_event/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -# Use the official Python image. -# https://hub.docker.com/_/python -FROM python:3.7-slim - -# Copy local code to the container image. -ENV APP_HOME /app -ENV PYTHONUNBUFFERED TRUE - -WORKDIR $APP_HOME -COPY . . - -# Install production dependencies. -RUN pip install gunicorn functions-framework -RUN pip install -r requirements.txt - -# Run the web service on container startup. -CMD exec functions-framework --target=hello --signature-type=event diff --git a/examples/cloud_run_event/main.py b/examples/cloud_run_event/main.py deleted file mode 100644 index e5ca470..0000000 --- a/examples/cloud_run_event/main.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright 2020 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. - - -def hello(event, context): - pass diff --git a/examples/cloud_run_event/requirements.txt b/examples/cloud_run_event/requirements.txt deleted file mode 100644 index 33c5f99..0000000 --- a/examples/cloud_run_event/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -# Optionally include additional dependencies here diff --git a/examples/cloud_run_http/Dockerfile b/examples/cloud_run_http/Dockerfile deleted file mode 100644 index b7d6f50..0000000 --- a/examples/cloud_run_http/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -# Use the official Python image. -# https://hub.docker.com/_/python -FROM python:3.7-slim - -# Copy local code to the container image. -ENV APP_HOME /app -ENV PYTHONUNBUFFERED TRUE - -WORKDIR $APP_HOME -COPY . . - -# Install production dependencies. -RUN pip install functions-framework -RUN pip install -r requirements.txt - -# Run the web service on container startup. -CMD exec functions-framework --target=hello diff --git a/examples/cloud_run_http/README.md b/examples/cloud_run_http/README.md deleted file mode 100644 index 4cbe96d..0000000 --- a/examples/cloud_run_http/README.md +++ /dev/null @@ -1,69 +0,0 @@ -# Deploy a function to Cloud Run - -[![Run on Google Cloud](https://deploy.cloud.run/button.svg)](https://deploy.cloud.run) - -This guide will show you how to deploy the following example function to [Cloud Run](https://cloud.google.com/run): - -```python -def hello(request): - return "Hello world!" -``` - -This guide assumes your Python function is defined in a `main.py` file and dependencies are specified in `requirements.txt` file. - -## Running your function in a container - -To run your function in a container, create a `Dockerfile` with the following contents: - -```Dockerfile -# Use the official Python image. -# https://hub.docker.com/_/python -FROM python:3.7-slim - -# Copy local code to the container image. -ENV APP_HOME /app -WORKDIR $APP_HOME -COPY . . - -# Install production dependencies. -RUN pip install functions-framework -RUN pip install -r requirements.txt - -# Run the web service on container startup. -CMD exec functions-framework --target=hello -``` - -Start the container locally by running `docker build` and `docker run`: - -```sh -docker build -t helloworld . && docker run --rm -p 8080:8080 -e PORT=8080 helloworld -``` - -Send requests to this function using `curl` from another terminal window: - -```sh -curl localhost:8080 -# Output: Hello world! -``` - -## Configure gcloud - -To use Docker with gcloud, [configure the Docker credential helper](https://cloud.google.com/container-registry/docs/advanced-authentication): - -```sh -gcloud auth configure-docker -``` - -## Deploy a Container - -You can deploy your containerized function to Cloud Run by following the [Cloud Run quickstart](https://cloud.google.com/run/docs/quickstarts/build-and-deploy). - -Use the `docker` and `gcloud` CLIs to build and deploy a container to Cloud Run, replacing `[PROJECT-ID]` with the project id and `helloworld` with a different image name if necessary: - -```sh -docker build -t gcr.io/[PROJECT-ID]/helloworld . -docker push gcr.io/[PROJECT-ID]/helloworld -gcloud run deploy helloworld --image gcr.io/[PROJECT-ID]/helloworld --region us-central1 -``` - -If you want even more control over the environment, you can [deploy your container image to Cloud Run on GKE](https://cloud.google.com/run/docs/quickstarts/prebuilt-deploy-gke). With Cloud Run on GKE, you can run your function on a GKE cluster, which gives you additional control over the environment (including use of GPU-based instances, longer timeouts and more). diff --git a/examples/cloud_run_http/main.py b/examples/cloud_run_http/main.py deleted file mode 100644 index 5253627..0000000 --- a/examples/cloud_run_http/main.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright 2020 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. - - -def hello(request): - return "Hello world!" diff --git a/examples/cloud_run_http/requirements.txt b/examples/cloud_run_http/requirements.txt deleted file mode 100644 index 33c5f99..0000000 --- a/examples/cloud_run_http/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -# Optionally include additional dependencies here diff --git a/examples/docker-compose/Dockerfile b/examples/docker-compose/Dockerfile deleted file mode 100644 index a7abbfb..0000000 --- a/examples/docker-compose/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -# Use the Python base image -FROM python - -# Set a working directory -WORKDIR /func - -# Copy all the files from the local directory into the container -COPY . . - -# Install the Functions Framework -RUN pip install functions-framework - -# Install any dependencies of the function -RUN pip install -r requirements.txt - -# Run the function -CMD ["functions-framework", "--target=hello", "--debug"] diff --git a/examples/docker-compose/README.md b/examples/docker-compose/README.md deleted file mode 100644 index 68ce73b..0000000 --- a/examples/docker-compose/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# Developing functions with Docker Compose - -## Introduction - -This examples shows you how to develop a Cloud Function locally with Docker Compose, including live reloading. - -## Install `docker-compose`: -https://docs.docker.com/compose/install/ - -## Start the `docker-compose` environment: - -In this directory, bring up the `docker-compose` environment with: -``` -docker-compose up -``` - -You should see output similar to: - -``` -Building function -[+] Building 7.0s (10/10) FINISHED - => [internal] load build definition from Dockerfile 0.0s - => => transferring dockerfile: 431B 0.0s - => [internal] load .dockerignore 0.0s - => => transferring context: 2B 0.0s - => [internal] load metadata for docker.io/library/python:latest 0.6s - => [1/5] FROM docker.io/library/python@sha256:7a93befe45f3afb6b337 0.0s - => [internal] load build context 0.0s - => => transferring context: 2.11kB 0.0s - => CACHED [2/5] WORKDIR /func 0.0s - => [3/5] COPY . . 0.0s - => [4/5] RUN pip install functions-framework 4.7s - => [5/5] RUN pip install -r requirements.txt 1.1s - => exporting to image 0.4s - => => exporting layers 0.4s - => => writing image sha256:99962e5907e80856af6b032aa96a3130dde9ab6 0.0s - => => naming to docker.io/library/docker-compose_function 0.0s - -Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them -Recreating docker-compose_function_1 ... done -Attaching to docker-compose_function_1 -function_1 | * Serving Flask app 'hello' (lazy loading) -function_1 | * Environment: production -function_1 | WARNING: This is a development server. Do not use it in a production deployment. -function_1 | Use a production WSGI server instead. -function_1 | * Debug mode: on -function_1 | * Running on all addresses. -function_1 | WARNING: This is a development server. Do not use it in a production deployment. -function_1 | * Running on http://172.21.0.2:8080/ (Press CTRL+C to quit) -function_1 | * Restarting with watchdog (inotify) -function_1 | * Debugger is active! -``` -function_1 | * Debugger PIN: 162-882-413 - -## Call your Cloud Function - -Leaving the previous command running, in a **new terminal**, call your functions. To call the `hello` function: - -```bash -curl localhost:8080/hello -``` - -You should see output similar to: - -```terminal -Hello, World! -``` diff --git a/examples/docker-compose/docker-compose.yml b/examples/docker-compose/docker-compose.yml deleted file mode 100644 index b801ce1..0000000 --- a/examples/docker-compose/docker-compose.yml +++ /dev/null @@ -1,8 +0,0 @@ -version: "3.9" -services: - function: - build: . - ports: - - "8080:8080" - volumes: - - .:/func diff --git a/examples/docker-compose/main.py b/examples/docker-compose/main.py deleted file mode 100644 index cf91e51..0000000 --- a/examples/docker-compose/main.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright 2021 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. - - -def hello(request): - """Return a friendly HTTP greeting.""" - return "Hello, World!!!" diff --git a/examples/docker-compose/requirements.txt b/examples/docker-compose/requirements.txt deleted file mode 100644 index 3601409..0000000 --- a/examples/docker-compose/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -# Add any Python requirements here diff --git a/examples/openfunction_dapr_trigger/README.md b/examples/openfunction_dapr_trigger/README.md new file mode 100644 index 0000000..ad78edd --- /dev/null +++ b/examples/openfunction_dapr_trigger/README.md @@ -0,0 +1,84 @@ +Follow [this guide](https://github.com/OpenFunction/samples/blob/main/Prerequisites.md#kafka) to install a Kafka server named `kafka-server` and a Topic named `sample-topic`. + +Follow [this guide](https://docs.dapr.io/getting-started/) to install DAPR. + +Use kubectl to apply the [manifests](./manifests.yaml), then connect the container via [nocalhost](https://nocalhost.dev/). + +Create a DAPR Component: + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: kafka-binding +spec: + type: bindings.kafka + version: v1 + metadata: + - name: topics + value: "sample-topic" + - name: brokers + value: "kafka-server:9092" + - name: consumerGroup + value: "group1" + - name: publishTopic + value: "sample-topic" + - name: authRequired + value: "false" +``` + +FUNC_CONTEXT example: + + +```json +{ + "name": "ff-python", + "version": "v1", + "triggers": { + "dapr": [ + { + "name": "kafka-binding", + "type": "bindings.kafka" + } + ] + }, + "port": 50055 +} +``` + +After logging into the container terminal, export `FUNC_CONTEXT`: + +```shell +export FUNC_CONTEXT='{"name":"ff-python","version":"v1","triggers":{"dapr":[{"name":"kafka-binding","type":"bindings.kafka"}]},"port":50055}' +``` + +Run function: + +```shell +ff --source examples/openfunction_dapr_trigger/user_function.py --target user_function +``` + +Use the DAPR client to call function or you can apply the [caller](caller) + +```python +import json +import time + +from dapr.clients import DaprClient + +with DaprClient() as d: + n = 0 + while True: + n += 1 + req_data = { + 'id': n, + 'message': 'hello world' + } + + print(f'Sending message id: {req_data["id"]}, message "{req_data["message"]}"', flush=True) + + # Create a typed message with content type and body + _ = d.invoke_binding('kafka-binding', 'create', json.dumps(req_data)) + + time.sleep(2) +``` diff --git a/examples/openfunction_dapr_trigger/caller/Dockerfile b/examples/openfunction_dapr_trigger/caller/Dockerfile new file mode 100644 index 0000000..cce656b --- /dev/null +++ b/examples/openfunction_dapr_trigger/caller/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.7-slim + +WORKDIR /app + +ADD requirements.txt . +RUN pip install -r requirements.txt + +COPY *.py /app/ + +CMD [ "python", "main.py" ] \ No newline at end of file diff --git a/examples/openfunction_dapr_trigger/caller/__init__.py b/examples/openfunction_dapr_trigger/caller/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/openfunction_dapr_trigger/caller/main.py b/examples/openfunction_dapr_trigger/caller/main.py new file mode 100644 index 0000000..518bac8 --- /dev/null +++ b/examples/openfunction_dapr_trigger/caller/main.py @@ -0,0 +1,20 @@ +import json +import time + +from dapr.clients import DaprClient + +with DaprClient() as d: + n = 0 + while True: + n += 1 + req_data = { + 'id': n, + 'message': 'hello world' + } + + print(f'Sending message id: {req_data["id"]}, message "{req_data["message"]}"', flush=True) + + # Create a typed message with content type and body + _ = d.invoke_binding('kafka-binding', 'create', json.dumps(req_data)) + + time.sleep(2) diff --git a/examples/openfunction_dapr_trigger/caller/requirements.txt b/examples/openfunction_dapr_trigger/caller/requirements.txt new file mode 100644 index 0000000..f352a68 --- /dev/null +++ b/examples/openfunction_dapr_trigger/caller/requirements.txt @@ -0,0 +1,2 @@ +dapr-ext-grpc == 1.10.0 +dapr == 1.10.0 diff --git a/examples/openfunction_dapr_trigger/manifests.yaml b/examples/openfunction_dapr_trigger/manifests.yaml new file mode 100644 index 0000000..25370fc --- /dev/null +++ b/examples/openfunction_dapr_trigger/manifests.yaml @@ -0,0 +1,25 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ff-python +spec: + replicas: 1 + selector: + matchLabels: + app: ff-python + template: + metadata: + labels: + app: ff-python + annotations: + dapr.io/enabled: "true" + dapr.io/app-id: "ff-python" + dapr.io/app-port: "50055" + dapr.io/app-protocol: "grpc" + spec: + containers: + - name: ff-python + image: ff-python:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 50055 diff --git a/examples/openfunction_dapr_trigger/user_function.py b/examples/openfunction_dapr_trigger/user_function.py new file mode 100644 index 0000000..4817b28 --- /dev/null +++ b/examples/openfunction_dapr_trigger/user_function.py @@ -0,0 +1,20 @@ +from datetime import datetime + +from functions_framework.context.user_context import UserContext + + +def user_function(context: UserContext): + print(context.get_binding_request().metadata, flush=True) + print(context.get_binding_request().text(), flush=True) + + now = datetime.now() + + current_time = now.strftime("%H:%M:%S") + print("Current Time =", current_time) + + # 调用 FunctionContext 实例的方法 + # context.dapr_client.invoke_method(...) + + # 用户函数处理逻辑 + output_data = "hahahahaha" + return output_data diff --git a/examples/openfunction_http_trigger/README.md b/examples/openfunction_http_trigger/README.md new file mode 100644 index 0000000..308dd99 --- /dev/null +++ b/examples/openfunction_http_trigger/README.md @@ -0,0 +1,37 @@ +Use kubectl to apply the [manifests](./manifests.yaml), then connect the container via [nocalhost](https://nocalhost.dev/). + +FUNC_CONTEXT example: + + +```json +{ + "name": "ff-python", + "version": "v1", + "triggers": { + "http": { + "port": 8080 + } + } +} +``` + +After logging into the container terminal, export `FUNC_CONTEXT`: + +```shell +export FUNC_CONTEXT='{"name":"ff-python","version":"v1","triggers":{"http":{"port":8080}}}' +``` + +Run function: + +```shell +ff --source examples/openfunction_http_trigger/user_function.py --target user_function +``` + +Start a curl pod to access the function: + +```shell +~ $ curl ff-python-http-service + +hello world +``` + diff --git a/examples/openfunction_http_trigger/function.log b/examples/openfunction_http_trigger/function.log new file mode 100644 index 0000000..e69de29 diff --git a/examples/openfunction_http_trigger/manifests.yaml b/examples/openfunction_http_trigger/manifests.yaml new file mode 100644 index 0000000..2a07742 --- /dev/null +++ b/examples/openfunction_http_trigger/manifests.yaml @@ -0,0 +1,33 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ff-python-http +spec: + replicas: 1 + selector: + matchLabels: + app: ff-python-http + template: + metadata: + labels: + app: ff-python-http + spec: + containers: + - name: ff-python-http + image: ff-python-http:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: ff-python-http-service +spec: + selector: + app: ff-python-http + ports: + - protocol: TCP + port: 80 + targetPort: 8080 + type: ClusterIP diff --git a/examples/openfunction_http_trigger/user_function.py b/examples/openfunction_http_trigger/user_function.py new file mode 100644 index 0000000..b2c4f1e --- /dev/null +++ b/examples/openfunction_http_trigger/user_function.py @@ -0,0 +1,15 @@ +from datetime import datetime + +from functions_framework.context.user_context import UserContext + + +def user_function(context: UserContext): + # context.get_http_request() is a flask request object + print(context.get_http_request()) + + now = datetime.now() + + current_time = now.strftime("%H:%M:%S") + print("Current Time =", current_time) + output_data = "hello world" + return output_data diff --git a/examples/skaffold/README.md b/examples/skaffold/README.md deleted file mode 100644 index d115e30..0000000 --- a/examples/skaffold/README.md +++ /dev/null @@ -1,139 +0,0 @@ -# Developing multiple functions on the same host using Minikube and Skaffold - -## Introduction - -This example shows you how to develop multiple Cloud Functions to a single host -using Minikube and Skaffold. - -The example will focus on: -* taking two separate Cloud Functions (defined in the same file) -* building them each individually with Cloud Buildpacks and the Functions Framework -* deploying them to a local Kubernetes cluster with `minikube` and `skaffold` -* including live reloading! - -## Install `minikube` -*Note: If on Cloud Shell, `minikube` is pre-installed.* - -Install `minikube` via the instructions for your platform at - -Confirm that `minikube` is installed: - -```bash -minikube version -``` - -You should see output similar to: - -```terminal -minikube version: v1.15.1 -commit: 23f40a012abb52eff365ff99a709501a61ac5876 -``` - -## Start `minikube` - -This starts `minikube` using the default profile: - -```bash -minikube start -``` - -This may take a few minutes. - -*Note: If on Cloud Shell, you may be asked to enable Cloud Shell to make API calls* - -You should see output similar to: - -```terminal -😄 minikube v1.15.1 on Debian 10.6 - ▪ MINIKUBE_FORCE_SYSTEMD=true - ▪ MINIKUBE_HOME=/google/minikube - ▪ MINIKUBE_WANTUPDATENOTIFICATION=false -✨ Automatically selected the docker driver -👍 Starting control plane node minikube in cluster minikube -🚜 Pulling base image ... -💾 Downloading Kubernetes v1.19.4 preload ... -🔥 Creating docker container (CPUs=2, Memory=4000MB) ... -🐳 Preparing Kubernetes v1.19.4 on Docker 19.03.13 ... -🔎 Verifying Kubernetes components... -🌟 Enabled addons: storage-provisioner, default-storageclass -🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default -``` - -## Install the `ingress` addon for `minikube` - -This allows `minikube` to handle external traffic: - -```bash -minikube addons enable ingress -``` - -You should see output similar to: - -```terminal -🔎 Verifying ingress addon... -🌟 The 'ingress' addon is enabled -``` - -## Install `skaffold` -*Note: If on Cloud Shell, `skaffold` is pre-installed.* - -Install `skaffold` via the instructions for your platform at - -Confirm that `skaffold` is installed: - -```bash -skaffold version -``` - -You should see output similar to: - -```terminal -v1.16.0 -``` - -## Start `skaffold` - -Start `skaffold` with: - -```bash -skaffold dev -``` - -You should see output similar to: - -```terminal -Starting deploy... -Waiting for deployments to stabilize... - - deployment/hello is ready. [1/2 deployment(s) still pending] - - deployment/goodbye is ready. -Deployments stabilized in 1.154162006s -Watching for changes... -``` - -This command will continue running indefinitely, watching for changes and redeploying as necessary. - -## Call your Cloud Functions - -Leaving the previous command running, in a **new terminal**, call your functions. To call the `hello` function: - -```bash -curl `minikube ip`/hello -``` - -You should see output similar to: - -```terminal -Hello, World! -``` - -To call the `goodbye` function: - -```bash -curl `minikube ip`/goodbye -``` - -You should see output similar to: - -```terminal -Goodbye, World! -``` diff --git a/examples/skaffold/k8s/goodbye.yaml b/examples/skaffold/k8s/goodbye.yaml deleted file mode 100644 index 6a89123..0000000 --- a/examples/skaffold/k8s/goodbye.yaml +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright 2020 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. - -apiVersion: v1 -kind: Service -metadata: - name: goodbye -spec: - ports: - - port: 8080 - name: http - type: LoadBalancer - selector: - app: goodbye ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: goodbye -spec: - selector: - matchLabels: - app: goodbye - template: - metadata: - labels: - app: goodbye - spec: - containers: - - name: goodbye - image: example-goodbye-image - env: - - name: PORT - value: "8080" - ports: - - containerPort: 8080 diff --git a/examples/skaffold/k8s/hello.yaml b/examples/skaffold/k8s/hello.yaml deleted file mode 100644 index 68570ff..0000000 --- a/examples/skaffold/k8s/hello.yaml +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright 2020 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. - -apiVersion: v1 -kind: Service -metadata: - name: hello -spec: - ports: - - port: 8080 - name: http - type: LoadBalancer - selector: - app: hello ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: hello -spec: - selector: - matchLabels: - app: hello - template: - metadata: - labels: - app: hello - spec: - containers: - - name: hello - image: example-hello-image - env: - - name: PORT - value: "8080" - ports: - - containerPort: 8080 diff --git a/examples/skaffold/k8s/ingress.yaml b/examples/skaffold/k8s/ingress.yaml deleted file mode 100644 index 0abc2d6..0000000 --- a/examples/skaffold/k8s/ingress.yaml +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright 2020 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. - -apiVersion: extensions/v1beta1 -kind: Ingress -metadata: - name: scaffold-example-ingress -spec: - rules: - - http: - paths: - - path: /hello - backend: - serviceName: hello - servicePort: 8080 - - path: /goodbye - backend: - serviceName: goodbye - servicePort: 8080 diff --git a/examples/skaffold/main.py b/examples/skaffold/main.py deleted file mode 100644 index 298249b..0000000 --- a/examples/skaffold/main.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2020 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. - - -def hello(request): - """Return a friendly HTTP greeting.""" - return "Hello, World!" - - -def goodbye(request): - """Return a friendly HTTP goodbye.""" - return "Goodbye, World!" diff --git a/examples/skaffold/requirements.txt b/examples/skaffold/requirements.txt deleted file mode 100644 index 3601409..0000000 --- a/examples/skaffold/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -# Add any Python requirements here diff --git a/examples/skaffold/skaffold.yaml b/examples/skaffold/skaffold.yaml deleted file mode 100644 index ca66822..0000000 --- a/examples/skaffold/skaffold.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright 2020 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. - -apiVersion: skaffold/v2beta9 -kind: Config -build: - artifacts: - - image: example-hello-image - buildpacks: - builder: "gcr.io/buildpacks/builder:v1" - env: - - "GOOGLE_FUNCTION_TARGET=hello" - - image: example-goodbye-image - buildpacks: - builder: "gcr.io/buildpacks/builder:v1" - env: - - "GOOGLE_FUNCTION_TARGET=goodbye" diff --git a/setup.py b/setup.py index 566a242..8063172 100644 --- a/setup.py +++ b/setup.py @@ -24,8 +24,8 @@ long_description = f.read() setup( - name="functions-framework", - version="3.1.0", + name="ofn-functions-framework", + version="0.2.0", description="An open source FaaS (Function as a service) framework for writing portable Python functions.", long_description=long_description, long_description_content_type="text/markdown", diff --git a/src/functions_framework/triggers/http_trigger/http.py b/src/functions_framework/triggers/http_trigger/http.py index 4d35e67..cf92ecb 100644 --- a/src/functions_framework/triggers/http_trigger/http.py +++ b/src/functions_framework/triggers/http_trigger/http.py @@ -28,8 +28,6 @@ def __init__(self, port, trigger: HTTPRoute, source=None, target=None, user_func self.source = source self.target = target self.trigger = trigger - self.hostname = trigger.hostname - self.route_rules = trigger.rules self.user_function = user_function self.debug = debug if self.port == 0: @@ -41,6 +39,3 @@ def start(self, context: RuntimeContext, logger=None): app = create_app(context, self.target, self.source, logger) create_server(app, self.debug).run("0.0.0.0", self.port) - - -