diff --git a/README.md b/README.md index 0433028d..b32b2e3e 100644 --- a/README.md +++ b/README.md @@ -59,10 +59,11 @@ functions-framework==3.* Create an `main.py` file with the following contents: ```python +import flask import functions_framework @functions_framework.http -def hello(request): +def hello(request: flask.Request) -> flask.typing.ResponseReturnValue: return "Hello world!" ``` @@ -98,9 +99,10 @@ Create an `main.py` file with the following contents: ```python import functions_framework +from cloudevents.http.event import CloudEvent @functions_framework.cloud_event -def hello_cloud_event(cloud_event): +def hello_cloud_event(cloud_event: CloudEvent) -> None: print(f"Received event with ID: {cloud_event['id']} and data {cloud_event.data}") ``` diff --git a/setup.py b/setup.py index 9e8aedab..0a98faef 100644 --- a/setup.py +++ b/setup.py @@ -46,6 +46,7 @@ ], 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", diff --git a/src/functions_framework/__init__.py b/src/functions_framework/__init__.py index d4575b57..8d47670c 100644 --- a/src/functions_framework/__init__.py +++ b/src/functions_framework/__init__.py @@ -23,13 +23,14 @@ import types from inspect import signature -from typing import Type +from typing import Callable, Type import cloudevents.exceptions as cloud_exceptions import flask import werkzeug from cloudevents.http import from_http, is_binary +from cloudevents.http.event import CloudEvent from functions_framework import _function_registry, _typed_event, event_conversion from functions_framework.background_event import BackgroundEvent @@ -45,6 +46,9 @@ _CLOUDEVENT_MIME_TYPE = "application/cloudevents+json" +CloudEventFunction = Callable[[CloudEvent], None] +HTTPFunction = Callable[[flask.Request], flask.typing.ResponseReturnValue] + class _LoggingHandler(io.TextIOWrapper): """Logging replacement for stdout and stderr in GCF Python 3.7.""" @@ -59,7 +63,7 @@ def write(self, out): return self.stderr.write(json.dumps(payload) + "\n") -def cloud_event(func): +def cloud_event(func: CloudEventFunction) -> CloudEventFunction: """Decorator that registers cloudevent as user function signature type.""" _function_registry.REGISTRY_MAP[ func.__name__ @@ -99,7 +103,7 @@ def wrapper(*args, **kwargs): return _typed -def http(func): +def http(func: HTTPFunction) -> HTTPFunction: """Decorator that registers http as user function signature type.""" _function_registry.REGISTRY_MAP[ func.__name__ diff --git a/src/functions_framework/py.typed b/src/functions_framework/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_typing.py b/tests/test_typing.py new file mode 100644 index 00000000..279cd636 --- /dev/null +++ b/tests/test_typing.py @@ -0,0 +1,16 @@ +import typing + +if typing.TYPE_CHECKING: # pragma: no cover + import flask + + from cloudevents.http.event import CloudEvent + + import functions_framework + + @functions_framework.http + def hello(request: flask.Request) -> flask.typing.ResponseReturnValue: + return "Hello world!" + + @functions_framework.cloud_event + def hello_cloud_event(cloud_event: CloudEvent) -> None: + print(f"Received event: id={cloud_event['id']} and data={cloud_event.data}") diff --git a/tox.ini b/tox.ini index 0fe3dba6..e8c555b5 100644 --- a/tox.ini +++ b/tox.ini @@ -19,8 +19,10 @@ deps = black twine isort + mypy commands = black --check src tests setup.py conftest.py --exclude tests/test_functions/background_load_error/main.py isort -c src tests setup.py conftest.py + mypy tests/test_typing.py python setup.py --quiet sdist bdist_wheel twine check dist/*