functions-framework 3.8.3__tar.gz → 3.9.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. {functions_framework-3.8.3 → functions_framework-3.9.1}/PKG-INFO +11 -3
  2. {functions_framework-3.8.3 → functions_framework-3.9.1}/pyproject.toml +19 -8
  3. functions_framework-3.9.1/setup.py +72 -0
  4. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/functions_framework/__init__.py +28 -0
  5. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/functions_framework/_cli.py +14 -3
  6. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/functions_framework/_function_registry.py +4 -0
  7. functions_framework-3.9.1/src/functions_framework/_http/__init__.py +59 -0
  8. functions_framework-3.9.1/src/functions_framework/_http/asgi.py +43 -0
  9. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/functions_framework/_http/gunicorn.py +25 -0
  10. functions_framework-3.9.1/src/functions_framework/aio/__init__.py +352 -0
  11. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/functions_framework/execution_id.py +115 -10
  12. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/functions_framework.egg-info/PKG-INFO +11 -3
  13. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/functions_framework.egg-info/SOURCES.txt +7 -0
  14. functions_framework-3.9.1/src/functions_framework.egg-info/namespace_packages.txt +2 -0
  15. functions_framework-3.9.1/src/functions_framework.egg-info/requires.txt +13 -0
  16. functions_framework-3.9.1/tests/test_aio.py +171 -0
  17. functions_framework-3.9.1/tests/test_asgi.py +117 -0
  18. {functions_framework-3.8.3 → functions_framework-3.9.1}/tests/test_cli.py +60 -0
  19. {functions_framework-3.8.3 → functions_framework-3.9.1}/tests/test_cloud_event_functions.py +45 -26
  20. functions_framework-3.9.1/tests/test_decorator_functions.py +177 -0
  21. {functions_framework-3.8.3 → functions_framework-3.9.1}/tests/test_execution_id.py +1 -0
  22. functions_framework-3.9.1/tests/test_execution_id_async.py +365 -0
  23. {functions_framework-3.8.3 → functions_framework-3.9.1}/tests/test_function_registry.py +16 -0
  24. {functions_framework-3.8.3 → functions_framework-3.9.1}/tests/test_functions.py +190 -133
  25. {functions_framework-3.8.3 → functions_framework-3.9.1}/tests/test_http.py +118 -1
  26. {functions_framework-3.8.3 → functions_framework-3.9.1}/tests/test_typing.py +12 -0
  27. functions_framework-3.8.3/src/functions_framework/_http/__init__.py +0 -42
  28. functions_framework-3.8.3/src/functions_framework.egg-info/requires.txt +0 -8
  29. functions_framework-3.8.3/tests/test_decorator_functions.py +0 -69
  30. {functions_framework-3.8.3 → functions_framework-3.9.1}/LICENSE +0 -0
  31. {functions_framework-3.8.3 → functions_framework-3.9.1}/README.md +0 -0
  32. {functions_framework-3.8.3 → functions_framework-3.9.1}/setup.cfg +0 -0
  33. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/functions_framework/__main__.py +0 -0
  34. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/functions_framework/_http/flask.py +0 -0
  35. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/functions_framework/_typed_event.py +0 -0
  36. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/functions_framework/background_event.py +0 -0
  37. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/functions_framework/event_conversion.py +0 -0
  38. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/functions_framework/exceptions.py +0 -0
  39. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/functions_framework/py.typed +0 -0
  40. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/functions_framework/request_timeout.py +0 -0
  41. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/functions_framework.egg-info/dependency_links.txt +0 -0
  42. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/functions_framework.egg-info/entry_points.txt +0 -0
  43. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/functions_framework.egg-info/top_level.txt +0 -0
  44. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/google/__init__.py +0 -0
  45. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/google/cloud/__init__.py +0 -0
  46. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/google/cloud/functions/__init__.py +0 -0
  47. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/google/cloud/functions/context.py +0 -0
  48. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/google/cloud/functions_v1/__init__.py +0 -0
  49. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/google/cloud/functions_v1/context.py +0 -0
  50. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/google/cloud/functions_v1beta2/__init__.py +0 -0
  51. {functions_framework-3.8.3 → functions_framework-3.9.1}/src/google/cloud/functions_v1beta2/context.py +0 -0
  52. {functions_framework-3.8.3 → functions_framework-3.9.1}/tests/test_convert.py +0 -0
  53. {functions_framework-3.8.3 → functions_framework-3.9.1}/tests/test_main.py +0 -0
  54. {functions_framework-3.8.3 → functions_framework-3.9.1}/tests/test_samples.py +0 -0
  55. {functions_framework-3.8.3 → functions_framework-3.9.1}/tests/test_timeouts.py +0 -0
  56. {functions_framework-3.8.3 → functions_framework-3.9.1}/tests/test_typed_event_functions.py +0 -0
  57. {functions_framework-3.8.3 → functions_framework-3.9.1}/tests/test_view_functions.py +0 -0
@@ -1,7 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: functions-framework
3
- Version: 3.8.3
3
+ Version: 3.9.1
4
4
  Summary: An open source FaaS (Function as a service) framework for writing portable Python functions -- brought to you by the Google Cloud Functions team.
5
+ Home-page: https://github.com/googlecloudplatform/functions-framework-python
6
+ Author: Google LLC
5
7
  Author-email: Google LLC <googleapis-packages@google.com>
6
8
  Maintainer-email: Google LLC <googleapis-packages@google.com>
7
9
  License: Apache-2.0
@@ -15,16 +17,22 @@ Classifier: Programming Language :: Python :: 3.9
15
17
  Classifier: Programming Language :: Python :: 3.10
16
18
  Classifier: Programming Language :: Python :: 3.11
17
19
  Classifier: Programming Language :: Python :: 3.12
18
- Requires-Python: <4,>=3.5
20
+ Requires-Python: >=3.5, <4
19
21
  Description-Content-Type: text/markdown
20
22
  License-File: LICENSE
21
23
  Requires-Dist: flask<4.0,>=2.0
22
24
  Requires-Dist: click<9.0,>=7.0
23
25
  Requires-Dist: watchdog>=1.0.0
24
26
  Requires-Dist: gunicorn>=22.0.0; platform_system != "Windows"
25
- Requires-Dist: cloudevents<2.0.0,>=1.2.0
27
+ Requires-Dist: cloudevents<=1.11.0,>=1.2.0
26
28
  Requires-Dist: Werkzeug<4.0.0,>=0.14
29
+ Requires-Dist: starlette<1.0.0,>=0.37.0; python_version >= "3.8"
30
+ Requires-Dist: uvicorn<1.0.0,>=0.18.0; python_version >= "3.8"
31
+ Requires-Dist: uvicorn-worker<1.0.0,>=0.2.0; python_version >= "3.8"
32
+ Dynamic: author
33
+ Dynamic: home-page
27
34
  Dynamic: license-file
35
+ Dynamic: requires-python
28
36
 
29
37
  # Functions Framework for Python
30
38
 
@@ -1,17 +1,15 @@
1
1
  [project]
2
2
  name = "functions-framework"
3
- version = "3.8.3"
3
+ version = "3.9.1"
4
4
  description = "An open source FaaS (Function as a service) framework for writing portable Python functions -- brought to you by the Google Cloud Functions team."
5
5
  readme = "README.md"
6
- requires-python = ">=3.5, <4"
6
+ requires-python = ">=3.7, <4"
7
7
  # Once we drop support for Python 3.7 and 3.8, this can become
8
8
  # license = "Apache-2.0"
9
- license = {text = "Apache-2.0"}
10
- authors = [
11
- { name = "Google LLC", email = "googleapis-packages@google.com" }
12
- ]
9
+ license = { text = "Apache-2.0" }
10
+ authors = [{ name = "Google LLC", email = "googleapis-packages@google.com" }]
13
11
  maintainers = [
14
- { name = "Google LLC", email = "googleapis-packages@google.com" }
12
+ { name = "Google LLC", email = "googleapis-packages@google.com" },
15
13
  ]
16
14
  keywords = ["functions-framework"]
17
15
  classifiers = [
@@ -29,8 +27,11 @@ dependencies = [
29
27
  "click>=7.0,<9.0",
30
28
  "watchdog>=1.0.0",
31
29
  "gunicorn>=22.0.0; platform_system!='Windows'",
32
- "cloudevents>=1.2.0,<2.0.0",
30
+ "cloudevents>=1.2.0,<=1.11.0", # Must support python 3.7
33
31
  "Werkzeug>=0.14,<4.0.0",
32
+ "starlette>=0.37.0,<1.0.0; python_version>='3.8'",
33
+ "uvicorn>=0.18.0,<1.0.0; python_version>='3.8'",
34
+ "uvicorn-worker>=0.2.0,<1.0.0; python_version>='3.8'",
34
35
  ]
35
36
 
36
37
  [project.urls]
@@ -55,3 +56,13 @@ functions_framework = ["py.typed"]
55
56
 
56
57
  [tool.setuptools.package-dir]
57
58
  "" = "src"
59
+
60
+ [dependency-groups]
61
+ dev = [
62
+ "black>=23.3.0",
63
+ "build>=1.1.1",
64
+ "isort>=5.11.5",
65
+ "pretend>=1.0.9",
66
+ "pytest>=7.4.4",
67
+ "pytest-asyncio>=0.21.2",
68
+ ]
@@ -0,0 +1,72 @@
1
+ # Copyright 2020 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from io import open
16
+ from os import path
17
+
18
+ from setuptools import find_packages, setup
19
+
20
+ here = path.abspath(path.dirname(__file__))
21
+
22
+ # Get the long description from the README file
23
+ with open(path.join(here, "README.md"), encoding="utf-8") as f:
24
+ long_description = f.read()
25
+
26
+ setup(
27
+ name="functions-framework",
28
+ version="3.9.1",
29
+ description="An open source FaaS (Function as a service) framework for writing portable Python functions -- brought to you by the Google Cloud Functions team.",
30
+ long_description=long_description,
31
+ long_description_content_type="text/markdown",
32
+ url="https://github.com/googlecloudplatform/functions-framework-python",
33
+ author="Google LLC",
34
+ author_email="googleapis-packages@google.com",
35
+ classifiers=[
36
+ "Development Status :: 5 - Production/Stable ",
37
+ "Intended Audience :: Developers",
38
+ "License :: OSI Approved :: Apache Software License",
39
+ "Programming Language :: Python :: 3.7",
40
+ "Programming Language :: Python :: 3.8",
41
+ "Programming Language :: Python :: 3.9",
42
+ "Programming Language :: Python :: 3.10",
43
+ "Programming Language :: Python :: 3.11",
44
+ "Programming Language :: Python :: 3.12",
45
+ ],
46
+ keywords="functions-framework",
47
+ packages=find_packages(where="src"),
48
+ package_data={"functions_framework": ["py.typed"]},
49
+ namespace_packages=["google", "google.cloud"],
50
+ package_dir={"": "src"},
51
+ python_requires=">=3.5, <4",
52
+ install_requires=[
53
+ "flask>=1.0,<4.0",
54
+ "click>=7.0,<9.0",
55
+ "watchdog>=1.0.0",
56
+ "gunicorn>=22.0.0; platform_system!='Windows'",
57
+ "cloudevents>=1.2.0,<2.0.0",
58
+ "Werkzeug>=0.14,<4.0.0",
59
+ ],
60
+ extras_require={
61
+ "async": ["starlette>=0.37.0,<1.0.0"],
62
+ },
63
+ entry_points={
64
+ "console_scripts": [
65
+ "ff=functions_framework._cli:_cli",
66
+ "functions-framework=functions_framework._cli:_cli",
67
+ "functions_framework=functions_framework._cli:_cli",
68
+ "functions-framework-python=functions_framework._cli:_cli",
69
+ "functions_framework_python=functions_framework._cli:_cli",
70
+ ]
71
+ },
72
+ )
@@ -327,6 +327,16 @@ def crash_handler(e):
327
327
 
328
328
 
329
329
  def create_app(target=None, source=None, signature_type=None):
330
+ """Create an app for the function.
331
+
332
+ Args:
333
+ target: The name of the target function to invoke
334
+ source: The source file containing the function
335
+ signature_type: The signature type of the function
336
+
337
+ Returns:
338
+ A Flask WSGI app or Starlette ASGI app depending on function decorators
339
+ """
330
340
  target = _function_registry.get_function_target(target)
331
341
  source = _function_registry.get_function_source(source)
332
342
 
@@ -370,6 +380,7 @@ def create_app(target=None, source=None, signature_type=None):
370
380
  setup_logging()
371
381
 
372
382
  _app.wsgi_app = execution_id.WsgiMiddleware(_app.wsgi_app)
383
+
373
384
  # Execute the module, within the application context
374
385
  with _app.app_context():
375
386
  try:
@@ -394,6 +405,23 @@ def create_app(target=None, source=None, signature_type=None):
394
405
  # command fails.
395
406
  raise e from None
396
407
 
408
+ use_asgi = target in _function_registry.ASGI_FUNCTIONS
409
+ if use_asgi:
410
+ # This function needs ASGI, delegate to create_asgi_app
411
+ # Note: @aio decorators only register functions in ASGI_FUNCTIONS when the
412
+ # module is imported. We can't know if a function uses @aio until after
413
+ # we load the module.
414
+ #
415
+ # To avoid loading modules twice, we always create a Flask app first, load the
416
+ # module within its context, then check if ASGI is needed. This results in an
417
+ # unused Flask app for ASGI functions, but we accept this memory overhead as a
418
+ # trade-off.
419
+ from functions_framework.aio import create_asgi_app_from_module
420
+
421
+ return create_asgi_app_from_module(
422
+ target, source, signature_type, source_module, spec
423
+ )
424
+
397
425
  # Get the configured function signature type
398
426
  signature_type = _function_registry.get_func_signature_type(target, signature_type)
399
427
 
@@ -16,7 +16,7 @@ import os
16
16
 
17
17
  import click
18
18
 
19
- from functions_framework import create_app
19
+ from functions_framework import _function_registry, create_app
20
20
  from functions_framework._http import create_server
21
21
 
22
22
 
@@ -32,6 +32,17 @@ from functions_framework._http import create_server
32
32
  @click.option("--host", envvar="HOST", type=click.STRING, default="0.0.0.0")
33
33
  @click.option("--port", envvar="PORT", type=click.INT, default=8080)
34
34
  @click.option("--debug", envvar="DEBUG", is_flag=True)
35
- def _cli(target, source, signature_type, host, port, debug):
36
- app = create_app(target, source, signature_type)
35
+ @click.option(
36
+ "--asgi",
37
+ envvar="FUNCTION_USE_ASGI",
38
+ is_flag=True,
39
+ help="Use ASGI server for function execution",
40
+ )
41
+ def _cli(target, source, signature_type, host, port, debug, asgi):
42
+ if asgi:
43
+ from functions_framework.aio import create_asgi_app
44
+
45
+ app = create_asgi_app(target, source, signature_type)
46
+ else:
47
+ app = create_app(target, source, signature_type)
37
48
  create_server(app, debug).run(host, port)
@@ -40,6 +40,10 @@ REGISTRY_MAP = {}
40
40
  # Keys are the user function name, values are the type of the function input
41
41
  INPUT_TYPE_MAP = {}
42
42
 
43
+ # ASGI_FUNCTIONS stores function names that require ASGI mode.
44
+ # Functions decorated with @aio.http or @aio.cloud_event are added here.
45
+ ASGI_FUNCTIONS = set()
46
+
43
47
 
44
48
  def get_user_function(source, source_module, target):
45
49
  """Returns user function, raises exception for invalid function."""
@@ -0,0 +1,59 @@
1
+ # Copyright 2020 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from flask import Flask
16
+
17
+ from functions_framework._http.flask import FlaskApplication
18
+
19
+
20
+ class HTTPServer:
21
+ def __init__(self, app, debug, **options):
22
+ self.app = app
23
+ self.debug = debug
24
+ self.options = options
25
+
26
+ if isinstance(app, Flask):
27
+ if self.debug:
28
+ self.server_class = FlaskApplication
29
+ else:
30
+ try:
31
+ from functions_framework._http.gunicorn import GunicornApplication
32
+
33
+ self.server_class = GunicornApplication
34
+ except ImportError as e:
35
+ self.server_class = FlaskApplication
36
+ else: # pragma: no cover
37
+ if self.debug:
38
+ from functions_framework._http.asgi import StarletteApplication
39
+
40
+ self.server_class = StarletteApplication
41
+ else:
42
+ try:
43
+ from functions_framework._http.gunicorn import UvicornApplication
44
+
45
+ self.server_class = UvicornApplication
46
+ except ImportError as e:
47
+ from functions_framework._http.asgi import StarletteApplication
48
+
49
+ self.server_class = StarletteApplication
50
+
51
+ def run(self, host, port):
52
+ http_server = self.server_class(
53
+ self.app, host, port, self.debug, **self.options
54
+ )
55
+ http_server.run()
56
+
57
+
58
+ def create_server(app, debug, **options):
59
+ return HTTPServer(app, debug, **options)
@@ -0,0 +1,43 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import uvicorn
16
+
17
+
18
+ class StarletteApplication:
19
+ """A Starlette application that uses Uvicorn for direct serving (development mode)."""
20
+
21
+ def __init__(self, app, host, port, debug, **options):
22
+ """Initialize the Starlette application.
23
+
24
+ Args:
25
+ app: The ASGI application to serve
26
+ host: The host to bind to
27
+ port: The port to bind to
28
+ debug: Whether to run in debug mode
29
+ **options: Additional options to pass to Uvicorn
30
+ """
31
+ self.app = app
32
+ self.host = host
33
+ self.port = port
34
+ self.debug = debug
35
+
36
+ self.options = {
37
+ "log_level": "debug" if debug else "error",
38
+ }
39
+ self.options.update(options)
40
+
41
+ def run(self):
42
+ """Run the Uvicorn server directly."""
43
+ uvicorn.run(self.app, host=self.host, port=int(self.port), **self.options)
@@ -70,3 +70,28 @@ class GThreadWorkerWithTimeoutSupport(ThreadWorker): # pragma: no cover
70
70
  def handle_request(self, req, conn):
71
71
  with ThreadingTimeout(TIMEOUT_SECONDS):
72
72
  super(GThreadWorkerWithTimeoutSupport, self).handle_request(req, conn)
73
+
74
+
75
+ class UvicornApplication(gunicorn.app.base.BaseApplication):
76
+ """Gunicorn application for ASGI apps using Uvicorn workers."""
77
+
78
+ def __init__(self, app, host, port, debug, **options):
79
+ self.options = {
80
+ "bind": "%s:%s" % (host, port),
81
+ "workers": int(os.environ.get("WORKERS", 1)),
82
+ "worker_class": "uvicorn_worker.UvicornWorker",
83
+ "timeout": int(os.environ.get("CLOUD_RUN_TIMEOUT_SECONDS", 0)),
84
+ "loglevel": os.environ.get("GUNICORN_LOG_LEVEL", "error"),
85
+ "limit_request_line": 0,
86
+ }
87
+ self.options.update(options)
88
+ self.app = app
89
+
90
+ super().__init__()
91
+
92
+ def load_config(self):
93
+ for key, value in self.options.items():
94
+ self.cfg.set(key, value)
95
+
96
+ def load(self):
97
+ return self.app