SQLAlchemy building blocks for Belgie
Project description
belgie-alchemy
SQLAlchemy 2.0 building blocks for database models.
Overview
belgie-alchemy provides opinionated defaults and utilities for SQLAlchemy:
- Base: Declarative base with dataclass mapping and sensible defaults
- Mixins:
PrimaryKeyMixin(UUID),TimestampMixin(created/updated/deleted timestamps) - Types:
DateTimeUTC(timezone-aware datetimes),Scopes(dialect-specific array/JSON storage)
This module provides building blocks only - you define your own models.
Quick Start
from datetime import datetime
from belgie_alchemy import Base, PrimaryKeyMixin, TimestampMixin, DateTimeUTC
from sqlalchemy.orm import Mapped, mapped_column
class Article(Base, PrimaryKeyMixin, TimestampMixin):
__tablename__ = "articles"
title: Mapped[str]
published_at: Mapped[datetime] = mapped_column(DateTimeUTC)
This gives you:
- UUID primary key with server-side generation
- Automatic
created_at,updated_at,deleted_attimestamps - Timezone-aware datetime handling
- Dataclass-style
__init__,__repr__,__eq__
Building Blocks
Base
Declarative base with dataclass mapping enabled:
from belgie_alchemy import Base
from sqlalchemy.orm import Mapped, mapped_column
class MyModel(Base):
__tablename__ = "my_models"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
# Dataclass-style instantiation
model = MyModel(id=1, name="example")
Features:
- Consistent naming conventions for constraints
- Automatic type annotation mapping (
datetime→DateTimeUTC) - Dataclass mapping with
kw_only=True,repr=True,eq=True
Mixins
PrimaryKeyMixin
Adds a UUID primary key with server-side generation:
from belgie_alchemy import Base, PrimaryKeyMixin
class MyModel(Base, PrimaryKeyMixin):
__tablename__ = "my_models"
# Automatically includes: id: Mapped[UUID]
The id field:
- Type:
UUID - Server-generated using
gen_random_uuid() - Indexed and unique
- Primary key
TimestampMixin
Adds automatic timestamp tracking:
from belgie_alchemy import Base, TimestampMixin
class MyModel(Base, TimestampMixin):
__tablename__ = "my_models"
# Automatically includes:
# - created_at: Mapped[datetime]
# - updated_at: Mapped[datetime] (auto-updates on changes)
# - deleted_at: Mapped[datetime | None]
Features:
created_atset automatically on insertupdated_atauto-updates on row changesdeleted_atfor soft deletionmark_deleted()method to setdeleted_at
Types
DateTimeUTC
Timezone-aware datetime storage:
from datetime import datetime
from belgie_alchemy import Base, DateTimeUTC
from sqlalchemy.orm import Mapped, mapped_column
class Event(Base):
__tablename__ = "events"
id: Mapped[int] = mapped_column(primary_key=True)
happened_at: Mapped[datetime] = mapped_column(DateTimeUTC)
Features:
- Automatically converts naive datetimes to UTC
- Preserves timezone-aware datetimes
- Always returns UTC-aware datetimes from database
- Works with PostgreSQL, SQLite, MySQL
Scopes
Dialect-specific array storage for permission scopes:
from enum import StrEnum
from belgie_alchemy import Base, Scopes
from sqlalchemy.orm import Mapped, mapped_column
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
# Option 1: Simple string array (works everywhere)
scopes: Mapped[list[str] | None] = mapped_column(Scopes, default=None)
Features:
- PostgreSQL: Uses native
ARRAY(String)type - SQLite/MySQL: Uses JSON storage
- Automatically converts StrEnum values to strings
- Handles
Nonevalues correctly
For PostgreSQL with application-specific enum types, you can override:
from enum import StrEnum
from sqlalchemy import ARRAY
from sqlalchemy.dialects.postgresql import ENUM
class AppScope(StrEnum):
READ = "resource:read"
WRITE = "resource:write"
ADMIN = "admin"
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
# Option 2: PostgreSQL native ENUM array (type-safe)
scopes: Mapped[list[AppScope] | None] = mapped_column(
ARRAY(ENUM(AppScope, name="app_scope", create_type=True)),
default=None,
)
Complete Example: Auth Models
See examples/alchemy/auth_models.py for a complete reference implementation of authentication models:
User- with email, verification, and scopesAccount- OAuth provider linkageSession- user session managementOAuthState- OAuth flow state
These are templates - copy them to your project and customize as needed.
Example structure:
from datetime import datetime
from uuid import UUID
from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from belgie_alchemy import Base, PrimaryKeyMixin, TimestampMixin, DateTimeUTC, Scopes
class User(Base, PrimaryKeyMixin, TimestampMixin):
__tablename__ = "users"
email: Mapped[str] = mapped_column(unique=True, index=True)
email_verified: Mapped[bool] = mapped_column(default=False)
scopes: Mapped[list[str] | None] = mapped_column(Scopes, default=None)
accounts: Mapped[list["Account"]] = relationship(
back_populates="user",
cascade="all, delete-orphan",
init=False,
)
class Account(Base, PrimaryKeyMixin, TimestampMixin):
__tablename__ = "accounts"
user_id: Mapped[UUID] = mapped_column(
ForeignKey("users.id", ondelete="cascade"),
nullable=False,
)
provider: Mapped[str]
provider_account_id: Mapped[str]
user: Mapped[User] = relationship(
back_populates="accounts",
lazy="selectin",
init=False,
)
Design Principles
- Building blocks, not frameworks - You own your models completely
- Sensible defaults - UTC datetimes, UUIDs, timestamps by default
- Dataclass-friendly - Clean instantiation and repr
- Dialect-aware - Use the best type for each database
- Minimal magic - Clear, explicit behavior
Migration from impl/auth.py
If you previously imported models from belgie_alchemy.impl.auth:
Before:
from belgie_alchemy.impl.auth import User, Account, Session, OAuthState
After:
# Copy models from examples/alchemy/auth_models.py to your project
# Then import from your own code:
from myapp.models import User, Account, Session, OAuthState
This gives you full control to customize the models for your application.
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 belgie_alchemy-0.1.0.tar.gz.
File metadata
- Download URL: belgie_alchemy-0.1.0.tar.gz
- Upload date:
- Size: 18.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.28 {"installer":{"name":"uv","version":"0.9.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
06e86282100d60411b125c64ee6802764f2bf085b934ffb786ce85906a3582f6
|
|
| MD5 |
a0f47568adb97076e8690247017154ba
|
|
| BLAKE2b-256 |
dbab6d6cbf3e4573098ee8ccb77b24d12b6bb27ed120125d3f9629082a33dd63
|
File details
Details for the file belgie_alchemy-0.1.0-py3-none-any.whl.
File metadata
- Download URL: belgie_alchemy-0.1.0-py3-none-any.whl
- Upload date:
- Size: 28.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.28 {"installer":{"name":"uv","version":"0.9.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
167fa0eb4e4f677a29fe6f337f8b4cc300da48ff70d5a1a9d88900a500f5b1c5
|
|
| MD5 |
98461a9eb882d101615db1e8bfc6539f
|
|
| BLAKE2b-256 |
756dcf16e3d50ed0fa8757fc00b1af64aea62e8bcb93dfefb7326e347e286afb
|