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
- Quickstart
- Tests
- Architecture
- Integration in the Suite
- Dependencies
- Versioning & Compatibility Policy
- License
- About us
- Known Limitations & Roadmap
- Status & Compatibility
- Useful Links
- Migration Notes
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 byget_entity_name(), which callsstring_to_snake_case(cls.__name__). A class namedUserAccountmaps to the tableuser_accountwith no annotation needed. - Primary key —
idis aBigIntegerwithautoincrement=True. Awith_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 typedINTEGER PRIMARY KEY, notBIGINT).
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 theAbstractEntitysubclass it owns.find(id)— delegates tosession.get(EntityType, id); returnsNonewhen no session is set.find_one_by(filter_clause)—query().filter(clause).first().query()— returns a rawsession.query(EntityType); calls_require_session()first.save(entity, flush=True, refresh=False)—session.addthensession.commit; optionally refreshes from DB._require_session()— raisesRepositorySessionMissingExceptionwhensessionisNone; 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/dborsqlite:///:memory:.engine_options/session_options— dicts forwarded verbatim tocreate_engine()andsessionmaker().create_session()— returns a freshSessionfrom the cached maker.get_engine()— returns the cached engine (creates it on first call).dispose()— callsengine.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, walksrepository_classeslooking for the class whoseget_entity_type()matches, instantiates it withsession=self.session, stores it, and returns it. RaisesUnknownRepositoryExceptionif nothing matches.get_by_table_name(table_name)— same lookup but keyed onentity_type.get_entity_name()(the snake-case table name).get_classes()— returns a copy ofrepository_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
- Homepage: https://github.com/wexample/python-orm
- Documentation: docs.wexample.com
- Issue Tracker: https://github.com/wexample/python-orm/issues
- Discussions: https://github.com/wexample/python-orm/discussions
- PyPI: pypi.org/project/wexample-orm
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)
| File | Size | Uploaded | |
|---|---|---|---|
| wexample_orm-1.3.2.tar.gz | 15.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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
|