Asyncio¶
The asyncio API drives PostGamma's level-triggered waitable descriptors from the active event loop. Query progress does not allocate one Python helper thread per request. Instance startup and shutdown may use bounded thread offload because those lifecycle operations are blocking.
import asyncio
import postgamma
async def main() -> None:
async with postgamma.AsyncDatabase("agent.pgm", worker_count=4) as database:
first = await database.connect(autocommit=True)
second = await database.connect(autocommit=True)
try:
await first.execute(
"create table if not exists memory("
"id bigint primary key, body jsonb not null)"
)
rows, answer = await asyncio.gather(
first.execute("select count(*) from memory"),
second.execute("select 42"),
)
print(rows.fetchone(), answer.fetchone())
finally:
await first.close()
await second.close()
asyncio.run(main())
Run the example in an empty directory. It prints (0,) (42,); the two result
objects are produced by independent logical sessions scheduled through one
embedded instance.
One asynchronous connection¶
connect_async() is the direct counterpart of connect(). Awaiting it returns
an already-open AsyncConnection:
connection = await postgamma.connect_async("agent.pgm", autocommit=True)
try:
result = await connection.execute("select current_database()")
print(result.fetchone())
finally:
await connection.close()
It accepts the same path, mode, logical database, role, settings, worker count,
autocommit, and timeout options as synchronous connect(). Sync and async
top-level calls for the same canonical path share the same process-local
instance registry and still return independent sessions.
Explicit async instance¶
Use AsyncDatabase for several connections, events, checkpoint, maintenance,
or logical backup and restore:
async with postgamma.AsyncDatabase("agent.pgm", worker_count=4) as database:
first = await database.connect(autocommit=True)
second = await database.connect(autocommit=True)
try:
left, right = await asyncio.gather(
first.execute("select pg_sleep(0.1), 1"),
second.execute("select pg_sleep(0.1), 2"),
)
finally:
await first.close()
await second.close()
AsyncDatabase may wrap an existing synchronous Database through
database.as_async(). AsyncConnection similarly wraps a logical session
through connection.as_async().
Concurrency¶
Use separate connections for simultaneous work. One logical connection retains the same serialization rule in synchronous and asynchronous code:
left, right = await asyncio.gather(
first.execute("select pg_sleep(0.1), 1"),
second.execute("select pg_sleep(0.1), 2"),
)
The number of simultaneously open transactions must not exceed the database's worker count.
Transaction scopes¶
An async transaction scope has the same commit and rollback rules as its sync counterpart and does not close the connection:
connection = await postgamma.connect_async(
"agent.pgm",
mode=postgamma.OpenMode.OPEN_EXISTING,
)
try:
async with connection.transaction() as transaction:
await transaction.execute(
"insert into jobs(id, state) values ($1, $2)",
[1, "queued"],
)
finally:
await connection.close()
A clean scope commits and an exception rolls back. Nested scopes on the same connection are rejected.
Cancellation and timeouts¶
Canceling an asyncio task routes cancellation to the PostgreSQL request, retires
the native request, and leaves the connection reusable when PostgreSQL can
recover it safely. Query deadlines use the same cancellation path and raise
QueryTimeoutError.
task = asyncio.create_task(connection.execute("select pg_sleep(30)"))
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
Do not close a connection concurrently with another operation using it. Await the canceled task before closing the connection.
Events¶
AsyncDatabase.events() is an async iterator over the instance event queue:
The iterator stops when the database closes and does not retain a closed event loop.