Rails annotate_models for modern SQLAlchemy 2.x + Alembic projects.
Project description
sqlalchemy-annotate
Rails annotate_models for modern SQLAlchemy 2.x + Alembic projects.
It keeps a Schema Information comment block alongside every model class, in
sync with your actual SQLAlchemy metadata, so reviewers and editors see the
table shape without opening a database or a migration. The block sits below
the class by default; flip position = "top" to put it above instead.
class User(Base):
__tablename__ = "users"
...
# == Schema Information
#
# Table name: users
#
# id : integer, primary key
# email : varchar, not null
# created_at : timestamp
#
# Indexes
# ix_users_email (email) UNIQUE
#
# Foreign Keys
# profile_id -> profiles.id (ON DELETE CASCADE)
#
# == End Schema Information
Why it is safe
- No database required. Everything is read from
Base.metadata/__table__. A connection URL, if you pass one, is parsed only for its dialect so types compile correctly offline. No engine is ever connected. - No regex rewriting. Files are edited through a single libcst transformer. Imports, comments, blank lines and Black formatting are preserved byte-for-byte; only the region between the markers is touched.
- Idempotent. Running
generatetwice produces no diff, which is what makescheckreliable in CI and pre-commit.
Install
pip install sqlalchemy-annotate # or: uv pip install sqlalchemy-annotate
Usage
sqlalchemy-annotate generate
sqlalchemy-annotate generate --models app.models --engine postgresql://localhost/db
sqlalchemy-annotate check # exit 1 if stale (CI)
sqlalchemy-annotate remove # strip every block
sqlalchemy-annotate generate --dry-run # show the diff, write nothing
--models points at the dotted package path. Subpackages are imported
recursively (app/models/user.py, app/models/post.py, ...), so every model
registers regardless of how many files you split them across. A broken or
circularly-importing module is reported as a warning and skipped, never fatal.
Configuration
Put defaults in pyproject.toml; CLI flags override them.
[tool.sqlalchemy-annotate]
models = "app.models"
engine = "postgresql+asyncpg://..." # dialect only, never connected
include_indexes = true
include_foreign_keys = true
include_relationships = false
normalize_types = false # true -> Rails-style (bigint, varchar(255))
sort = "definition" # or "alphabetical"
position = "bottom" # or "top" to place the block above the class
exclude = ["audit_*", "*_history"]
Type rendering
By default types are truthful: rendered exactly as SQLAlchemy compiles
them for the resolved dialect (Mapped[int] PK -> integer, Mapped[str] ->
varchar). Set normalize_types = true for Rails-style fabrication
(autoincrement int PK -> bigint, unlengthed string -> varchar(255)).
The dialect is --dialect if given, else the backend of engine
(async drivers collapse to their sync backend), else PostgreSQL.
Alembic / pre-commit
Run it right after a schema change:
alembic revision --autogenerate -m "add users"
alembic upgrade head
sqlalchemy-annotate generate
.pre-commit-config.yaml:
repos:
- repo: https://github.com/abhinavs/sqlalchemy-annotate
rev: v0.1.1
hooks:
- id: sqlalchemy-annotate
CI gate:
- run: sqlalchemy-annotate check
How it works
| Module | Responsibility |
|---|---|
discovery.py |
import-walk the package, collect mapped classes -> files |
schema.py |
Table/Mapper -> ModelSchema value objects (pure) |
formatter.py |
ModelSchema -> comment lines (pure, pluggable renderer) |
parser.py |
libcst helpers: build lines, locate the existing block |
writer.py |
one libcst transformer: insert / replace / remove |
config.py |
defaults <- pyproject.toml <- CLI |
cli.py |
typer commands; heavy imports stay lazy |
Roadmap (designed, not built)
The formatter is already a swappable Renderer behind the
sqlalchemy_annotate.renderers entry-point group, leaving room for: Markdown
schema export, ER diagram generation, a schema diff command, watch mode, and
an IDE/VSCode integration.
Project
- Contributing - dev setup, the idempotency invariant, release steps
- Changelog
- Security policy - note: the tool imports your model package, like
pytest/alembic - Ships with PEP 561 type information (
py.typed)
License
MIT (c) 2026 Abhinav Saxena
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sqlalchemy_annotate-0.1.1.tar.gz.
File metadata
- Download URL: sqlalchemy_annotate-0.1.1.tar.gz
- Upload date:
- Size: 23.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d1695360994fd04e3886a2f798e76bf1c143e35022241246035fd295e1d8e959
|
|
| MD5 |
57399868f63b5a980b1559f2dfd76602
|
|
| BLAKE2b-256 |
7238d6b346e481bf1ffec781ebe040fcdee050b2b51c1316044c76e29fa8f564
|
Provenance
The following attestation bundles were made for sqlalchemy_annotate-0.1.1.tar.gz:
Publisher:
release.yml on abhinavs/sqlalchemy-annotate
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sqlalchemy_annotate-0.1.1.tar.gz -
Subject digest:
d1695360994fd04e3886a2f798e76bf1c143e35022241246035fd295e1d8e959 - Sigstore transparency entry: 1710349600
- Sigstore integration time:
-
Permalink:
abhinavs/sqlalchemy-annotate@86eae9f22288fe23186e54cb0ff5d94ddc9dca39 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/abhinavs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@86eae9f22288fe23186e54cb0ff5d94ddc9dca39 -
Trigger Event:
release
-
Statement type:
File details
Details for the file sqlalchemy_annotate-0.1.1-py3-none-any.whl.
File metadata
- Download URL: sqlalchemy_annotate-0.1.1-py3-none-any.whl
- Upload date:
- Size: 20.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
009b422d65ae111101c84d926592a3c11290cef5d7498982524e3ebd22e50631
|
|
| MD5 |
caf92e66fe5f27cc177482499e5fb18f
|
|
| BLAKE2b-256 |
c3d644023b3dfa729a9557abfc004da002c0821c8da59abb803e8aa21f0f7aa4
|
Provenance
The following attestation bundles were made for sqlalchemy_annotate-0.1.1-py3-none-any.whl:
Publisher:
release.yml on abhinavs/sqlalchemy-annotate
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sqlalchemy_annotate-0.1.1-py3-none-any.whl -
Subject digest:
009b422d65ae111101c84d926592a3c11290cef5d7498982524e3ebd22e50631 - Sigstore transparency entry: 1710349627
- Sigstore integration time:
-
Permalink:
abhinavs/sqlalchemy-annotate@86eae9f22288fe23186e54cb0ff5d94ddc9dca39 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/abhinavs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@86eae9f22288fe23186e54cb0ff5d94ddc9dca39 -
Trigger Event:
release
-
Statement type: