# asyncpg: a PostgreSQL client for asyncio that speaks the binary protocol directly

> asyncpg is a PostgreSQL driver written specifically for Python's asyncio, implementing the server binary protocol instead of sitting on a DB-API facade. It is fast and gives you prepared statements, cursors and type codecs, but it is PostgreSQL-only and its API is not the DB-API you may already know.

**MagicStack/asyncpg** — A fast PostgreSQL Database Client Library for Python/asyncio.

- Repository: https://github.com/MagicStack/asyncpg
- Stars: 8,096 · Forks: 474
- Language: Python
- License: Apache-2.0
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/magicstack-asyncpg

## What asyncpg is for, and who should reach for it

asyncpg is a database interface library built specifically for PostgreSQL and Python's asyncio framework. The README is explicit that it implements the PostgreSQL server binary protocol natively and exposes server features directly, "as opposed to hiding them behind a generic facade like DB-API." That sentence is the whole design thesis. If you have written Python against psycopg2, you know the DB-API shape: cursor objects, execute, fetchall, a connection that is not tied to any event loop. asyncpg throws that shape away in exchange for lower overhead and direct access to PostgreSQL semantics.

The audience is narrow on purpose. You need an asyncio application, and you need PostgreSQL. The README states the project requires Python 3.9 or later and is supported for PostgreSQL versions 9.5 to 18. Other databases that implement the PostgreSQL wire protocol may work, but the README says they are not actively tested. So a team running CockroachDB or a proxy in front of Postgres should treat compatibility as unverified rather than assumed.

The payoff for that narrowness is feature access without translation layers. The README lists prepared statements, scrollable cursors, partial iteration on query results, automatic encoding and decoding of composite types and arrays, and straightforward support for custom data types. Those are things a generic DB-API driver either cannot expose or exposes awkwardly.

## The binary protocol is the mechanism, not a marketing line

Most Python database drivers sit on top of a specification that predates asyncio by two decades. That specification assumes blocking calls and a cursor abstraction. asyncpg skips it. The package contains a protocol layer under asyncpg/protocol and a separate pgproto module, and the Makefile compiles Cython sources for both, which is consistent with the README's claim that the protocol is implemented natively rather than wrapped.

That architecture has visible consequences in the API. A query is not assembled as a string with parameters interpolated by the client. The README's example passes a placeholder and a value separately:

```python
values = await conn.fetch(
    'SELECT * FROM mytable WHERE id = $1',
    10,
)
```

The $1 placeholder is PostgreSQL's own parameter syntax, not a Python-side substitution. Because the driver speaks the protocol, it can prepare the statement on the server and reuse it, which is where a large part of the performance difference comes from. The README claims asyncpg is, on average, 5x faster than psycopg3, citing benchmarks run with the MagicStack pgbench tool in June 2023. That is the project's own measurement on its own benchmark harness, so treat the number as a directional claim from an interested party rather than an independent result.

Type handling also follows from the protocol work. When the server sends a composite type or an array, asyncpg decodes it into Python objects automatically. The README lists this as a feature, and it is the kind of thing a DB-API driver leaves to the caller or to a separate adapter.

## Installing asyncpg and running a first query

The README gives the install command directly. asyncpg is on PyPI, and when you are not using GSSAPI or SSPI authentication it has no dependencies.

```bash
pip install asyncpg
```

If you need GSSAPI or SSPI authentication, the README specifies an extra:

```bash
pip install 'asyncpg[gssauth]'
```

That extra pulls in gssapi on non-Windows platforms and sspilib on Windows, according to pyproject.toml. Note the quoting: without it, some shells will interpret the brackets.

For a first real use, the README's basic example is the shortest complete program. It connects, fetches one row by parameter, and closes.

```python
import asyncio
import asyncpg

async def run():
    conn = await asyncpg.connect(user='user', password='password',
                                 database='database', host='127.0.0.1')
    values = await conn.fetch(
        'SELECT * FROM mytable WHERE id = $1',
        10,
    )
    await conn.close()

asyncio.run(run())
```

What you should see is a list of Record objects, one per matching row, with column names accessible as attributes. The connect call takes the usual host, user, password and database arguments. The README does not show pool creation in this example, so if your workload is concurrent, look up create_pool in the project documentation rather than copying this snippet into a web handler; a single connection shared across concurrent tasks is not what this example is demonstrating.

For development from a checkout, the Makefile provides targets. `make compile` installs the package in editable mode with ASYNCPG_BUILD_CYTHON_ALWAYS=1, and `make test` runs the unittest suite three times, once with PYTHONASYNCIODEBUG=1 and once with USE_UVLOOP=1. Those environment variables are the project's own test harness, not user-facing configuration.

## Where asyncpg is the wrong tool

The same decision that makes asyncpg fast makes it a poor fit in several common situations.

First, it is PostgreSQL-only. If your codebase supports multiple databases behind one interface, asyncpg cannot be that interface. It does not implement DB-API, and the README presents that as a deliberate choice rather than a gap to be filled later.

Second, it is asyncio-only. There is no synchronous path. A script, a Celery worker, or a Django view running under a synchronous server cannot call `await conn.fetch(...)` without an event loop. Retrofitting an event loop into synchronous code to use a driver is the tail wagging the dog.

Third, the API surface is smaller than what DB-API users expect. There is no cursor object with the familiar method names. The README mentions scrollable cursors and partial iteration as features, but they are exposed through asyncpg's own interfaces, so code written against psycopg2 will need rewriting rather than a connection-string swap.

Fourth, version range matters. The README states support for PostgreSQL 9.5 to 18. If you run a version outside that range, or a database that merely implements the PostgreSQL protocol, the README says it may work but is not actively tested. That is a real boundary, not a formality.

Finally, the README's performance claim comes from the project's own benchmark tooling. If throughput is the reason you are switching, measure against your own schema and query mix before assuming the 5x figure transfers.

## asyncpg against psycopg3 and aiopg

The most common comparison is with psycopg3. The difference is architectural. psycopg3 offers both a synchronous and an asynchronous interface and implements the DB-API specification, which means code written for psycopg2 often ports with modest changes. asyncpg does neither: it is asynchronous only and does not implement DB-API. The README frames this as the source of its advantage, stating that exposing server features directly rather than behind a generic facade is what enables its prepared statement, cursor and type-codec support. The trade is straightforward: psycopg3 buys you portability and familiarity, asyncpg buys you a shorter path between your Python code and the PostgreSQL wire protocol.

aiopg is the other name that comes up. It is an asyncio wrapper around psycopg2 rather than a native protocol implementation, so the async layer sits on top of a blocking driver. That is a different bet about where the complexity belongs. asyncpg's answer is to own the protocol; aiopg's is to reuse an existing driver and adapt it.

If you use SQLAlchemy, the relevant question is which driver it delegates to. SQLAlchemy can use asyncpg as its async PostgreSQL dialect, which means you get SQLAlchemy's query construction and session management on top of asyncpg's protocol handling. That combination is worth understanding before you decide you must choose one or the other, though the asyncpg README itself does not document the SQLAlchemy integration; that lives in SQLAlchemy's documentation.

## Maintenance, releases and the Apache 2.0 licence

The repository is not archived, and the last push was on 2026-09-21, one day before this writing. Release cadence is visible in the tags: v0.31.0 on 2025-11-24, v0.30.0 on 2024-10-20, and v0.29.0 on 2023-11-05. That is roughly one release per year over the last three, so plan upgrades as an annual event rather than a continuous stream. The version is still 0.x, which in practice means the maintainers have not declared a stable API contract even though the PyPI classifier reads "Development Status :: 5 - Production/Stable". Those two signals point in different directions, and the classifier is the more generous one.

Upgrade cost is dominated by the build. asyncpg compiles Cython sources for its protocol and pgproto modules, so installing from source requires a working C toolchain and the Cython dependency pinned in setup.py as Cython>=3.2.1,<4.0.0. On platforms without prebuilt wheels you will pay that compile cost on every environment, including CI. The Makefile's `make clean` target removes the generated .c and .so artifacts if a build goes stale.

Licensing is Apache 2.0, stated in both the README and pyproject.toml, with license-files pointing at LICENSE. Apache 2.0 is a permissive licence with an explicit patent grant, which is generally the least friction option for commercial use. That is a description of the licence, not legal advice; if your organisation has a policy review process, route it there.

Python support tracks current versions: pyproject.toml lists 3.9 through 3.14, plus a "Free Threading :: 2 - Beta" classifier. The free-threading classifier is a beta designation, so treat no-GIL builds as something the project is testing rather than something you should assume is production-ready.

## Conclusion

Adopt asyncpg when your application is already asyncio-based and PostgreSQL is the only database you need; the native protocol implementation and prepared statement handling are the payoff. Do not adopt it if you need DB-API compatibility, synchronous code, or a driver that can also talk to MySQL or SQLite, because asyncpg is PostgreSQL-only and does not hide the protocol behind a generic facade. Before committing, verify that your Python version is 3.9 or later and your server version falls in the 9.5 to 18 range the README states, and confirm how you will manage connection lifetime, since the README's basic example opens and closes a single connection rather than showing pool setup.

## FAQ

### What is asyncpg used for?

It is a PostgreSQL client library for Python's asyncio framework, used to run queries against PostgreSQL from asynchronous code. The README describes it as an implementation of the PostgreSQL server binary protocol that exposes server features directly rather than through a DB-API facade.

### How do I install asyncpg?

Install it from PyPI with pip install asyncpg; the README notes it has no dependencies when you are not using GSSAPI or SSPI authentication. For those authentication methods, use pip install 'asyncpg[gssauth]'.

### Which is better, psycopg3 or asyncpg?

They make different trade-offs rather than one being strictly better. psycopg3 implements DB-API and offers both synchronous and asynchronous interfaces, while asyncpg is asyncio-only and bypasses DB-API to expose PostgreSQL features directly; the README claims asyncpg is on average 5x faster than psycopg3 based on the project's own benchmarks.

### What is an asyncpg pool?

A pool is a managed set of reusable connections created through asyncpg's create_pool interface, which the project documentation covers. The README's basic usage example shows a single connection opened with asyncpg.connect and closed afterward, not a pool.

### What does asyncpg do?

It implements the PostgreSQL server binary protocol for asyncio, so Python code can issue parameterized queries and receive decoded results, including composite types and arrays, without a DB-API layer in between. The README lists prepared statements, scrollable cursors and partial iteration among the features this enables.

### Is asyncpg good?

Whether it is good depends on your constraints: it is fast and exposes PostgreSQL features directly, but it is PostgreSQL-only and asyncio-only, and it does not implement DB-API. The README supports PostgreSQL 9.5 to 18 and requires Python 3.9 or later.

## Sources

- [Issues](https://github.com/MagicStack/asyncpg/issues)
- [License: Apache-2.0](https://github.com/MagicStack/asyncpg/blob/master/LICENSE)
- [MagicStack/asyncpg on GitHub](https://github.com/MagicStack/asyncpg)
- [README](https://github.com/MagicStack/asyncpg/blob/master/README.md)
- [Releases](https://github.com/MagicStack/asyncpg/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/magicstack-asyncpg
