{"id":18404402,"url":"https://github.com/jordic/fastapi_asyncpg","last_synced_at":"2025-04-07T07:32:56.207Z","repository":{"id":55537360,"uuid":"322918974","full_name":"jordic/fastapi_asyncpg","owner":"jordic","description":"asyncpg integration for fastapi","archived":false,"fork":false,"pushed_at":"2023-01-22T21:40:25.000Z","size":21,"stargazers_count":50,"open_issues_count":5,"forks_count":10,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-22T15:02:05.542Z","etag":null,"topics":["asyncio","asyncpg","fastapi","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jordic.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.rst","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-12-19T19:05:40.000Z","updated_at":"2024-11-19T05:04:56.000Z","dependencies_parsed_at":"2023-02-12T18:20:19.654Z","dependency_job_id":null,"html_url":"https://github.com/jordic/fastapi_asyncpg","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jordic%2Ffastapi_asyncpg","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jordic%2Ffastapi_asyncpg/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jordic%2Ffastapi_asyncpg/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jordic%2Ffastapi_asyncpg/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jordic","download_url":"https://codeload.github.com/jordic/fastapi_asyncpg/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247612569,"owners_count":20966776,"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":["asyncio","asyncpg","fastapi","python"],"created_at":"2024-11-06T02:51:41.454Z","updated_at":"2025-04-07T07:32:53.185Z","avatar_url":"https://github.com/jordic.png","language":"Python","readme":"# FastAPI AsyncPG\n\nFastAPI integration for AsyncPG\n\n## Narrative\n\nFirst of all, so sorry for my poor english. I will be so happy,\nif someone pushes a PR correcting all my english mistakes. Anyway\nI will try to do my best.\n\nLooking at fastapi ecosystem seems like everyone is trying to integrate\nfastapi with orms, but from my experience working with raw\nsql I'm so productive.\n\nIf you think a bit around, your real model layer, is the schema on your\ndb (you can add abastractions on top of it), but what ends\nis your data, and these are tables, columns and rows.\n\nAlso, sql, it's one of the best things I learned\nbecause it's something that always is there.\n\nOn another side, postgresql it's robust and rock solid,\nthousands of projects depend on it, and use it as their storage layer.\nAsyncPG it's a crazy fast postgresql driver\nwritten from scratch.\n\nFastAPI seems like a clean, and developer productive approach to web\nframeworks. It's crazy how well it integrates with OpenAPI,\nand how easy makes things to a developer to move on.\n\n## Integration\n\nfastapi_asyncpg trys to integrate fastapi and asyncpg in an idiomatic way.\nfastapi_asyncpg when configured exposes two injectable providers to\nfastapi path functions, can use:\n\n- `db.connection` : it's just a raw connection picked from the pool,\n  that it's auto released when pathfunction ends, this is mostly\n  merit of the DI system around fastapi.\n\n- `db.transaction`: the same, but wraps the pathfuncion on a transaction\n  this is more or less the same than the `atomic` decorator from Django.\n  also `db.atomic` it's aliased\n\n```python\nfrom fastapi import FastAPI\nfrom fastapi import Depends\nfrom fastapi_asyncpg import configure_asyncpg\n\napp = FastAPI()\n# we need to pass the fastapi app to make use of lifespan asgi events\ndb = configure_asyncpg(app, \"postgresql://postgres:postgres@localhost/db\")\n\n@db.on_init\nasync def initialization(conn):\n    # you can run your db initialization code here\n    await conn.execute(\"SELECT 1\")\n\n\n@app.get(\"/\")\nasync def get_content(db=Depends(db.connection)):\n    rows = await db.fetch(\"SELECT wathever FROM tablexxx\")\n    return [dict(r) for r in rows]\n\n@app.post(\"/\")\nasync def mutate_something_compled(db=Depends(db.atomic))\n    await db.execute()\n    await db.execute()\n    # if something fails, everyting is rolleback, you know all or nothing\n```\n\nAnd there's also an `initialization` callable on the main factory function.\nThat can be used like in flask to initialize whatever you need on the db.\nThe `initialization` is called right after asyncpg stablishes a connection,\nand before the app fully boots. (Some projects use this as a poor migration\nrunner, not the best practice if you are deploying multiple\ninstances of the app).\n\n## Testing\n\nFor testing we use [pytest-docker-fixtures](https://pypi.org/project/pytest-docker-fixtures/), it requires docker on the host machine or on whatever CI you use\n(seems like works as expected with github actions)\n\nIt works, creating a container for the session and exposing it as pytest fixture.\nIt's a good practice to run tests with a real database, and\npytest-docker-fixtures make it's so easy. As a bonus, all fixtures run on a CI.\nWe use Jenkins witht docker and docker, but also seems like travis and github actions\nalso work.\n\nThe fixture needs to be added to the pytest plugins `conftest.py` file.\n\non conftest.py\n\n```python\npytest_plugins = [\n    \"pytest_docker_fixtures\",\n]\n```\n\nWith this in place, we can just yield a pg fixture\n\n```python\nfrom pytest_docker_fixtures import images\n\n# image params can be configured from here\nimages.configure(\n    \"postgresql\", \"postgres\", \"11.1\", env={\"POSTGRES_DB\": \"test_db\"}\n)\n\n# and then on our test we have a pg container running\n# ready to recreate our db\nasync def test_pg(pg):\n    host, port = pg\n    dsn = f\"postgresql://postgres@{host}:{port}/test_db\"\n    await asyncpg.Connect(dsn=dsn)\n    # let's go\n\n```\n\nWith this in place, we can just create our own pytest.fixture that\n_patches_ the app dsn to make it work with our custom created\ncontainer.\n\n````python\n\nfrom .app import app, db\nfrom async_asgi_testclient import TestClient\n\nimport pytest\n\npytestmark = pytest.mark.asyncio\n\n@pytest.fixture\nasync def asgi_app(pg)\n    host, port = pg\n    dsn = f\"postgresql://postgres@{host}:{port}/test_db\"\n    # here we patch the dsn for the db\n    # con_opts: are also accessible\n    db.dsn = dsn\n    yield app, db\n\nasync def test_something(asgi_app):\n    app, db = asgi_app\n    async with db.pool.acquire() as db:\n        # setup your test state\n\n    # this context manager handlers lifespan events\n    async with TestClient(app) as client:\n        res = await client.request(\"/\")\n```\n\nAnyway if the application will grow, to multiples subpackages,\nand apps, we trend to build the main app as a factory, that\ncreates it, something like:\n\n```python\nfrom fastapi_asyncpg import configure_asyncpg\nfrom apppackage import settings\n\nimport venusian\n\ndef make_asgi_app(settings):\n    app = FastAPI()\n    db = configure_asyncpg(settings.DSN)\n\n    scanner = venusian.Scanner(app=app)\n    venusian.scan(theapp)\n    return app\n````\n\nThen on the fixture, we just need, to factorze and app from our function\n\n```python\n\nfrom .factory import make_asgi_app\nfrom async_asgi_testclient import TestClient\n\nimport pytest\n\npytestmark = pytest.mark.asyncio\n\n@pytest.fixture\nasync def asgi_app(pg)\n    host, port = pg\n    dsn = f\"postgresql://postgres@{host}:{port}/test_db\"\n    app = make_asgi_app({\"dsn\": dsn})\n    # ther's a pointer on the pool into app.state\n    yield app\n\nasync def test_something(asgi_app):\n    app = asgi_app\n    pool = app.state.pool\n    async with db.pool.acquire() as db:\n        # setup your test state\n\n    # this context manager handlers lifespan events\n    async with TestClient(app) as client:\n        res = await client.request(\"/\")\n\n```\n\nThere's also another approach exposed and used on [tests](tests/test_db.py),\nthat exposes a single connection to the test and rolls back changes on end.\nWe use this approach on a large project (500 tables per schema and\nmultiples schemas), and seems like it speeds up a bit test creation.\nThis approach is what [Databases](https://www.encode.io/databases/) it's using.\nFeel free to follow the tests to see if it feets better.\n\n## Extras\n\nThere are some utility functions I daily use with asyncpg that helps me\nspeed up some sql operations like, they are all on sql.py, and mostly are\nself documented. They are in use on tests.\n\n### Authors\n\n`fastapi_asyncpg` was written by `Jordi collell \u003cjordic@gmail.com\u003e`\\_.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjordic%2Ffastapi_asyncpg","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjordic%2Ffastapi_asyncpg","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjordic%2Ffastapi_asyncpg/lists"}