https://github.com/adriangb/lazgi

Lazily initialized ASGI apps

https://github.com/adriangb/lazgi

Science Score: 26.0%

This score indicates how likely this project is to be science-related based on various indicators:

  • CITATION.cff file
  • codemeta.json file
    Found codemeta.json file
  • .zenodo.json file
    Found .zenodo.json file
  • DOI references
  • Academic publication links
  • Committers with academic emails
  • Institutional organization owner
  • JOSS paper metadata
  • Scientific vocabulary similarity
    Low similarity (10.4%) to scientific vocabulary
Last synced: 11 months ago · JSON representation

Repository

Lazily initialized ASGI apps

Basic Info
  • Host: GitHub
  • Owner: adriangb
  • License: mit
  • Language: Python
  • Default Branch: main
  • Size: 49.8 KB
Statistics
  • Stars: 11
  • Watchers: 2
  • Forks: 0
  • Open Issues: 0
  • Releases: 0
Created over 3 years ago · Last pushed over 1 year ago
Metadata Files
Readme License

README.md

lazgi

Lazily initialized ASGI apps.

Why?

Web servers like Gunicorn and Uvicorn prefer¹ to have applications as an attribute on a module, something like:

```python from fastapi import FastAPI

app = FastAPI() ```

  1. Uvicorn can be run programmatically as long as you are not running Gunicorn on top of it. To the best of my knowledge Gunicorn can only be run from the command line.

There are entire patterns built around this, like the @app.<method> pattern that FastAPI and Flask use.

The problem with this pattern, especially for ASGI apps, is that resources like database connections, http clients and even TaskGroups require an async context to be initialized.

The solution to this was ASGI lifespans, which are part of the ASGI spec. This solution works great for simple cases but doesn't completely solve the issue for larger applications, primarily because it doesn't provide a good place to store state. There's several workarounds frameworks use for this, including:

  • Storing data on the app instance. Starlette and Quart do this. This generally works of course, but is not without it's downsides.
  • Dependency injection. FastAPI and Xpresso (I am the author of the latter) propose using dependency injection. In the case of FastAPI you still need to store things on the app instance but at least you can move the type casting outside of your endpoint function. Xpresso provides storage for lifepsan-scoped dependencies, but it can be boilerplatey and error prone. There are also many valid objections to using a dependency injection container in and of itself.

The ideal solution to all of this would be if the servers supported app being a Callable[[], AsyncContextManager[ASGIApp]] but sadly no server supports this.

This repo tries to provide a solution to this problem that is the next best/closest thing: lazily initialized apps. LazyApp is simply a valid ASGI app that can live as a module global. When it's lifespan is triggered, it calls a user defined async context manager that initializes the ASGI app and any resources it depends on and also calls that ASGI app's lifespan.

Example (Starlette)

This example employs so called "pure" dependency injection to initialize a Starlette application with a database dependency.

```python from contextlib import asynccontextmanager from functools import partial from typing import AsyncIterator from lazgi import LazyApp from starlette.applications import Starlette from starlette.requests import Request from starlette.responses import Response from starlette.routing import Route

provided by the database driver

class DBConnection: async def execute(self, query: str) -> None: print(query)

@asynccontextmanager async def connect() -> AsyncIterator[DBConnection]: yield DBConnection()

user code, maybe in some endpoints.py file

async def endpoint(request: Request, db: DBConnection) -> Response: await db.execute("SELECT 1!") return Response()

user code, in main.py or app.py

note how createapp _explicitly lists all of the dependencies

with their appropriate types

def create_app(db: DBConnection) -> Starlette: return Starlette( routes=[Route("/", partial(endpoint, db=db))] )

user code, probably in main.py

the composition root (dependency injection term)

where we create all dependencies and "bind them"

(in this case that just means passing them into create_app)

@asynccontextmanager async def main() -> AsyncIterator[Starlette]: async with connect() as db: yield create_app(db)

a global object that can be accessed

by Guncicorn or Uvicorn as main:app

app = LazyApp(main)

an example test

you'd probably use a pytest fixture to create

and tear down the db and such

async def testapp() -> None: async with connect() as db: app = createapp(db) # run some tests # maybe using Starlette's TestClient # client = TestClient(app) ```

Owner

  • Name: Adrian Garcia Badaracco
  • Login: adriangb
  • Kind: user
  • Location: Chicago, IL

GitHub Events

Total
  • Watch event: 2
  • Push event: 3
Last Year
  • Watch event: 2
  • Push event: 3

Committers

Last synced: about 1 year ago

All Time
  • Total Commits: 8
  • Total Committers: 1
  • Avg Commits per committer: 8.0
  • Development Distribution Score (DDS): 0.0
Past Year
  • Commits: 3
  • Committers: 1
  • Avg Commits per committer: 3.0
  • Development Distribution Score (DDS): 0.0
Top Committers
Name Email Commits
Adrian Garcia Badaracco 1****b 8

Issues and Pull Requests

Last synced: 11 months ago

All Time
  • Total issues: 0
  • Total pull requests: 0
  • Average time to close issues: N/A
  • Average time to close pull requests: N/A
  • Total issue authors: 0
  • Total pull request authors: 0
  • Average comments per issue: 0
  • Average comments per pull request: 0
  • Merged pull requests: 0
  • Bot issues: 0
  • Bot pull requests: 0
Past Year
  • Issues: 0
  • Pull requests: 0
  • Average time to close issues: N/A
  • Average time to close pull requests: N/A
  • Issue authors: 0
  • Pull request authors: 0
  • Average comments per issue: 0
  • Average comments per pull request: 0
  • Merged pull requests: 0
  • Bot issues: 0
  • Bot pull requests: 0
Top Authors
Issue Authors
Pull Request Authors
Top Labels
Issue Labels
Pull Request Labels

Packages

  • Total packages: 1
  • Total downloads:
    • pypi 13 last-month
  • Total dependent packages: 0
  • Total dependent repositories: 0
  • Total versions: 1
  • Total maintainers: 1
pypi.org: lazgi

Lazily created ASGI apps

  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Downloads: 13 Last month
Rankings
Dependent packages count: 9.7%
Average: 32.3%
Dependent repos count: 54.8%
Maintainers (1)
Last synced: 11 months ago