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
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
Metadata Files
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() ```
- 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
appinstance. 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
appinstance 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
- Repositories: 172
- Profile: https://github.com/adriangb
GitHub Events
Total
- Watch event: 2
- Push event: 3
Last Year
- Watch event: 2
- Push event: 3
Committers
Last synced: about 1 year ago
Top Committers
| Name | 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
- Documentation: https://github.com/adriangb/lazgi
- License: MIT License
-
Latest release: 0.1.0
published over 1 year ago