Skip to main content

orm

Version: 1.3.2

wexample-orm provides four SQLAlchemy base classes — AbstractEntity, AbstractRepository, AbstractSessionFactory, and AbstractRepositoriesManager — that impose a Doctrine-style repository pattern on top of SQLAlchemy 2. It targets Python developers in the Wexample stack who connect to PostgreSQL via psycopg and want a consistent, opinionated foundation without rewriting engine, session, and repository boilerplate for every project.

Table of Contents

Installation

pip install wexample-orm

Requires Python >=3.10.

Quickstart

Install from PyPI:

pip install wexample-orm

Define an entity and a repository

Subclass AbstractEntity to get a BIGINT primary key and a table name derived automatically from the class name (User → user). Subclass AbstractRepository and declare which entity it covers:

from sqlalchemy.orm import Mapped, mapped_column
from wexample_orm.entity.abstract_entity import AbstractEntity
from wexample_orm.repository.abstract_repository import AbstractRepository

class User(AbstractEntity):
    name: Mapped[str] = mapped_column()

class UserRepository(AbstractRepository):
    @classmethod
    def get_entity_type(cls) -> type[AbstractEntity]:
        return User

Connect and persist (PostgreSQL)

AbstractSessionFactory wraps create_engine and sessionmaker. Pass a SQLAlchemy URL and call create_session():

from wexample_orm.session.abstract_session_factory import AbstractSessionFactory

factory = AbstractSessionFactory(dsn="postgresql+psycopg://user:password@localhost/mydb")
User.metadata.create_all(factory.get_engine())

session = factory.create_session()
repo = UserRepository(session=session)

user = repo.save(User(name="alice"))
print(user.id)    # 1 — assigned by the database

found = repo.find(user.id)
print(found.name) # alice

save() calls session.commit() by default. Pass flush=False to add the row without committing, or refresh=True to reload the instance from the database after commit.

In-memory session for local development and tests

wexample_orm.testing.in_memory_session creates a disposable SQLite engine — no PostgreSQL required. It creates all tables on entry and disposes the engine on exit:

from wexample_orm.testing import in_memory_session

with in_memory_session(AbstractEntity) as session:
    repo = UserRepository(session=session)
    repo.save(User(name="alice"))

    found = repo.find(1)
    print(found.name)  # alice

Tests

This project uses pytest for testing and pytest-cov for code coverage analysis.

Installation

First, install the required testing dependencies:

.venv/bin/python -m pip install pytest pytest-cov

Basic Usage

Run all tests with coverage:

.venv/bin/python -m pytest --cov --cov-report=html

Common Commands

# Run tests with coverage for a specific module
.venv/bin/python -m pytest --cov=your_module

# Show which lines are not covered
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing

# Generate an HTML coverage report
.venv/bin/python -m pytest --cov=your_module --cov-report=html

# Combine terminal and HTML reports
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing --cov-report=html

# Run specific test file with coverage
.venv/bin/python -m pytest tests/test_file.py --cov=your_module --cov-report=term-missing

Viewing HTML Reports

After generating an HTML report, open htmlcov/index.html in your browser to view detailed line-by-line coverage information.

Coverage Threshold

To enforce a minimum coverage percentage:

.venv/bin/python -m pytest --cov=your_module --cov-fail-under=80

This will cause the test suite to fail if coverage drops below 80%.

Architecture

The package (wexample-orm) is a thin layer on top of SQLAlchemy 2.x. It defines four abstract base classes that projects subclass, two domain exceptions, and a test helper. Nothing is concrete out of the box: the library ships patterns, not implementations.

Entity

src/wexample_orm/entity/abstract_entity.py

AbstractEntity is the SQLAlchemy declarative base for the whole stack. It is decorated with @as_declarative(), so every subclass automatically participates in the same MetaData registry.

Two conventions are fixed here and nowhere else:

  • Table name — __tablename__ is set by get_entity_name(), which calls string_to_snake_case(cls.__name__). A class named UserAccount maps to the table user_account with no annotation needed.
  • Primary key — id is a BigInteger with autoincrement=True. A with_variant(Integer(), "sqlite") is bolted on so that the same declaration works in the in-memory SQLite sessions used during testing (SQLite only auto-increments a column typed INTEGER PRIMARY KEY, not BIGINT).

Repository

src/wexample_orm/repository/abstract_repository.py

AbstractRepository is the data-access object. It holds one session field (default None) and exposes:

  • get_entity_type() — abstract classmethod every concrete repository must implement; returns the AbstractEntity subclass it owns.
  • find(id) — delegates to session.get(EntityType, id); returns None when no session is set.
  • find_one_by(filter_clause) — query().filter(clause).first().
  • query() — returns a raw session.query(EntityType); calls _require_session() first.
  • save(entity, flush=True, refresh=False) — session.add then session.commit; optionally refreshes from DB.
  • _require_session() — raises RepositorySessionMissingException when session is None; called by any method that cannot function without it.

A repository can be constructed standalone (session-less find returns None, everything else raises) or injected with a session directly.

Session factory

src/wexample_orm/session/abstract_session_factory.py

AbstractSessionFactory builds and caches a SQLAlchemy engine and sessionmaker from a DSN string. The engine is created on first call to get_engine() and reused thereafter; the sessionmaker is created on first call to create_session().

Public API:

  • dsn — SQLAlchemy URL, e.g. postgresql+psycopg://user:pwd@host/db or sqlite:///:memory:.
  • engine_options / session_options — dicts forwarded verbatim to create_engine() and sessionmaker().
  • create_session() — returns a fresh Session from the cached maker.
  • get_engine() — returns the cached engine (creates it on first call).
  • dispose() — calls engine.dispose() and resets both caches; intended for test teardown.
  • _engine_kwargs() / _session_kwargs() — overridable hooks; by default they just copy the option dicts.

The session factory is decoupled from the rest: nothing in the repository or manager imports it. Callers create a session through it and pass it in manually.

Repositories manager

src/wexample_orm/common/abstract_repositories_manager.py

AbstractRepositoriesManager is a registry that maps entity types to repository instances. It is the single entry point when a project has multiple repositories and wants to avoid constructing them by hand.

Fields:

  • repository_classes — the list of repository classes the manager may instantiate.
  • session — the SQLAlchemy session shared across every repository the manager creates.
  • _instances — private cache (entity_type → repository instance); a repository is built once and reused.

Methods:

  • get(entity_type) — looks up the cache; on miss, walks repository_classes looking for the class whose get_entity_type() matches, instantiates it with session=self.session, stores it, and returns it. Raises UnknownRepositoryException if nothing matches.
  • get_by_table_name(table_name) — same lookup but keyed on entity_type.get_entity_name() (the snake-case table name).
  • get_classes() — returns a copy of repository_classes.

Call path

A typical read goes: caller asks the manager for a repository (manager.get(User)) → manager instantiates UserRepository(session=session) if not cached → caller calls repo.find(42) → repository calls session.get(User, 42) → SQLAlchemy issues the SQL against the engine that the session factory built.

A write follows the same path through repo.save(entity), which calls session.add then session.commit.

Exceptions

src/wexample_orm/exception/unknown_repository_exception.py

UnknownRepositoryException (error code ORM_UNKNOWN_REPOSITORY) is raised by the manager when get() receives an entity type for which no repository class is registered.

src/wexample_orm/exception/repository_session_missing_exception.py

RepositorySessionMissingException (error code ORM_REPOSITORY_SESSION_MISSING) is raised by AbstractRepository._require_session() when a session-dependent method is called on a repository that has session=None.

Both extend UndefinedException from wexample-helpers and carry a structured data dict alongside the human-readable message.

Testing helper

src/wexample_orm/testing/sqlite.py

in_memory_session(declarative_base) is a context manager that creates a fresh SQLite in-memory engine, runs metadata.create_all() on it, yields a session, then closes the session and disposes the engine. The declarative_base argument must be the @as_declarative()-decorated class whose MetaData holds the tables to create — in practice AbstractEntity or a project subclass of it.

from wexample_orm.testing import in_memory_session
from wexample_orm.entity.abstract_entity import AbstractEntity

with in_memory_session(AbstractEntity) as session:
    repo = UserRepository(session=session)
    repo.save(User(name="alice"))
    assert repo.find(1).name == "alice"

The helper is excluded from the published wheel (tool.setuptools.packages.find omits wexample_orm.testing*) and is only available in a development install.

Source layout

src/wexample_orm/
    __init__.py                        # re-exports the three main abstractions
    entity/abstract_entity.py          # declarative base + PK convention
    repository/abstract_repository.py  # data-access object
    session/abstract_session_factory.py# engine + sessionmaker cache
    common/abstract_repositories_manager.py  # repository registry
    exception/
        unknown_repository_exception.py
        repository_session_missing_exception.py
    testing/sqlite.py                  # in-memory SQLite helper

Integration in the Suite

This package is part of the Wexample Suite — a collection of high-quality, modular tools designed to work seamlessly together across multiple languages and environments.

Related Packages

The suite includes packages for configuration management, file handling, prompts, and more. Each package can be used independently or as part of the integrated suite.

Visit the Wexample Suite documentation for the complete package ecosystem.

Dependencies

  • attrs: >=23.1.0
  • psycopg: >=3.2
  • sqlalchemy: <3,>=2
  • wexample-helpers: >=19.1.0

Versioning & Compatibility Policy

Wexample packages follow Semantic Versioning (SemVer):

  • MAJOR: Breaking changes
  • MINOR: New features, backward compatible
  • PATCH: Bug fixes, backward compatible

We maintain backward compatibility within major versions and provide clear migration guides for breaking changes.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Free to use in both personal and commercial projects.

About us

Wexample stands as a cornerstone of the digital ecosystem — a collective of seasoned engineers, researchers, and creators driven by a relentless pursuit of technological excellence. More than a media platform, it has grown into a vibrant community where innovation meets craftsmanship, and where every line of code reflects a commitment to clarity, durability, and shared intelligence.

This packages suite embodies this spirit. Trusted by professionals and enthusiasts alike, it delivers a consistent, high-quality foundation for modern development — open, elegant, and battle-tested. Its reputation is built on years of collaboration, refinement, and rigorous attention to detail, making it a natural choice for those who demand both robustness and beauty in their tools.

Wexample cultivates a culture of mastery. Each package, each contribution carries the mark of a community that values precision, ethics, and innovation — a community proud to shape the future of digital craftsmanship.

Known Limitations & Roadmap

Current limitations and planned features are tracked in the GitHub issues.

See the project roadmap for upcoming features and improvements.

Status & Compatibility

Maturity: Production-ready

Python Support: >=3.10

OS Support: Linux, macOS, Windows

Status: Actively maintained

Useful Links

Migration Notes

When upgrading between major versions, refer to the migration guides in the documentation.

Breaking changes are clearly documented with upgrade paths and examples.

Metadata

Release files for wexample-orm 1.3.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for wexample-orm 1.3.2
File Size Uploaded
wexample_orm-1.3.2.tar.gz 15.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for wexample-orm 1.3.2
File Interpreter ABI Platform
wexample_orm-1.3.2-py3-none-any.whl Python 3 none any Details

Total release size: 29.4 kB

Release files / wexample_orm-1.3.2.tar.gz

Download URL wexample_orm-1.3.2.tar.gz
Size 15.5 kB
Tags Source
SHA-256 checksum
How to use checksums
0727d3a93b44bf8005e1c37ece03b1371bab4a05bcc7bbabdae0cdc1b12b9b80
BLAKE2b-256 checksum
How to use checksums
a7a8eb46e2e5640ab0284bffe4ec5a5d38ba0c97e730b81c63712f69f46e981a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via pdm/2.25.9 CPython/3.12.3 Linux/6.8.0-138-generic

Release files / wexample_orm-1.3.2-py3-none-any.whl

Download URL wexample_orm-1.3.2-py3-none-any.whl
Size 13.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5efbb7e61ccba25c59955e4119168857ef9ddc90fbf892c55af368987ed88d6f
BLAKE2b-256 checksum
How to use checksums
9cdd9aed6949afada4ab51eba78a2651b93848d4f8867e5dafc38e4d9e3ff5e1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via pdm/2.25.9 CPython/3.12.3 Linux/6.8.0-138-generic

Release history Release notifications | RSS feed

1.4.2

2 release files

1.4.1

2 release files

1.4.0

2 release files

1.3.3

2 release files

This release

1.3.2 This release

2 release files

1.3.1

2 release files

1.3.0

2 release files

1.2.3

2 release files

1.2.2

2 release files

1.2.1

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.5

2 release files

1.0.4

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.0.6

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page