Skip to content

Instantly share code, notes, and snippets.

@evert0n
Forked from glenfant/a_G_for_FastAPI.md
Created February 9, 2021 15:31
Show Gist options
  • Select an option

  • Save evert0n/11b37f74b86e45529214c45f30a2b379 to your computer and use it in GitHub Desktop.

Select an option

Save evert0n/11b37f74b86e45529214c45f30a2b379 to your computer and use it in GitHub Desktop.

Revisions

  1. @glenfant glenfant revised this gist Aug 2, 2020. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions requestvars.py
    Original file line number Diff line number Diff line change
    @@ -6,5 +6,6 @@
    default=types.SimpleNamespace())


    # This is the only public API
    def g():
    return request_global.get()
  2. @glenfant glenfant revised this gist Aug 2, 2020. 2 changed files with 4 additions and 2 deletions.
    4 changes: 3 additions & 1 deletion asgi.py
    Original file line number Diff line number Diff line change
    @@ -9,7 +9,9 @@ def create_app() -> fastapi.FastAPI:

    @app.middleware("http")
    async def init_requestvars(request: fastapi.Request, call_next):
    requestvars.request_global.set(types.SimpleNamespace())
    # Customize that SimpleNamespace with hatever you need
    initial_g = types.SimpleNamespace()
    requestvars.request_global.set(initial_g)
    response = await call_next(request)
    return response

    2 changes: 1 addition & 1 deletion requestvars.py
    Original file line number Diff line number Diff line change
    @@ -2,7 +2,7 @@
    import types
    import typing

    request_global = contextvars.ContextVar("requestvars",
    request_global = contextvars.ContextVar("request_global",
    default=types.SimpleNamespace())


  3. @glenfant glenfant renamed this gist Aug 1, 2020. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  4. @glenfant glenfant revised this gist Aug 1, 2020. No changes.
  5. @glenfant glenfant renamed this gist Aug 1, 2020. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  6. @glenfant glenfant renamed this gist Aug 1, 2020. 1 changed file with 4 additions and 0 deletions.
    4 changes: 4 additions & 0 deletions README.md → g_for_fastapi.md
    Original file line number Diff line number Diff line change
    @@ -7,6 +7,10 @@ here](https://flask.palletsprojects.com/en/1.1.x/appcontext/#storing-data).
    There is no OTB equivalent in FastAPI, but thanks to the new
    [contextvars](https://docs.python.org/3/library/contextvars.html) Python 3.7+ module, I made this simple demo.

    Any comment or help to improve yhis recipe is welcome.

    Hereafter follow the files descriptions and usage for the demo.

    ## requestvars.py

    The heart of the stuff. Simple ! Just have a look at the doc of the `contextvars` module to
  7. @glenfant glenfant revised this gist Aug 1, 2020. 5 changed files with 129 additions and 0 deletions.
    73 changes: 73 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -1,2 +1,75 @@
    # Like Flask "g". Good news you can do the same with FastAPI

    "g" is a threadlocal magic object that lets developer add / change / remove attributes during the
    request lifecycle. [Learn more about this "g"
    here](https://flask.palletsprojects.com/en/1.1.x/appcontext/#storing-data).

    There is no OTB equivalent in FastAPI, but thanks to the new
    [contextvars](https://docs.python.org/3/library/contextvars.html) Python 3.7+ module, I made this simple demo.

    ## requestvars.py

    The heart of the stuff. Simple ! Just have a look at the doc of the `contextvars` module to
    understand in seconds what it does.

    ## asgi.py

    Nothing special here, except the middleware function `init_requestvar` that stores an empty
    `type.SimpleNamespace` object in our context variable at the start of each request cycle.

    ## routes.py

    The demo of usage of our FastAPI "g" that is a function (sorry for this, but I dunno how to create a
    lazily evaluated variable).

    Note that no data is passed from the `foo_route` handler to the `double` async function.

    ## server.py

    Just a simple ordinary uvicorn server for the app that listens on port 8000/

    ## client.py

    A simple client that queries in a loop `http://localhost:8000?q=xyz` and prints the response in an infinite loop. `xyz` being the first argument of the shell command line that runs client.py.

    ## Run the demo

    If you want to run the demo, just create a Python 3.7+ virtual env and :

    ```
    pip install fastapi uvicorn requests
    ```

    Open a first terminal and run the server:

    ```
    python server.py
    ```

    Open a second terminal and run the client with an argument:

    ```
    python client.py arg1
    ```

    It show:

    ````
    {'result': 'arg1arg1'}
    ... (same each second)
    ````

    Open a third terminal and run the client with another argument:

    ```
    python client.py foo1
    ```

    It show:

    ````
    {'result': 'foo1foo1'}
    ... (same each second)
    ````

    Repeat the operation on any new terminal you want...
    17 changes: 17 additions & 0 deletions asgi.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,17 @@
    import requestvars
    import types
    import fastapi
    import routes


    def create_app() -> fastapi.FastAPI:
    app = fastapi.FastAPI()

    @app.middleware("http")
    async def init_requestvars(request: fastapi.Request, call_next):
    requestvars.request_global.set(types.SimpleNamespace())
    response = await call_next(request)
    return response

    app.include_router(routes.router)
    return app
    11 changes: 11 additions & 0 deletions client.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,11 @@
    import sys
    import requests

    endpoint = "http://localhost:8000/foo"

    query = sys.argv[1]

    while True:
    params = {"q": query}
    response = requests.get(endpoint, params=params)
    print(response.json())
    17 changes: 17 additions & 0 deletions routes.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,17 @@
    import asyncio
    import fastapi
    from requestvars import g

    router = fastapi.APIRouter()


    @router.get("/foo")
    async def foo_route(q: str = ""):
    g().blah = q
    result = await double()
    return {"result": result}


    async def double():
    await asyncio.sleep(1.0)
    return g().blah * 2
    11 changes: 11 additions & 0 deletions server.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,11 @@
    import uvicorn
    from asgi import create_app


    def main():
    app = create_app()
    uvicorn.run(app)


    if __name__ == "__main__":
    main()
  8. @glenfant glenfant created this gist Aug 1, 2020.
    2 changes: 2 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,2 @@
    # Like Flask "g". Good news you can do the same with FastAPI

    10 changes: 10 additions & 0 deletions requestvars.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,10 @@
    import contextvars
    import types
    import typing

    request_global = contextvars.ContextVar("requestvars",
    default=types.SimpleNamespace())


    def g():
    return request_global.get()