Getting Started
Installation
pip install d2# Runtime dependencies: asyncpg, pypika, msgspecConfiguration
d2 reads project settings from pyproject.toml:
[tool.d2]migrations_dir = "migrations" # where .py migration files livemodels = "myapp.models" # dotted import path to your Table definitionsBoth keys have defaults (./migrations and ./models) so the section is optional for simple layouts.
Defining your first table
>>> from d2 import Table, Field, PrimaryKey, ForeignKey, Unique, field, TableMeta, db>>> class User(Table):... id: PrimaryKey[int] = field(default=db.serial())... username: Unique[str]... email: Unique[str]... bio: Field[str | None]...>>> class Post(Table):... id: PrimaryKey[int] = field(default=db.serial())... user_id: ForeignKey[User] = field(on_delete=db.CASCADE)... title: Field[str]... body: Field[str | None]...Key points:
- Subclass
Tablefor read-write tables,Viewfor read-only. - Use
PrimaryKey[T],Unique[T],Field[T]annotations to declare columns. field(default=db.serial())marks serial primary key columns — d2 skips them in INSERT by default.Field[str | None]marks a column as nullable.TableMetasets schema, table name overrides, composite indexes, and extensions.
Building a query (no DB required)
>>> sql, params = (... User... .select(User.id, User.username)... .where(User.id > 0)... .build()... )>>> sql'SELECT "users"."id","users"."username" FROM "public"."users" WHERE "users"."id">$1'>>> params(0,)Connecting and running a query
>>> import asyncpg # doctest: +SKIP>>> from d2 import AsyncConnection # doctest: +SKIP>>> import msgspec # doctest: +SKIP>>> class UserRow(msgspec.Struct): # doctest: +SKIP... id: int... username: str...>>> raw_conn = await asyncpg.connect("postgresql://localhost/mydb") # doctest: +SKIP>>> conn = AsyncConnection(raw_conn) # doctest: +SKIP>>> rows = await conn.fetch( # doctest: +SKIP... User.select(User.id, User.username).where(User.id > 0),... list[UserRow],... )>>> row = await conn.fetch( # doctest: +SKIP... User.select(User.id, User.username).where(User.id == 1),... UserRow,... )>>> await conn.execute( # doctest: +SKIP... User.insert(username="alice", email="alice@example.com")... )>>> await raw_conn.close() # doctest: +SKIPGenerating and applying migrations
After defining your tables, generate the first migration:
python -m d2.migrations make --label initialThis creates migrations/0001_initial.py. Apply it:
python -m d2.migrations apply --dsn postgresql://localhost/mydbSee Migrations for the full workflow.