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.3.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.3-py3-none-any.whl (14.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: belgie_alchemy-0.10.3.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.3.tar.gz
Algorithm Hash digest
SHA256 cad2110d064f82c58f5d6f9c8b23e87fb49d0c8902c9620249f6d601e1d01792
MD5 c05ec434822dd04d6a7f5c524d9d20c8
BLAKE2b-256 1ca31cc0ef76d9b7d7539d83ab801b474ddf79f68cd028d0609beaa636edbfea

See more details on using hashes here.

File details

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

File metadata

  • Download URL: belgie_alchemy-0.10.3-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.3-py3-none-any.whl
Algorithm Hash digest
SHA256 d6f80bd47405c48e633dede3a6469626f98ac3ec6741cf0519a924ceab9800d4
MD5 262513c3bece574f475e17daac42477f
BLAKE2b-256 0ad6c52afe25e2477a3f927cae3441df4d755db48af5d0950757941145365c9b

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