Skip to content

feat: add support for async functions #364

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 31 commits into from
Jun 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2a533c2
feat: Introduce functions_framework.aio submodule that support async …
taeold Apr 2, 2025
fe73e7d
Merge branch 'GoogleCloudPlatform:main' into dl-async
taeold Apr 7, 2025
cfa0691
Merge branch 'main' into dl-async
taeold May 8, 2025
a185820
Remove httpx.
taeold May 8, 2025
51098ac
Merge remote-tracking branch 'origin/main' into dl-async
taeold Jun 3, 2025
8dfe381
Merge branch 'dl-async' of https://github.com/taeold/functions-framew…
taeold Jun 3, 2025
15e4490
Update pyproject.toml to include extra async package.
taeold Jun 3, 2025
79f2b73
Update test deps.
taeold Jun 3, 2025
ddc55d6
Improve test coverage.
taeold Jun 4, 2025
5d67344
Make linter happy.
taeold Jun 4, 2025
8d5458b
Fix test harness to support py37.
taeold Jun 6, 2025
26d5828
Remove version filter in tox file.
taeold Jun 6, 2025
4d49695
Remove dependency-groups in pyproject.toml for now.
taeold Jun 6, 2025
e1fe361
Use py3.8 compatible types.
taeold Jun 6, 2025
c3f99bc
Fix more incompatibility with python38
taeold Jun 6, 2025
ca68963
Pin cloudevent sdk to python37 compatible version.
taeold Jun 7, 2025
c6628c1
Fix more py37 incompatibility.
taeold Jun 7, 2025
4a52a07
Merge remote-tracking branch 'origin/main' into dl-async
taeold Jun 10, 2025
7db0c79
fix: Prevent test_aio.py collection errors on Python 3.7
taeold Jun 10, 2025
8313ad7
style: Apply black formatting to conftest.py
taeold Jun 10, 2025
d6704f9
fix: Use modern pytest collection_path parameter and return None
taeold Jun 10, 2025
9c4cceb
fix: Skip tests parametrized with None on Python 3.7
taeold Jun 10, 2025
7009b19
fix: Replace asyncio.to_thread with Python 3.8 compatible code
taeold Jun 10, 2025
f3933ed
fix: Improve async test detection for Python 3.7
taeold Jun 10, 2025
297cb96
fix: Handle Flask vs Starlette redirect behavior differences
taeold Jun 10, 2025
41e7309
fix: Exclude aio module from coverage on Python 3.7
taeold Jun 10, 2025
e7e6683
fix: Simplify conftest.py.
taeold Jun 10, 2025
c92984b
fix: Use full environment names for py37 coverage exclusion
taeold Jun 10, 2025
1d92822
fix: Explicitly list each py37 environment for coverage exclusion
taeold Jun 10, 2025
dee798e
Merge branch 'main' into dl-async
taeold Jun 10, 2025
a387aa0
fix: Add Python 3.7 specific coverage configuration
taeold Jun 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .coveragerc-py37
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[run]
# Coverage configuration specifically for Python 3.7 environments
# Excludes the aio module which requires Python 3.8+ (Starlette dependency)
# This file is only used by py37-* tox environments
omit =
*/functions_framework/aio/*
*/.tox/*
*/tests/*
*/venv/*
*/.venv/*
50 changes: 50 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,53 @@ def isolate_logging():
sys.stderr = sys.__stderr__
logging.shutdown()
reload(logging)


# Safe to remove when we drop Python 3.7 support
def pytest_ignore_collect(collection_path, config):
"""Ignore async test files on Python 3.7 since Starlette requires Python 3.8+"""
if sys.version_info >= (3, 8):
return None

# Skip test_aio.py entirely on Python 3.7
if collection_path.name == "test_aio.py":
return True

return None


# Safe to remove when we drop Python 3.7 support
def pytest_collection_modifyitems(config, items):
"""Skip async-related tests on Python 3.7 since Starlette requires Python 3.8+"""
if sys.version_info >= (3, 8):
return

skip_async = pytest.mark.skip(
reason="Async features require Python 3.8+ (Starlette dependency)"
)

# Keywords that indicate async-related tests
async_keywords = ["async", "asgi", "aio", "starlette"]

for item in items:
skip_test = False

if hasattr(item, "callspec") and hasattr(item.callspec, "params"):
for param_name, param_value in item.callspec.params.items():
# Check if test has fixtures with async-related parameters
if isinstance(param_value, str) and any(
keyword in param_value.lower() for keyword in async_keywords
):
skip_test = True
break
# Skip tests parametrized with None (create_asgi_app on Python 3.7)
if param_value is None:
skip_test = True
break

# Skip tests that explicitly test async functionality
if any(keyword in item.name.lower() for keyword in async_keywords):
skip_test = True

if skip_test:
item.add_marker(skip_async)
6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "functions-framework"
version = "3.8.3"
description = "An open source FaaS (Function as a service) framework for writing portable Python functions -- brought to you by the Google Cloud Functions team."
readme = "README.md"
requires-python = ">=3.5, <4"
requires-python = ">=3.7, <4"
# Once we drop support for Python 3.7 and 3.8, this can become
# license = "Apache-2.0"
license = { text = "Apache-2.0" }
Expand All @@ -29,11 +29,15 @@ dependencies = [
"gunicorn>=22.0.0; platform_system!='Windows'",
"cloudevents>=1.2.0,<=1.11.0", # Must support python 3.7
"Werkzeug>=0.14,<4.0.0",
"httpx>=0.24.1",
]

[project.urls]
Homepage = "https://github.com/googlecloudplatform/functions-framework-python"

[project.optional-dependencies]
async = ["starlette>=0.37.0,<1.0.0; python_version>='3.8'"]

[project.scripts]
ff = "functions_framework._cli:_cli"
functions-framework = "functions_framework._cli:_cli"
Expand Down
72 changes: 72 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# 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 io import open
from os import path

from setuptools import find_packages, setup

here = path.abspath(path.dirname(__file__))

# Get the long description from the README file
with open(path.join(here, "README.md"), encoding="utf-8") as f:
long_description = f.read()

setup(
name="functions-framework",
version="3.8.2",
description="An open source FaaS (Function as a service) framework for writing portable Python functions -- brought to you by the Google Cloud Functions team.",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/googlecloudplatform/functions-framework-python",
author="Google LLC",
author_email="[email protected]",
classifiers=[
"Development Status :: 5 - Production/Stable ",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
],
keywords="functions-framework",
packages=find_packages(where="src"),
package_data={"functions_framework": ["py.typed"]},
namespace_packages=["google", "google.cloud"],
package_dir={"": "src"},
python_requires=">=3.5, <4",
install_requires=[
"flask>=1.0,<4.0",
"click>=7.0,<9.0",
"watchdog>=1.0.0",
"gunicorn>=22.0.0; platform_system!='Windows'",
"cloudevents>=1.2.0,<2.0.0",
"Werkzeug>=0.14,<4.0.0",
],
extras_require={
"async": ["starlette>=0.37.0,<1.0.0"],
},
entry_points={
"console_scripts": [
"ff=functions_framework._cli:_cli",
"functions-framework=functions_framework._cli:_cli",
"functions_framework=functions_framework._cli:_cli",
"functions-framework-python=functions_framework._cli:_cli",
"functions_framework_python=functions_framework._cli:_cli",
]
},
)
Loading
Loading