{"id":15441305,"url":"https://github.com/adriangb/asapi","last_synced_at":"2025-04-19T18:25:42.111Z","repository":{"id":242055195,"uuid":"808555361","full_name":"adriangb/asapi","owner":"adriangb","description":"An opinionated set of utilities / patterns for FastAPI","archived":false,"fork":false,"pushed_at":"2025-02-05T16:38:08.000Z","size":138,"stargazers_count":25,"open_issues_count":3,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-16T15:13:50.174Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/adriangb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-05-31T10:06:08.000Z","updated_at":"2025-03-24T14:45:32.000Z","dependencies_parsed_at":"2024-11-08T16:02:13.038Z","dependency_job_id":null,"html_url":"https://github.com/adriangb/asapi","commit_stats":{"total_commits":23,"total_committers":1,"mean_commits":23.0,"dds":0.0,"last_synced_commit":"68e598e67e620fc56c0f735716140e27ce513fa2"},"previous_names":["adriangb/asapi"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adriangb%2Fasapi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adriangb%2Fasapi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adriangb%2Fasapi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adriangb%2Fasapi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/adriangb","download_url":"https://codeload.github.com/adriangb/asapi/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249762553,"owners_count":21321955,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-01T19:19:58.597Z","updated_at":"2025-04-19T18:25:42.078Z","avatar_url":"https://github.com/adriangb.png","language":"Python","readme":"# asapi\n\nA thin opinionated wrapper around FastAPI. Because it's wrapping FastAPI you can work it into your existing projects.\n\n## Explicit composition root\n\nFastAPI uses callbacks inside of `Depends` to do it's dependency injection.\nThis forces you to end up using multiple layers of `Depends` to compose your application.\nThe creation of these `Depends` resources often ends up distributed across modules so it's hard to know where something is initialized.\n\nFastAPI also has no application-level dependencies, so you end up having to use globals to share resources across requests.\n\n`asapi` solves this by having an explicit composition root where you can define all your dependencies in one place.\n\nEndpoints then use `Injected[DependencyType]` to get access to the dependencies they need.\n\n## Example\n\n```python\nfrom __future__ import annotations\n\nimport anyio\nfrom fastapi import FastAPI\nfrom psycopg_pool import AsyncConnectionPool\nfrom asapi import FromPath, Injected, serve, bind\n\n\napp = FastAPI()\n\n\n@app.get(\"/hello/{name}\")\nasync def hello(\n    name: FromPath[str],\n    pool: Injected[AsyncConnectionPool],\n) -\u003e str:\n    async with pool.connection() as conn:\n        async with conn.cursor() as cur:\n            await cur.execute(\"SELECT '¡Hola ' || %(name)s || '!'\", {\"name\": name})\n            res = await cur.fetchone()\n            assert res is not None\n            return res[0]\n```\n\nTODO: in the future I'd like to provide a wrapper around `APIRouter` and `FastAPI` that also forces you to mark every argument to an endpoint as `Injected`, `Query`, `Path`, `Body`, which makes it explicit where arguments are coming from with minimal boilerplate.\n\n## Run in your event loop\n\nFastAPI recommends using Uvicorn to run your application (note: if you're using Gunicorn you probably don't need to unless you're deploying on a a 'bare meta' server with multiple cores like a large EC2 instance).\n\nBut using `uvicorn app:app` from the command line has several issues:\n\n1. It takes control of the event loop and startup out of your hands. You have to rely on Uvicorn to configure the event loop, configure logging, etc.\n2. You'll have to use ASGI lifespans to initialize your resources, or the globals trick mentioned above.\n3. You can't run anything else in the event loop (e.g. a background worker).\n\n`asapi` solves this by providing a `serve` function that you can use to run your application in your own event loop.\n\n```python\nfrom __future__ import annotations\n\nimport anyio\nfrom asapi import serve\nfrom fastapi import FastAPI\n\napp = FastAPI()\n\n@app.get(\"/\")\nasync def root() -\u003e dict[str, str]:\n    return {\"message\": \"Hello World\"}\n\n\nasync def main():\n    await serve(app, 8000)\n\nif __name__ == \"__main__\":\n    anyio.run(main)\n```\n\nNow you have full control of the event loop and can make database connections, run background tasks, etc.\nCombined with the explicit composition root, you can initialize all your resources in one place, bind them to an application instance that is specific to this event loop and inject them into the endpoints that need them, all without global state or multiple layers of `Depends`.\n\n```python\nfrom __future__ import annotations\n\nimport logging\nfrom typing import Any\nimport anyio\nfrom fastapi import FastAPI, APIRouter\nfrom psycopg_pool import AsyncConnectionPool\nfrom asapi import FromPath, Injected, serve, bind\n\n\nrouter = APIRouter()\n\n\n@router.get(\"/hello/{name}\")\nasync def hello(name: FromPath[str], pool: Injected[AsyncConnectionPool]) -\u003e str:\n    async with pool.connection() as conn:\n        async with conn.cursor() as cur:\n            await cur.execute(\"SELECT '¡Hola ' || %(name)s || '!'\", {\"name\": name})\n            res = await cur.fetchone()\n            assert res is not None\n            return res[0]\n\n\ndef create_app(pool: AsyncConnectionPool[Any]) -\u003e FastAPI:\n    app = FastAPI()\n    bind(app, AsyncConnectionPool, pool)\n    app.include_router(router)\n    return app\n\n\nasync def main() -\u003e None:\n    logging.basicConfig(level=logging.INFO)\n\n    async with AsyncConnectionPool(\n        \"postgres://postgres:postgres@localhost:54320/postgres\"\n    ) as pool:\n        app = create_app(pool)\n        await serve(app, 9000)\n\n\nif __name__ == \"__main__\":\n    anyio.run(main)\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadriangb%2Fasapi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fadriangb%2Fasapi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadriangb%2Fasapi/lists"}