Connection
AsyncConnection
AsyncConnection wraps an asyncpg connection and adds query-builder integration and msgspec deserialisation.
>>> import asyncpg # doctest: +SKIP>>> from d2 import AsyncConnection # doctest: +SKIP>>> raw = await asyncpg.connect("postgresql://localhost/mydb") # doctest: +SKIP>>> conn = AsyncConnection(raw) # doctest: +SKIPYou can also pass a custom dialect (currently only PostgresDialect is implemented):
>>> from d2.dialect import PostgresDialect # doctest: +SKIP>>> conn = AsyncConnection(raw, dialect=PostgresDialect()) # doctest: +SKIPfetch
Executes a SELECT query and deserialises the results.
Fetch a list
>>> import msgspec>>> class UserRow(msgspec.Struct):... id: int... name: str...>>> rows = await conn.fetch( # doctest: +SKIP... User.select(User.id, User.name),... list[UserRow],... )Fetch one (or None)
>>> row = await conn.fetch( # doctest: +SKIP... User.select(User.id, User.name).where(User.id == 42),... UserRow,... )Fetch JSON
When the query ends with .json(), fetch decodes the JSON payload and deserialises it into the result type. A JSON codec is registered on the underlying connection automatically (once per AsyncConnection instance):
>>> class UserWithPosts(msgspec.Struct):... id: int... name: str... posts: list[dict]...>>> from d2 import Table, Field, PrimaryKey, field, With # doctest: +SKIP>>> result = await conn.fetch( # doctest: +SKIP... User.select(User.id, User.name).prefetch(... Post.select(Post.id, Post.title).aliased("posts")... ).json(),... list[UserWithPosts],... )fetchval
Fetches a single scalar value from the first row of the result:
>>> count = await conn.fetchval( # doctest: +SKIP... User.select(User.id.count().aliased("n"))... )Useful for aggregates, MAX, MIN, or any query that projects a single column.
execute
Executes INSERT, UPDATE, DELETE, or any write query. Returns the asyncpg command status string (e.g. "INSERT 0 1"):
>>> status = await conn.execute( # doctest: +SKIP... User.insert(username="alice", email="alice@example.com")... )execute_many
Executes a bulk INSERT (when is_many=True, i.e. the rows= list form was used):
>>> rows = [... {"username": "alice", "email": "alice@example.com"},... {"username": "bob", "email": "bob@example.com"},... ]>>> await conn.execute_many(User.insert(rows)) # doctest: +SKIPtransaction
Returns the underlying asyncpg transaction context manager:
>>> async with conn.transaction(): # doctest: +SKIP... await conn.execute(User.insert(username="alice", email="alice@example.com"))... await conn.execute(Post.insert(user_id=1, title="Hello"))If any statement inside raises, the transaction is rolled back automatically.
Raw SQL access
These are intended for the migration system and should rarely be needed in application code:
>>> await conn.raw_execute("TRUNCATE TABLE users") # doctest: +SKIP>>> await conn.raw_execute("INSERT INTO users (name) VALUES ($1)", "alice") # doctest: +SKIP>>> rows = await conn.raw_fetch("SELECT id, name FROM users WHERE id > $1", 0) # doctest: +SKIP>>> async with conn.raw_transaction(): # doctest: +SKIP... await conn.raw_execute("DELETE FROM users")Result type mapping
fetch uses msgspec.convert(dict(row), result_type) under the hood. Any type that msgspec can decode works:
>>> class UserStruct(msgspec.Struct):... id: int... name: str...>>> from dataclasses import dataclass>>> @dataclass... class UserDC:... id: int... name: str...msgspec.Structsubclasses (recommended — fastest, type-safe)dict/list[dict]dataclasses.dataclassinstances- Plain
TypedDict