Table column annotations for SQLAlchemy
Table column annotations for SQLAlchemy
Instead of this:
from datetime import UTC, datetime
from uuid import UUID, uuid4
import sqlalchemy as sa
from sqlalchemy.orm import Mapped, mapped_column
class User(Base):
id: Mapped[int] = mapped_column(sa.Integer(), sa.Identity(always=True), primary_key=True)
email: Mapped[str] = mapped_column(sa.Text(), unique=True, default='')
confirmed: Mapped[bool] = mapped_column(sa.Boolean(), default=False)
connection_id: Mapped[int | None] = mapped_column(sa.BigInteger(), index=True)
token: Mapped[UUID | None] = mapped_column(sa.Uuid(), index=True, default=uuid4)
created_at: Mapped[datetime] = mapped_column(sa.DateTime(timezone=True), default=lambda: datetime.now(tz=UTC))
you can declare your SQLAlchemy models like so:
import sqlalchemy_annotations as saa
from sqlalchemy.orm import Mapped
class User(Base):
id: Mapped[saa.IntegerPKColumn]
email: Mapped[saa.TextUniqueColumn]
confirmed: Mapped[saa.BooleanDefaultFalseColumn]
connection_id: Mapped[saa.BigIntegerIndexColumn | None]
token: Mapped[saa.UUIDIndexColumn | None]
created_at: Mapped[saa.DateTimeDefaultUtcNowColumn]
What are these IntegerPKColumn, BigIntegerIndexColumn, BooleanDefaultFalseColumn and other ones?
These are just type aliases created with typing.Annotated function that adds context-specific metadata to a type,
so that you don't need to specify all these extra assignments to mapped_column function
Links
Examples
Integers (INTEGER, BIGINT, SERIAL, BIGSERIAL)
IntegerPKColumn/BigIntegerPKColumn–INTEGER / BIGINT, GENERATED ALWAYS AS IDENTITY, PRIMARY KEYSerialPKColumn/BigSerialPKColumn–SERIAL / BIGSERIAL, PRIMARY KEYIntegerColumn/BigIntegerColumn–INTEGER / BIGINTIntegerIndexColumn/BigIntegerIndexColumn–INTEGER / BIGINT, INDEX
from sqlalchemy.orm import Mapped
from sqlalchemy_annotations import IntegerColumn, IntegerIndexColumn, IntegerPKColumn
class Model(Base):
id: Mapped[IntegerPKColumn]
# equivalent to
# id: Mapped[int] = mapped_column(sa.Integer(), Identity(always=True), primary_key=True)
number: Mapped[IntegerColumn]
# equivalent to
# number: Mapped[int] = mapped_column(sa.Integer())
uid: Mapped[IntegerIndexColumn]
# equivalent to
# uid: Mapped[int] = mapped_column(sa.Integer(), index=True)
Strings (TEXT)
TextPKColumn–TEXT, PRIMARY KEYTextColumn–TEXTwithdefault=''TextUniqueColumn–TEXT, UNIQUETextIndexColumn–TEXT, INDEX
from sqlalchemy.orm import Mapped
from sqlalchemy_annotations import TextColumn, TextIndexColumn, TextPKColumn, TextUniqueColumn
class Model(Base):
id: Mapped[TextPKColumn]
# equivalent to
# id: Mapped[str] = mapped_column(sa.Text(), primary_key=True)
name: Mapped[TextColumn]
# equivalent to
# name: Mapped[str] = mapped_column(sa.Text(), default='')
email: Mapped[TextUniqueColumn]
# equivalent to
# email: Mapped[str] = mapped_column(sa.Text(), unique=True)
ext_id: Mapped[TextIndexColumn]
# equivalent to
# ext_id: Mapped[str] = mapped_column(sa.Text(), index=True)
UUID (UUID)
UUIDPKColumn–UUID, PRIMARY KEYwithdefault=uuid4UUIDIndexColumn–UUID, INDEXwithdefault=uuid4UUIDColumn–UUIDwithdefault=uuid4
from sqlalchemy.orm import Mapped
from sqlalchemy_annotations import UUIDColumn, UUIDIndexColumn, UUIDPKColumn
class Model(Base):
id: Mapped[UUIDPKColumn]
# equivalent to
# id: Mapped[str] = mapped_column(sa.Uuid(), primary_key=True, default=uuid4)
token: Mapped[UUIDIndexColumn]
# equivalent to
# token: Mapped[str] = mapped_column(sa.Uuid(), index=True, default=uuid4)
ext_id: Mapped[UUIDColumn]
# equivalent to
# ext_id: Mapped[str] = mapped_column(sa.Uuid(), default=uuid4)
Date, Time, Datetime (DATE, TIME, TIMESTAMP)
DateColumn–DATETimeColumn–TIME WITH TIME ZONETimeWOTimezoneColumn–TIME WITHOUT TIME ZONEDateTimeColumn–TIMESTAMP WITH TIME ZONEDateTimeWOTimezoneColumn–TIMESTAMP WITHOUT TIME ZONEDateTimeDefaultUtcNowColumn–TIMESTAMP WITH TIME ZONEwithdefault=lambda: datetime.now(tz=UTC)
from sqlalchemy.orm import Mapped
from sqlalchemy_annotations import (
DateColumn,
DateTimeColumn,
DateTimeDefaultUtcNowColumn,
DateTimeWOTimezoneColumn,
TimeColumn,
TimeWOTimezoneColumn,
)
class Model(Base):
...
birthday: Mapped[DateColumn]
# equivalent to
# birthday: Mapped[date] = mapped_column(sa.Date())
start_at: Mapped[TimeColumn]
# equivalent to
# start_at: Mapped[time] = mapped_column(sa.Time(timezone=True))
notify_at: Mapped[TimeWOTimezoneColumn]
# equivalent to
# notify_at: Mapped[time] = mapped_column(sa.Time(timezone=False))
approved_at: Mapped[DateTimeColumn]
# equivalent to
# approved_at: Mapped[datetime] = mapped_column(sa.DateTime(timezone=True))
failed_at: Mapped[DateTimeWOTimezoneColumn]
# equivalent to
# failed_at: Mapped[datetime] = mapped_column(sa.DateTime(timezone=False))
created_at: Mapped[DateTimeDefaultUtcNowColumn]
# equivalent to
# created_at: Mapped[datetime] = mapped_column(sa.DateTime(timezone=True), default=lambda: datetime.now(tz=UTC))
Booleans (BOOLEAN)
BooleanColumn–BOOLEANBooleanDefaultFalseColumn–BOOLEANwithdefault=FalseBooleanDefaultTrueColumn–BOOLEANwithdefault=True
from sqlalchemy.orm import Mapped
from sqlalchemy_annotations import BooleanColumn, BooleanDefaultFalseColumn, BooleanDefaultTrueColumn
class Model(Base):
...
is_active: Mapped[BooleanColumn]
# equivalent to
# is_active: Mapped[bool] = mapped_column(Boolean())
is_confirmed: Mapped[BooleanDefaultFalseColumn]
# equivalent to
# is_confirmed: Mapped[bool] = mapped_column(Boolean(), default=False)
is_visible: Mapped[BooleanDefaultTrueColumn]
# equivalent to
# is_visible: Mapped[bool] = mapped_column(Boolean(), default=True)
String enums (VARCHAR)
This annotation will not create a native enum
The column will be VARCHAR (without length limit) so that you can easily update your enum
BooleanColumn–BOOLEANBooleanDefaultFalseColumn–BOOLEANwithdefault=FalseBooleanDefaultTrueColumn–BOOLEANwithdefault=True
from enum import StrEnum
from sqlalchemy.orm import Mapped
from sqlalchemy_annotations import StrEnumColumn
class UserStatus(StrEnum):
REGISTERED = 'REGISTERED'
ACTIVATED = 'ACTIVATED'
BANNED = 'BANNED'
class Model(Base):
...
status: Mapped[StrEnumColumn[UserStatus]]
# equivalent to
# status: Mapped[UserStatus] = mapped_column(Enum(UserStatus, native_enum=False, length=None))
A note on contribution
This is opensource project
So if you find something that you can add of fix, feel free to
open an issue
and raise a pull request
Thank you!
Metadata
Release files for sqlalchemy-annotations 0.0.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| sqlalchemy_annotations-0.0.2.tar.gz | 7.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sqlalchemy_annotations-0.0.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 13.9 kB
Release files / sqlalchemy_annotations-0.0.2.tar.gz
| Download URL | sqlalchemy_annotations-0.0.2.tar.gz |
|---|---|
| Size | 7.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
74533ed688a82e5b0d01b3909512e898be823ab9f19c4404a2c1ebcacf93d319
|
|
BLAKE2b-256 checksum How to use checksums |
b0d80f9f62e1485c5c59ea1a9a34115410e9cf8915d9f021f695574198e531f1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.6.5
|
Release files / sqlalchemy_annotations-0.0.2-py3-none-any.whl
| Download URL | sqlalchemy_annotations-0.0.2-py3-none-any.whl |
|---|---|
| Size | 7.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
12416461a35c13e7a86ab4ff554a09c7374981f87c2cdc73b45e0c6687d69bcd
|
|
BLAKE2b-256 checksum How to use checksums |
7062c80555e64e09af782c6494eca6ac590fdd3998402b4916a3ee9320fbfb2c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.6.5
|