Skip to content

Global Variables

aims to make all objects globally accessible and make it easy for us to use them without passing function arguments. We provide global variables to access:

Note

All global objects are stored in fastack.globals.

Application

You can access the app object via the current_app variable. For example:

app/controllers/config.py
from fastack import Controller
from fastack.globals import current_app

class ConfigController(Controller):
    def get(self, name: str):
        return self.json("Config", {"setting": name, "value": current_app.get_setting(name)})

Add the above controller into the app and if you send

curl -X 'GET' \
   'http://127.0.0.1:7874/config?name=DEBUG' \
   -H 'accept: application/json'

you will get

{
  "detail": "Config",
  "data": {
    "setting": "DEBUG",
    "value": true
  }
}

Warning

Never add controller above, it's dangerous because it will know the settings used by your application!

Note

current_app can be used if:

  • Already running the app. Or...
  • Using enable_context() decorator.

State

fastack.globals.state here is a shortcut for current_app.state which is used to access the plugins in it. See here for more details.

Request

Now you no longer need to use the request object on a responder, because we have set it up globally and you can access it anywhere 🥳

For example:

app/controllers/helloworld/__init__.py
from app.plugins.globalvar import say_hello
from app.plugins.logger import log
from fastapi import Response
from pydantic import conint

from fastack.controller import ReadOnlyController
from fastack.decorators import route
from fastack.globals import request
from fastack.models import DetailModel, PaginatedModel

from .models import HelloWorldModel


class HelloWorldController(ReadOnlyController):
    name = "helloworld"
    url_prefix = "/world"

    def say_hello(self):
        if say_hello:
            log.debug("Hello there!")

    @route(response_model=PaginatedModel[HelloWorldModel])
    def list(self, page: conint(gt=0) = 1, page_size: conint(gt=0) = 10) -> Response:
        self.say_hello()
        data = [
            {"id": i, "title": request.url_for("helloworld:list")} for i in range(5)
        ]
        return self.get_paginated_response(data, page, page_size)

    @route(response_model=DetailModel[HelloWorldModel])
    def retrieve(self, id: int) -> Response:
        self.say_hello()
        return self.json(
            "Detail",
            {"id": id, "title": request.url_for("helloworld:single_update", id=id)},
        )

    @route(
        "/{id}/update",
        action=True,
        methods=["PUT"],
        response_model=DetailModel[HelloWorldModel],
    )
    def single_update(self, id: int) -> Response:
        self.say_hello()
        return self.json("Update", {"id": id, "title": "hello mars"})

Line:

  • 9 - We import the global request object
  • 27 and 36 - We use the url_for method in the request object to generate absolute url.

WebSocket

You can also access websocket connections globally via fastack.globals.websocket.

Technical details

Keep in mind, when you use websocket connection from fastack.globals.websocket you should not use websocket from typing hint in responder. This will cause the connection between the client and server to be out of sync.

For example:

from fastapi import WebSocket

@app.middleware("websocket")
async def on_websocket(ws: WebSocket):
  ws.accept()

@app.websocket("/ws")
async def hello_ws(websocket: WebSocket):
    await websocket.send_json({"hello": "world"})
    await websocket.close()

When you send data to client using websocket object from typing hint, it won't work because in websocket middleware we allow all incoming connections and use websocket object from global, while websocket from typing hint in responder connection status is still not connected (out of sync with global object)

The example below should work fine.

from fastapi import WebSocket
from fastack.globals import websocket

@app.middleware("websocket")
async def on_websocket(ws: WebSocket):
  ws.accept()

@app.websocket("/ws")
async def hello_ws(): # (1)
    await websocket.send_json({"hello": "world"})
    await websocket.close()
  1. We remove the websocket argument and use websocket from global

How it works?

First, thanks to werkzeug.local for providing an API for object proxying.

It's simpler than you think (maybe :D). Basically a web framework is an object that can be called and accept requests that are forwarded by the web server.

This is the main point, we save the app instance to local context using API from contextvars.ContextVar and also werkzeug.local.LocalProxy to access objects stored in local context.

For more details, please see the documentation directly:


Last update: January 17, 2022 13:26:42