Skip to main content

SQLAlchemy building blocks for Belgie

Project description

belgie-alchemy

SQLAlchemy 2.0 utilities for Belgie.

Overview

belgie-alchemy provides the BelgieAdapter and auth model mixins for Belgie. For SQLAlchemy building blocks (Base, low-level mixins, types), use brussels:

  • Base: Declarative base with dataclass mapping and sensible defaults
  • Mixins: PrimaryKeyMixin (UUID), TimestampMixin (created/updated/deleted timestamps)
  • Types: DateTimeUTC (timezone-aware datetimes), Json (dialect-specific JSON storage)

The examples below keep model ownership in your app while reducing boilerplate.

Quick Start

from datetime import datetime
from brussels.base import DataclassBase
from brussels.mixins import PrimaryKeyMixin, TimestampMixin
from brussels.types import DateTimeUTC
from sqlalchemy.orm import Mapped, mapped_column

class Article(DataclassBase, 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_at timestamps
  • Timezone-aware datetime handling
  • Dataclass-style __init__, __repr__, __eq__

Building Blocks

Base

Declarative base with dataclass mapping enabled:

from brussels.base import DataclassBase
from sqlalchemy.orm import Mapped, mapped_column

class MyModel(DataclassBase):
    __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 (datetimeDateTimeUTC)
  • Dataclass mapping for convenient instantiation

Mixins

PrimaryKeyMixin

Adds a UUID primary key with server-side generation:

from brussels.base import DataclassBase
from brussels.mixins import PrimaryKeyMixin

class MyModel(DataclassBase, 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 brussels.base import DataclassBase
from brussels.mixins import TimestampMixin

class MyModel(DataclassBase, 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_at set automatically on insert
  • updated_at auto-updates on row changes
  • deleted_at for soft deletion
  • mark_deleted() method to set deleted_at

Types

DateTimeUTC

Timezone-aware datetime storage:

from datetime import datetime
from brussels.base import DataclassBase
from brussels.types import DateTimeUTC
from sqlalchemy.orm import Mapped, mapped_column

class Event(DataclassBase):
    __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

Json

Dialect-specific JSON storage (JSONB on PostgreSQL):

from brussels.base import DataclassBase
from brussels.types import Json
from sqlalchemy.orm import Mapped, mapped_column

class User(DataclassBase):
    __tablename__ = "users"

    id: Mapped[int] = mapped_column(primary_key=True)
    # Store arbitrary structured data as JSON (works everywhere)
    metadata: Mapped[dict[str, str] | None] = mapped_column("metadata", Json, default=None)

Features:

  • PostgreSQL: Uses JSONB
  • SQLite/MySQL: Uses JSON

Belgie's auth mixins do not use Json for User.scopes on PostgreSQL. They default to text[] on PostgreSQL and fall back to Json on other dialects.

If your application uses a scope enum, override UserMixin.scopes with an enum array:

from enum import StrEnum
from brussels.base import DataclassBase
from sqlalchemy import Enum
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.orm import Mapped, mapped_column

class AppScope(StrEnum):
    READ = "resource:read"
    WRITE = "resource:write"
    ADMIN = "admin"

class User(DataclassBase):
    __tablename__ = "users"

    id: Mapped[int] = mapped_column(primary_key=True)
    # PostgreSQL native enum array for list[AppScope]
    scopes: Mapped[list[AppScope]] = mapped_column(
        ARRAY(Enum(AppScope, name="app_scope")),
        default_factory=list,
        nullable=False,
    )

Use ARRAY(Enum(...)) for list[AppScope] storage. A bare Enum(AppScope) column is scalar and will not store a scope list.

Auth Model Mixins

Use the built-in auth mixins for a minimal model setup:

from brussels.base import DataclassBase
from belgie_alchemy import AccountMixin, OAuthStateMixin, SessionMixin, UserMixin

class User(DataclassBase, UserMixin):
    pass

class Account(DataclassBase, AccountMixin):
    pass

class Session(DataclassBase, SessionMixin):
    pass

class OAuthState(DataclassBase, OAuthStateMixin):
    pass

Defaults include:

  • User email/profile fields and dialect-aware scopes (text[] on PostgreSQL, Json elsewhere)
  • Account provider linkage fields and uniqueness constraint
  • Session expiration and metadata fields
  • OAuth state PKCE fields and optional user linkage
  • UUID primary keys and timestamps on all models
  • PostgreSQL CITEXT variants for case-insensitive email, provider, provider_account_id, slug, and invitation email

For PostgreSQL deployments, ensure the citext extension is installed when using the default mixins.

If you already use the default mixins on PostgreSQL, migrate existing app-owned varchar auth and organization columns to text in your own app migration where needed. If you also created user.scopes as jsonb, migrate that column to text[], backfill any NULL rows to [], and then enforce NOT NULL as part of the same application migration. Belgie does not ship Alembic migrations for application tables.

You can still override any field, relationship, or __tablename__ in your concrete model classes.

See examples/alchemy/auth_models.py for a complete reference implementation.

Design Principles

  1. Building blocks, not frameworks - You own your models completely
  2. Sensible defaults - UTC datetimes, UUIDs, timestamps by default
  3. Dataclass-friendly - Clean instantiation and repr
  4. Dialect-aware - Use the best type for each database
  5. 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:

# Build your own concrete classes from mixins:
from brussels.base import DataclassBase
from belgie_alchemy import AccountMixin, OAuthStateMixin, SessionMixin, UserMixin

class User(DataclassBase, UserMixin): ...
class Account(DataclassBase, AccountMixin): ...
class Session(DataclassBase, SessionMixin): ...
class OAuthState(DataclassBase, OAuthStateMixin): ...

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

belgie_alchemy-0.10.4.tar.gz (8.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

belgie_alchemy-0.10.4-py3-none-any.whl (14.7 kB view details)

Uploaded Python 3

File details

Details for the file belgie_alchemy-0.10.4.tar.gz.

File metadata

  • Download URL: belgie_alchemy-0.10.4.tar.gz
  • Upload date:
  • Size: 8.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for belgie_alchemy-0.10.4.tar.gz
Algorithm Hash digest
SHA256 395f2414e2c585b208325459dbec9aafdb842c72f05b96b4cbc93955d937abec
MD5 b2cd67c1328c41273abc88c08dcd2337
BLAKE2b-256 fc360c21f86a86dec388185e27e3748ccf343ee8065e1bc3abf8c47f694d6418

See more details on using hashes here.

File details

Details for the file belgie_alchemy-0.10.4-py3-none-any.whl.

File metadata

  • Download URL: belgie_alchemy-0.10.4-py3-none-any.whl
  • Upload date:
  • Size: 14.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for belgie_alchemy-0.10.4-py3-none-any.whl
Algorithm Hash digest
SHA256 f10c80fa207cd97b42b2511a04c86d4972842b9fe0e9b92ae65ae0510c74d7e2
MD5 2a0e504c8de4f2d31724672e27e6c92a
BLAKE2b-256 1afca31e74e57500c8cdd8a025f2fb62b3281c784c24bfac2ecb070d41893b84

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page