peewee-async: an asyncio face on the peewee ORM without a rewrite
Asynchronous interface for peewee ORM powered by asyncio
At a glance
- What is it?
- peewee-async adds aio_ prefixed asynchronous counterparts to peewee's synchronous methods, keeps the same model definitions, and supports PostgreSQL through aiopg or psycopg3, MySQL through aiomysql and SQLite through aiosqlite. It also keeps a switch that turns synchronous access off so mistakes surface as errors.
- Who is it for?
- Adopt peewee-async if your team already knows peewee and wants to serve an asyncio application without moving to a different ORM or rewriting its model layer, since the aio_ prefix keeps the two APIs visually distinct and the allow_sync switch catches accidental blocking calls. Do not adopt it if you want the whole ecosystem of async drivers and a large community, because this is a compatibility layer over one ORM with a documented list of supported operations.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The design bet: keep peewee's models, add an aio_ prefix for the async path
peewee is a small, fast ORM whose methods are synchronous, and rewriting a peewee application to asyncio means either replacing the ORM or wrapping every call. peewee-async takes the middle path. It keeps the same model definitions, the same fields and the same query builder, and adds asynchronous counterparts of the synchronous methods with an aio_ prefix. A select becomes select().aio_execute(), a create becomes aio_create(), and the underlying query building is the peewee code you already know.
The stated goal is to be a drop-in replacement for sync code, with the rule that sync stays sync. That framing is the interesting part, because the project is not trying to hide the difference between the two worlds, it is trying to make the difference visible in the source. An aio_ prefix in a query chain is impossible to miss in review, and it tells the reader immediately whether a call will block the event loop.
The audience is therefore a specific one: a team with an existing peewee codebase and an asyncio service in front of it. The project requires Python 3.10 or newer, is MIT licensed, and its documentation lives at peewee-async-lib.readthedocs.io.
Four database backends, each behind an extra
Every asynchronous database driver in the Python ecosystem has its own quirks, so peewee-async keeps the choice with the user. PostgreSQL is supported through aiopg and, separately, through psycopg3, which is the newer generation of the PostgreSQL adapter. MySQL goes through aiomysql, and SQLite through aiosqlite. The install extras map one to one onto those choices.
pip install peewee-async[postgresql]pip install peewee-async[psycopg]pip install peewee-async[mysql]pip install peewee-async[sqlite]That last pair, PostgreSQL and SQLite, is worth comparing carefully, because they are not interchangeable in practice. SQLite through aiosqlite runs in the same process against a file, which is convenient for tests and single user tools, while the PostgreSQL options are what a service would use. Supporting psycopg3 alongside aiopg is a pragmatic choice rather than a preference for one library, and the project lists both so you can pick the one that fits the surrounding code.
The core dependencies are deliberately small: peewee itself, pinned to the 4 series, and typing-extensions. Everything driver specific is optional, so a SQLite project does not pull a PostgreSQL driver into its environment.
The quickstart shows sync and async against the same model in one file
The README example is the best description of the design, because it deliberately does both in a single script. The setup is ordinary peewee: a pooled PostgreSQL database object and a model class.
import asyncio
import peewee
import peewee_async
# Nothing special, just define model and database:
database = peewee_async.PooledPostgresqlDatabase(
database="db_name",
user="user",
host="127.0.0.1",
port="5432",
password="password",
)The model is an AioModel rather than a peewee Model, with the same field declarations and the same Meta block.
class TestModel(peewee_async.AioModel):
text = peewee.CharField()
class Meta:
database = databaseWhat changes next is the interesting part. The script creates the table and a row through the ordinary synchronous methods, then closes the database, and then switches the database object into a mode where synchronous access is not allowed.
database.set_allow_sync(False)With that set, the async methods are the only way through, and the handler shows the two of them you need most, an awaitable create and an awaitable query execution.
async def handler():
await TestModel.aio_create(text="Not bad. Watch this, I'm async!")
all_objects = await TestModel.select().aio_execute()
for obj in all_objects:
print(obj.text)Cleaning up switches the object back, and the with database.allow_sync(): context manager is the tidy way to do it, which matters when a script mixes both styles the way this one does.
allow_sync is the guardrail that turns a blocking call into an error
The single most useful idea in the project is database.set_allow_sync(False). Once it is set, calling a synchronous peewee method through this database object is an error rather than a silent stall of the event loop. In a small script that is a convenience, but in a service that is the difference between a bug that shows up as a slow endpoint and a bug that shows up as a traceback.
The allow_sync() context manager is the counterpart, and it gives you a scoped way to run synchronous code deliberately, which is what the quickstart uses for the cleanup step after the async work is done. That combination, a global switch to forbid sync and a context manager to allow it in one place, is a small design decision that carries a lot of weight.
The README also states that most basic operations are supported and that transaction support is present. Those two sentences are the honest scope of the project. It is not a full async reimplementation of peewee, it is a set of asynchronous analogues for the common operations, and a query that uses an unsupported method is where you would find out.
Working on the project: compose databases, pytest, and a strict lint setup
The development setup is short and shows how the project is tested across backends. The dev extra pulls in the four database extras at once, so the environment has drivers for PostgreSQL, MySQL, psycopg and SQLite, along with pytest, pytest-asyncio, pytest-mock, mypy and ruff. Installing it is a single command.
pip install -e .[dev]The databases come up as containers, one Postgres and one MySQL, and the compose file lets you move their host ports with the POSTGRES_PORT and MYSQL_PORT variables, defaulting to 5432 and 3306, and it starts Postgres with log_statement=all so statement logging is available when a query misbehaves.
docker-compose up -dThe test suite is then run with pytest, verbosely and without output capture.
pytest tests -v -sThe project also carries a load-testing/ directory and a examples/ directory with an aiohttp example and its own requirements file, which tells you both how the library is meant to be used inside a web framework and that the authors cared about behaviour under load. The tooling choices are worth noting for a project that has been around since 2014: the build backend is uv_build, the release tags follow conventional commits through commitizen, and ruff runs with a 120 character line length and a selected rule set that includes flake8-bugbear, flake8-comprehensions, isort and pyupgrade rules.
Version 2.1.0 under MIT, with a documented split between async and sync
The project is MIT licensed, copyright Alexey Kinev from 2014, and the licence file is in the repository root, so using it inside a commercial service carries no licence obligation beyond keeping the notice. The maintenance record is healthy for a project of its age: the repository is not archived, the last push was on 2026-10-19, and the releases include v2.1.0 from 2026-08-06, v2.0.0 from 2026-04-17 and v1.3.0 from 2026-02-21.
That v2.0.0 release in April 2026 is the thing to look at before you pin a version, because a major bump in a compatibility layer usually means the sync behaviour or the API shape changed. The project keeps its documentation on readthedocs under the peewee-async-lib name, and it marks the older peewee-async.readthedocs.io address as deprecated, so following the wrong link gets you stale documentation.
The two documentation links also imply something about the project size: this is a focused library with one maintained documentation site, not a framework with an ecosystem around it. The upstream peewee project, the asyncio standard library and the four database drivers are where the depth lives. If you need a query that peewee-async has not wrapped, you are reading driver documentation, not this project's.
When peewee-async is the wrong tool
The case against is not that the library is slow or broken, it is that the compatibility approach has a ceiling. The asynchronous surface is a set of analogues for peewee's synchronous methods, and the project itself says most basic operations are supported rather than all of them. A complex query with unusual joins, custom expressions or a custom function is exactly where you would discover the gap, and at that point you have a choice between avoiding that construct, waiting for a wrapper to exist, or dropping back to a synchronous call inside a thread, which reintroduces the blocking problem the library was meant to solve.
There is a second case, which is that the whole premise disappears if you are choosing an ORM today. The reason to reach for peewee-async is that you already have peewee code. If you are starting a new async service with no existing ORM investment, an ORM whose entire API is async, with an async-native driver and a mature async query builder, is the more natural choice, and peewee-async gives you none of its advantages in that scenario.
A third consideration is team familiarity. The aio_ prefix keeps the two APIs distinct, but the underlying query construction is still peewee's, so you are learning a wrapper and an ORM at once, and the debugging path when something misbehaves runs through two layers.
Editorial conclusion
Adopt peewee-async if your team already knows peewee and wants to serve an asyncio application without moving to a different ORM or rewriting its model layer, since the aio_ prefix keeps the two APIs visually distinct and the allow_sync switch catches accidental blocking calls. Do not adopt it if you want the whole ecosystem of async drivers and a large community, because this is a compatibility layer over one ORM with a documented list of supported operations. Before you build on it, read the list of supported methods in the documentation and confirm your queries are covered, run the test suite with pytest tests -v -s against the compose databases, and decide deliberately how you set allow_sync in production.
Frequently asked questions
How do I install peewee-async for PostgreSQL?
There are two PostgreSQL extras. Install peewee-async[postgresql] for the aiopg backend or peewee-async[psycopg] for the psycopg3 backend, and peewee-async[mysql] or peewee-async[sqlite] for MySQL and SQLite respectively.
How do peewee-async and synchronous peewee coexist in the same model?
The model is defined once as a peewee_async.AioModel, and both paths use it. Synchronous methods such as create and select work as they do in peewee, and the asynchronous counterparts carry the aio_ prefix, for example aio_create and select().aio_execute(), with the same query building underneath.
What does database.set_allow_sync(False) do in peewee-async?
It switches the database object into a mode where synchronous access is not allowed, so an accidental blocking call becomes an error instead of stalling the event loop. The allow_sync() context manager is the scoped counterpart for code that needs to run synchronously on purpose.
How do I run the peewee-async test suite?
Install the development extra with pip install -e .[dev], start the databases with docker-compose up -d, which brings up a Postgres and a MySQL container, and then run pytest tests -v -s. The dev extra pulls in the database extras for all four supported backends.
Does peewee-async support transactions and which Python version does it need?
The project states that transaction support is present and that most basic operations are supported, and it requires Python 3.10 or newer. The current release is v2.1.0, published on 2026-08-06.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/05bit-peewee-async)Was this useful?