How can I add python type annotations to the flask global context g?

前端 未结 3 1264
挽巷
挽巷 2021-01-05 06:07

I have a decorator which adds a user onto the flask global context g:

class User:
    def __init__(self, user_data) -&         


        
相关标签:
3条回答
  • 2021-01-05 06:35

    I had a similar issue described in Typechecking dynamically added attributes. One solution is to add the custom type hints using typing.TYPE_CHECKING:

    from typing import TYPE_CHECKING
    
    if TYPE_CHECKING:
        from flask.ctx import _AppCtxGlobals
    
        class MyGlobals(_AppCtxGlobals):
            user: 'User'
    
        g = MyGlobals()
    else:
        from flask import g
    

    Now e.g.

    reveal_type(g.user)
    

    will emit

    note: Revealed type is 'myapp.User'
    

    If the custom types should be reused in multiple modules, you can introduce a partial stub for flask. The location of the stubs is dependent on the type checker, e.g. mypy reads custom stubs from the MYPY_PATH environment variable, pyright looks for a typings directory in the project root dir etc. Example of a partial stub:

    # _typeshed/flask/__init__.pyi
    
    from typing import Any
    from flask.ctx import _AppCtxGlobals
    from models import User
    
    
    def __getattr__(name: str) -> Any: ...  # incomplete
    
    
    class MyGlobals(_AppCtxGlobals):
        user: User
        def __getattr__(self, name: str) -> Any: ...  # incomplete
    
    
    g: MyGlobals
    
    0 讨论(0)
  • 2021-01-05 06:41

    You could proxy the g object. Consider the following implementation:

    import flask
    
    
    class User:
        ...
    
    
    class _g:
    
        user: User
        # Add type hints for other attributes
        # ...
    
        def __getattr__(self, key):
            return getattr(flask.g, key)
    
    
    g = _g()
    
    
    0 讨论(0)
  • 2021-01-05 06:46

    You can annotate an attribute on a class, even if that class isn't yours, simply with a colon after it. For example:

    g.user: User
    

    That's it. Since it's presumably valid everywhere, I would put it at the top of your code:

    from functools import wraps
    
    from flask import Flask, g
    
    app = Flask(__name__)
    
    
    class User:
        def __init__(self, user_data) -> None:
            self.username: str = user_data["username"]
            self.email: str = user_data["email"]
    
    
    # Annotate the g.user attribute
    g.user: User
    
    
    def login_required(f):
        @wraps(f)
        def wrap(*args, **kwargs):
            g.user = User({'username': 'wile-e-coyote',
                           'email': 'coyote@localhost'})
    
            return f(*args, **kwargs)
    
        return wrap
    
    
    @app.route('/')
    @login_required
    def hello_world():
        return f'Hello, {g.user.email}'
    
    
    if __name__ == '__main__':
        app.run()
    

    That's it.

    0 讨论(0)
提交回复
热议问题