Skip to content
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

feat: Support strongly typed functions signature #208

Merged
merged 23 commits into from
Dec 12, 2022

Conversation

kappratiksha
Copy link
Contributor

@kappratiksha kappratiksha commented Nov 21, 2022

Add a new signature in functions framework that will support strongly typed objects. This signature will take a strong type T as an input and can return a strong type T, built in types supported by flask or None as an output.

Sample declarations:

@functions_framework.typed(T1)
def my_function(x:T1) -> T2:
@functions_framework.typed
def my_function(x:T1) -> T2:
@functions_framework.typed
def my_function(x:T1) -> None:
@functions_framework.typed
def my_function(x:T1) -> string:

The new signature will only support types abiding by the below contract to convert a python object to and from json:

  • Implement from_dict() method. This method converts a dict to the strongly typed python object.
  • Implement to_dict() method. This method converts the python object to a dict.

Here is sample python object:

class MyMessage:
    message: str
    retry_limit: int

    def __init__(self, message: str, retry_limit: int) -> None:
        self.message = message
        self.retry_limit = retry_limit

    @staticmethod
    def from_dict(obj: dict) -> 'MyMessage':
        assert isinstance(obj, dict)
        message = from_str(obj.get("message"))
        retry_limit = from_int(obj.get("retryLimit"))
        return MyMessage(message, retry_limit)

    def to_dict(self) -> dict:
        result: dict = {}
        result["message"] = from_str(self.message)
        result["retryLimit"] = from_int(self.retry_limit)
        return result

@kappratiksha kappratiksha marked this pull request as ready for review November 29, 2022 19:36
@anniefu
Copy link
Contributor

anniefu commented Nov 30, 2022

Could you add a description to the PR explaining the new feature and a code snippet sample of how to use it? I would define the typed class with the to_dict and from_dict functions in the code snippet to for illustrative purposes.

Copy link
Contributor

@anniefu anniefu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whew, pretty big one! Nice work getting this done!

I think I reviewed everything except tests. But gonna get these comments out for now.

src/functions_framework/typed_event.py Outdated Show resolved Hide resolved
tests/test_functions/typed_events/missing_from_dict.py Outdated Show resolved Hide resolved
src/functions_framework/event_conversion.py Outdated Show resolved Hide resolved
src/functions_framework/__init__.py Show resolved Hide resolved
src/functions_framework/__init__.py Outdated Show resolved Hide resolved
src/functions_framework/__init__.py Outdated Show resolved Hide resolved
src/functions_framework/_function_registry.py Outdated Show resolved Hide resolved
src/functions_framework/_function_registry.py Outdated Show resolved Hide resolved
src/functions_framework/__init__.py Outdated Show resolved Hide resolved
src/functions_framework/typed_event.py Outdated Show resolved Hide resolved
src/functions_framework/_typed_event.py Outdated Show resolved Hide resolved
src/functions_framework/_typed_event.py Outdated Show resolved Hide resolved
tests/test_functions/typed_events/typed_event.py Outdated Show resolved Hide resolved
tests/test_functions/typed_events/typed_event.py Outdated Show resolved Hide resolved
tests/test_typed_event_functions.py Outdated Show resolved Hide resolved
src/functions_framework/__init__.py Show resolved Hide resolved
tests/test_typed_event_functions.py Show resolved Hide resolved
src/functions_framework/__init__.py Outdated Show resolved Hide resolved
@anniefu
Copy link
Contributor

anniefu commented Dec 7, 2022

Oh btw, for the lint check, you can copy the commands executed here to reproduce locally: https://github.com/GoogleCloudPlatform/functions-framework-python/blob/master/.github/workflows/lint.yml

@kappratiksha kappratiksha changed the title Support strongly typed functions signature feat: Support strongly typed functions signature Dec 9, 2022
Copy link
Contributor

@anniefu anniefu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just nits and a existential-ish question

src/functions_framework/__init__.py Outdated Show resolved Hide resolved
src/functions_framework/__init__.py Show resolved Hide resolved
src/functions_framework/__init__.py Outdated Show resolved Hide resolved
@@ -67,6 +72,24 @@ def wrapper(*args, **kwargs):
return wrapper


def typed(*args):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I've been pondering for a while whether there's a way for us to provide typing here where like someone could hover over the definition of @typed and see that it's supposed to just take one, optional input type parameter instead of this kind of ambiguous *args situation.

I'm not sure it's possible though unless we enforce a named parameter like @typed(input_type=MyType)...

Can you think of anything?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that is the only way to get type hints for a function. Also, it's not very useful for generics also.

if I do something like this-def typed(input_type=T) , the type hints are input_type: Any
We can leave it as is right now, I added documentation for this decorator so it should be a little better.

@kappratiksha kappratiksha merged commit aa59a6b into GoogleCloudPlatform:master Dec 12, 2022
@haizaar
Copy link

haizaar commented Nov 29, 2023

Good day,
Is this functionality ready for use? (Couldn't find any docs on that)

Does it supersede functions_framework.http?
I.e. does

@functions_framework.typed
def get(m: MyModel):
  ...

essentially replace

@functions_framework.http
def get(request: flask.Request):
  m = MyModel.from_dict(request.get_json)
  ...

?

Will it work on Google Cloud Functions?

@haizaar
Copy link

haizaar commented Nov 30, 2023

Answering to myself:

  1. It work
  2. On Google Cloud Functions too
  3. Example:
from __future__ import annotations
from pydantic import BaseModel

class HelloRequest(BaseModel):
    name: str

    @classmethod
    def from_dict(cls, d: dict[str, Any]) -> HelloRequest:
        return cls(**d)

    def to_dict(self) -> dict[str, Any]:
        return self.model_dump()

@functions_framework.typed
def hello(hr: HelloRequest):
    return f"Hello, {hr.name}"

Well done guys!

@HKWinterhalter
Copy link
Contributor

@haizaar Thanks for trying it out and sharing an example! As you may have found, 'typed' is in additional feature and does not deprecate 'http'.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants