wexample-orm 1.3.1__tar.gz → 1.3.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. wexample_orm-1.3.3/PKG-INFO +348 -0
  2. wexample_orm-1.3.3/README.md +328 -0
  3. {wexample_orm-1.3.1 → wexample_orm-1.3.3}/pyproject.toml +3 -3
  4. wexample_orm-1.3.1/tests/__init__.py → wexample_orm-1.3.3/tests/.gitkeep +0 -0
  5. wexample_orm-1.3.3/tests/__init__.py +0 -0
  6. wexample_orm-1.3.1/PKG-INFO +0 -172
  7. wexample_orm-1.3.1/README.md +0 -152
  8. {wexample_orm-1.3.1 → wexample_orm-1.3.3}/src/wexample_orm/__init__.py +0 -0
  9. {wexample_orm-1.3.1 → wexample_orm-1.3.3}/src/wexample_orm/common/__init__.py +0 -0
  10. {wexample_orm-1.3.1 → wexample_orm-1.3.3}/src/wexample_orm/common/abstract_repositories_manager.py +0 -0
  11. {wexample_orm-1.3.1 → wexample_orm-1.3.3}/src/wexample_orm/entity/__init__.py +0 -0
  12. {wexample_orm-1.3.1 → wexample_orm-1.3.3}/src/wexample_orm/entity/abstract_entity.py +0 -0
  13. {wexample_orm-1.3.1 → wexample_orm-1.3.3}/src/wexample_orm/exception/__init__.py +0 -0
  14. {wexample_orm-1.3.1 → wexample_orm-1.3.3}/src/wexample_orm/exception/repository_session_missing_exception.py +0 -0
  15. {wexample_orm-1.3.1 → wexample_orm-1.3.3}/src/wexample_orm/exception/unknown_repository_exception.py +0 -0
  16. {wexample_orm-1.3.1 → wexample_orm-1.3.3}/src/wexample_orm/py.typed +0 -0
  17. {wexample_orm-1.3.1 → wexample_orm-1.3.3}/src/wexample_orm/repository/__init__.py +0 -0
  18. {wexample_orm-1.3.1 → wexample_orm-1.3.3}/src/wexample_orm/repository/abstract_repository.py +0 -0
  19. {wexample_orm-1.3.1 → wexample_orm-1.3.3}/src/wexample_orm/session/__init__.py +0 -0
  20. {wexample_orm-1.3.1 → wexample_orm-1.3.3}/src/wexample_orm/session/abstract_session_factory.py +0 -0
  21. {wexample_orm-1.3.1 → wexample_orm-1.3.3}/src/wexample_orm/testing/__init__.py +0 -0
  22. {wexample_orm-1.3.1 → wexample_orm-1.3.3}/src/wexample_orm/testing/sqlite.py +0 -0
  23. {wexample_orm-1.3.1 → wexample_orm-1.3.3}/tests/test_abstract.py +0 -0
@@ -0,0 +1,348 @@
1
+ Metadata-Version: 2.1
2
+ Name: wexample-orm
3
+ Version: 1.3.3
4
+ Summary: Provides SQLAlchemy ORM abstractions — base entity, repository, session factory, and repository manager — with PostgreSQL-first defaults via psycopg
5
+ Author-Email: weeger <contact@wexample.com>
6
+ License: MIT
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Project-URL: homepage, https://github.com/wexample/python-orm
11
+ Requires-Python: >=3.10
12
+ Requires-Dist: attrs>=23.1.0
13
+ Requires-Dist: psycopg[binary]>=3.2
14
+ Requires-Dist: sqlalchemy<3,>=2
15
+ Requires-Dist: wexample-helpers>=20.0.0
16
+ Provides-Extra: dev
17
+ Requires-Dist: pytest; extra == "dev"
18
+ Requires-Dist: pytest-cov; extra == "dev"
19
+ Description-Content-Type: text/markdown
20
+
21
+ # orm
22
+
23
+ Version: 1.3.3
24
+
25
+ `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.
26
+
27
+ ## Table of Contents
28
+
29
+ - [Installation](#installation)
30
+ - [Quickstart](#quickstart)
31
+ - [Tests](#tests)
32
+ - [Architecture](#architecture)
33
+ - [Integration in the Suite](#integration-in-the-suite)
34
+ - [Dependencies](#dependencies)
35
+ - [Versioning & Compatibility Policy](#versioning--compatibility-policy)
36
+ - [License](#license)
37
+ - [About us](#about-us)
38
+ - [Known Limitations & Roadmap](#known-limitations--roadmap)
39
+ - [Status & Compatibility](#status--compatibility)
40
+ - [Useful Links](#useful-links)
41
+ - [Migration Notes](#migration-notes)
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ pip install wexample-orm
47
+ ```
48
+
49
+ Requires Python >=3.10.
50
+
51
+ ## Quickstart
52
+
53
+ Install from PyPI:
54
+
55
+ ```bash
56
+ pip install wexample-orm
57
+ ```
58
+
59
+ ### Define an entity and a repository
60
+
61
+ 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:
62
+
63
+ ```python
64
+ from sqlalchemy.orm import Mapped, mapped_column
65
+ from wexample_orm.entity.abstract_entity import AbstractEntity
66
+ from wexample_orm.repository.abstract_repository import AbstractRepository
67
+
68
+ class User(AbstractEntity):
69
+ name: Mapped[str] = mapped_column()
70
+
71
+ class UserRepository(AbstractRepository):
72
+ @classmethod
73
+ def get_entity_type(cls) -> type[AbstractEntity]:
74
+ return User
75
+ ```
76
+
77
+ ### Connect and persist (PostgreSQL)
78
+
79
+ `AbstractSessionFactory` wraps `create_engine` and `sessionmaker`. Pass a SQLAlchemy URL and call `create_session()`:
80
+
81
+ ```python
82
+ from wexample_orm.session.abstract_session_factory import AbstractSessionFactory
83
+
84
+ factory = AbstractSessionFactory(dsn="postgresql+psycopg://user:password@localhost/mydb")
85
+ User.metadata.create_all(factory.get_engine())
86
+
87
+ session = factory.create_session()
88
+ repo = UserRepository(session=session)
89
+
90
+ user = repo.save(User(name="alice"))
91
+ print(user.id) # 1 — assigned by the database
92
+
93
+ found = repo.find(user.id)
94
+ print(found.name) # alice
95
+ ```
96
+
97
+ `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.
98
+
99
+ ### In-memory session for local development and tests
100
+
101
+ `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:
102
+
103
+ ```python
104
+ from wexample_orm.testing import in_memory_session
105
+
106
+ with in_memory_session(AbstractEntity) as session:
107
+ repo = UserRepository(session=session)
108
+ repo.save(User(name="alice"))
109
+
110
+ found = repo.find(1)
111
+ print(found.name) # alice
112
+ ```
113
+
114
+ ## Tests
115
+
116
+ This project uses `pytest` for testing and `pytest-cov` for code coverage analysis.
117
+
118
+ ### Installation
119
+
120
+ First, install the required testing dependencies:
121
+ ```bash
122
+ .venv/bin/python -m pip install pytest pytest-cov
123
+ ```
124
+
125
+ ### Basic Usage
126
+
127
+ Run all tests with coverage:
128
+ ```bash
129
+ .venv/bin/python -m pytest --cov --cov-report=html
130
+ ```
131
+
132
+ ### Common Commands
133
+ ```bash
134
+ # Run tests with coverage for a specific module
135
+ .venv/bin/python -m pytest --cov=your_module
136
+
137
+ # Show which lines are not covered
138
+ .venv/bin/python -m pytest --cov=your_module --cov-report=term-missing
139
+
140
+ # Generate an HTML coverage report
141
+ .venv/bin/python -m pytest --cov=your_module --cov-report=html
142
+
143
+ # Combine terminal and HTML reports
144
+ .venv/bin/python -m pytest --cov=your_module --cov-report=term-missing --cov-report=html
145
+
146
+ # Run specific test file with coverage
147
+ .venv/bin/python -m pytest tests/test_file.py --cov=your_module --cov-report=term-missing
148
+ ```
149
+
150
+ ### Viewing HTML Reports
151
+
152
+ After generating an HTML report, open `htmlcov/index.html` in your browser to view detailed line-by-line coverage information.
153
+
154
+ ### Coverage Threshold
155
+
156
+ To enforce a minimum coverage percentage:
157
+ ```bash
158
+ .venv/bin/python -m pytest --cov=your_module --cov-fail-under=80
159
+ ```
160
+
161
+ This will cause the test suite to fail if coverage drops below 80%.
162
+
163
+ ## Architecture
164
+
165
+ 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.
166
+
167
+ ### Entity
168
+
169
+ src/wexample_orm/entity/abstract_entity.py
170
+
171
+ `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.
172
+
173
+ Two conventions are fixed here and nowhere else:
174
+
175
+ - **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.
176
+ - **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`).
177
+
178
+ ### Repository
179
+
180
+ src/wexample_orm/repository/abstract_repository.py
181
+
182
+ `AbstractRepository` is the data-access object. It holds one `session` field (default `None`) and exposes:
183
+
184
+ - `get_entity_type()` — abstract classmethod every concrete repository must implement; returns the `AbstractEntity` subclass it owns.
185
+ - `find(id)` — delegates to `session.get(EntityType, id)`; returns `None` when no session is set.
186
+ - `find_one_by(filter_clause)` — `query().filter(clause).first()`.
187
+ - `query()` — returns a raw `session.query(EntityType)`; calls `_require_session()` first.
188
+ - `save(entity, flush=True, refresh=False)` — `session.add` then `session.commit`; optionally refreshes from DB.
189
+ - `_require_session()` — raises `RepositorySessionMissingException` when `session` is `None`; called by any method that cannot function without it.
190
+
191
+ A repository can be constructed standalone (session-less `find` returns `None`, everything else raises) or injected with a session directly.
192
+
193
+ ### Session factory
194
+
195
+ src/wexample_orm/session/abstract_session_factory.py
196
+
197
+ `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()`.
198
+
199
+ Public API:
200
+
201
+ - `dsn` — SQLAlchemy URL, e.g. `postgresql+psycopg://user:pwd@host/db` or `sqlite:///:memory:`.
202
+ - `engine_options` / `session_options` — dicts forwarded verbatim to `create_engine()` and `sessionmaker()`.
203
+ - `create_session()` — returns a fresh `Session` from the cached maker.
204
+ - `get_engine()` — returns the cached engine (creates it on first call).
205
+ - `dispose()` — calls `engine.dispose()` and resets both caches; intended for test teardown.
206
+ - `_engine_kwargs()` / `_session_kwargs()` — overridable hooks; by default they just copy the option dicts.
207
+
208
+ 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.
209
+
210
+ ### Repositories manager
211
+
212
+ src/wexample_orm/common/abstract_repositories_manager.py
213
+
214
+ `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.
215
+
216
+ Fields:
217
+
218
+ - `repository_classes` — the list of repository classes the manager may instantiate.
219
+ - `session` — the SQLAlchemy session shared across every repository the manager creates.
220
+ - `_instances` — private cache (`entity_type → repository instance`); a repository is built once and reused.
221
+
222
+ Methods:
223
+
224
+ - `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.
225
+ - `get_by_table_name(table_name)` — same lookup but keyed on `entity_type.get_entity_name()` (the snake-case table name).
226
+ - `get_classes()` — returns a copy of `repository_classes`.
227
+
228
+ ### Call path
229
+
230
+ 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.
231
+
232
+ A write follows the same path through `repo.save(entity)`, which calls `session.add` then `session.commit`.
233
+
234
+ ### Exceptions
235
+
236
+ src/wexample_orm/exception/unknown_repository_exception.py
237
+
238
+ `UnknownRepositoryException` (error code `ORM_UNKNOWN_REPOSITORY`) is raised by the manager when `get()` receives an entity type for which no repository class is registered.
239
+
240
+ src/wexample_orm/exception/repository_session_missing_exception.py
241
+
242
+ `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`.
243
+
244
+ Both extend `UndefinedException` from `wexample-helpers` and carry a structured `data` dict alongside the human-readable message.
245
+
246
+ ### Testing helper
247
+
248
+ src/wexample_orm/testing/sqlite.py
249
+
250
+ `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.
251
+
252
+ ```python
253
+ from wexample_orm.testing import in_memory_session
254
+ from wexample_orm.entity.abstract_entity import AbstractEntity
255
+
256
+ with in_memory_session(AbstractEntity) as session:
257
+ repo = UserRepository(session=session)
258
+ repo.save(User(name="alice"))
259
+ assert repo.find(1).name == "alice"
260
+ ```
261
+
262
+ The helper is excluded from the published wheel (`tool.setuptools.packages.find` omits `wexample_orm.testing*`) and is only available in a development install.
263
+
264
+ ### Source layout
265
+
266
+ ```
267
+ src/wexample_orm/
268
+ __init__.py # re-exports the three main abstractions
269
+ entity/abstract_entity.py # declarative base + PK convention
270
+ repository/abstract_repository.py # data-access object
271
+ session/abstract_session_factory.py# engine + sessionmaker cache
272
+ common/abstract_repositories_manager.py # repository registry
273
+ exception/
274
+ unknown_repository_exception.py
275
+ repository_session_missing_exception.py
276
+ testing/sqlite.py # in-memory SQLite helper
277
+ ```
278
+
279
+ ## Integration in the Suite
280
+
281
+ 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.
282
+
283
+ ### Related Packages
284
+
285
+ 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.
286
+
287
+ Visit the [Wexample Suite documentation](https://docs.wexample.com) for the complete package ecosystem.
288
+
289
+ ## Dependencies
290
+
291
+ - attrs: >=23.1.0
292
+ - psycopg: >=3.2
293
+ - sqlalchemy: <3,>=2
294
+ - wexample-helpers: >=20.0.0
295
+
296
+ ## Versioning & Compatibility Policy
297
+
298
+ Wexample packages follow **Semantic Versioning** (SemVer):
299
+
300
+ - **MAJOR**: Breaking changes
301
+ - **MINOR**: New features, backward compatible
302
+ - **PATCH**: Bug fixes, backward compatible
303
+
304
+ We maintain backward compatibility within major versions and provide clear migration guides for breaking changes.
305
+
306
+ ## License
307
+
308
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
309
+
310
+ Free to use in both personal and commercial projects.
311
+
312
+ ## About us
313
+
314
+ [Wexample](https://wexample.com) 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.
315
+
316
+ 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.
317
+
318
+ 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.
319
+
320
+ ## Known Limitations & Roadmap
321
+
322
+ Current limitations and planned features are tracked in the GitHub issues.
323
+
324
+ See the [project roadmap](https://github.com/wexample/python-orm/issues) for upcoming features and improvements.
325
+
326
+ ## Status & Compatibility
327
+
328
+ **Maturity**: Production-ready
329
+
330
+ **Python Support**: >=3.10
331
+
332
+ **OS Support**: Linux, macOS, Windows
333
+
334
+ **Status**: Actively maintained
335
+
336
+ ## Useful Links
337
+
338
+ - **Homepage**: https://github.com/wexample/python-orm
339
+ - **Documentation**: [docs.wexample.com](https://docs.wexample.com)
340
+ - **Issue Tracker**: https://github.com/wexample/python-orm/issues
341
+ - **Discussions**: https://github.com/wexample/python-orm/discussions
342
+ - **PyPI**: [pypi.org/project/wexample-orm](https://pypi.org/project/wexample-orm/)
343
+
344
+ ## Migration Notes
345
+
346
+ When upgrading between major versions, refer to the migration guides in the documentation.
347
+
348
+ Breaking changes are clearly documented with upgrade paths and examples.
@@ -0,0 +1,328 @@
1
+ # orm
2
+
3
+ Version: 1.3.3
4
+
5
+ `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.
6
+
7
+ ## Table of Contents
8
+
9
+ - [Installation](#installation)
10
+ - [Quickstart](#quickstart)
11
+ - [Tests](#tests)
12
+ - [Architecture](#architecture)
13
+ - [Integration in the Suite](#integration-in-the-suite)
14
+ - [Dependencies](#dependencies)
15
+ - [Versioning & Compatibility Policy](#versioning--compatibility-policy)
16
+ - [License](#license)
17
+ - [About us](#about-us)
18
+ - [Known Limitations & Roadmap](#known-limitations--roadmap)
19
+ - [Status & Compatibility](#status--compatibility)
20
+ - [Useful Links](#useful-links)
21
+ - [Migration Notes](#migration-notes)
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ pip install wexample-orm
27
+ ```
28
+
29
+ Requires Python >=3.10.
30
+
31
+ ## Quickstart
32
+
33
+ Install from PyPI:
34
+
35
+ ```bash
36
+ pip install wexample-orm
37
+ ```
38
+
39
+ ### Define an entity and a repository
40
+
41
+ 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:
42
+
43
+ ```python
44
+ from sqlalchemy.orm import Mapped, mapped_column
45
+ from wexample_orm.entity.abstract_entity import AbstractEntity
46
+ from wexample_orm.repository.abstract_repository import AbstractRepository
47
+
48
+ class User(AbstractEntity):
49
+ name: Mapped[str] = mapped_column()
50
+
51
+ class UserRepository(AbstractRepository):
52
+ @classmethod
53
+ def get_entity_type(cls) -> type[AbstractEntity]:
54
+ return User
55
+ ```
56
+
57
+ ### Connect and persist (PostgreSQL)
58
+
59
+ `AbstractSessionFactory` wraps `create_engine` and `sessionmaker`. Pass a SQLAlchemy URL and call `create_session()`:
60
+
61
+ ```python
62
+ from wexample_orm.session.abstract_session_factory import AbstractSessionFactory
63
+
64
+ factory = AbstractSessionFactory(dsn="postgresql+psycopg://user:password@localhost/mydb")
65
+ User.metadata.create_all(factory.get_engine())
66
+
67
+ session = factory.create_session()
68
+ repo = UserRepository(session=session)
69
+
70
+ user = repo.save(User(name="alice"))
71
+ print(user.id) # 1 — assigned by the database
72
+
73
+ found = repo.find(user.id)
74
+ print(found.name) # alice
75
+ ```
76
+
77
+ `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.
78
+
79
+ ### In-memory session for local development and tests
80
+
81
+ `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:
82
+
83
+ ```python
84
+ from wexample_orm.testing import in_memory_session
85
+
86
+ with in_memory_session(AbstractEntity) as session:
87
+ repo = UserRepository(session=session)
88
+ repo.save(User(name="alice"))
89
+
90
+ found = repo.find(1)
91
+ print(found.name) # alice
92
+ ```
93
+
94
+ ## Tests
95
+
96
+ This project uses `pytest` for testing and `pytest-cov` for code coverage analysis.
97
+
98
+ ### Installation
99
+
100
+ First, install the required testing dependencies:
101
+ ```bash
102
+ .venv/bin/python -m pip install pytest pytest-cov
103
+ ```
104
+
105
+ ### Basic Usage
106
+
107
+ Run all tests with coverage:
108
+ ```bash
109
+ .venv/bin/python -m pytest --cov --cov-report=html
110
+ ```
111
+
112
+ ### Common Commands
113
+ ```bash
114
+ # Run tests with coverage for a specific module
115
+ .venv/bin/python -m pytest --cov=your_module
116
+
117
+ # Show which lines are not covered
118
+ .venv/bin/python -m pytest --cov=your_module --cov-report=term-missing
119
+
120
+ # Generate an HTML coverage report
121
+ .venv/bin/python -m pytest --cov=your_module --cov-report=html
122
+
123
+ # Combine terminal and HTML reports
124
+ .venv/bin/python -m pytest --cov=your_module --cov-report=term-missing --cov-report=html
125
+
126
+ # Run specific test file with coverage
127
+ .venv/bin/python -m pytest tests/test_file.py --cov=your_module --cov-report=term-missing
128
+ ```
129
+
130
+ ### Viewing HTML Reports
131
+
132
+ After generating an HTML report, open `htmlcov/index.html` in your browser to view detailed line-by-line coverage information.
133
+
134
+ ### Coverage Threshold
135
+
136
+ To enforce a minimum coverage percentage:
137
+ ```bash
138
+ .venv/bin/python -m pytest --cov=your_module --cov-fail-under=80
139
+ ```
140
+
141
+ This will cause the test suite to fail if coverage drops below 80%.
142
+
143
+ ## Architecture
144
+
145
+ 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.
146
+
147
+ ### Entity
148
+
149
+ src/wexample_orm/entity/abstract_entity.py
150
+
151
+ `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.
152
+
153
+ Two conventions are fixed here and nowhere else:
154
+
155
+ - **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.
156
+ - **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`).
157
+
158
+ ### Repository
159
+
160
+ src/wexample_orm/repository/abstract_repository.py
161
+
162
+ `AbstractRepository` is the data-access object. It holds one `session` field (default `None`) and exposes:
163
+
164
+ - `get_entity_type()` — abstract classmethod every concrete repository must implement; returns the `AbstractEntity` subclass it owns.
165
+ - `find(id)` — delegates to `session.get(EntityType, id)`; returns `None` when no session is set.
166
+ - `find_one_by(filter_clause)` — `query().filter(clause).first()`.
167
+ - `query()` — returns a raw `session.query(EntityType)`; calls `_require_session()` first.
168
+ - `save(entity, flush=True, refresh=False)` — `session.add` then `session.commit`; optionally refreshes from DB.
169
+ - `_require_session()` — raises `RepositorySessionMissingException` when `session` is `None`; called by any method that cannot function without it.
170
+
171
+ A repository can be constructed standalone (session-less `find` returns `None`, everything else raises) or injected with a session directly.
172
+
173
+ ### Session factory
174
+
175
+ src/wexample_orm/session/abstract_session_factory.py
176
+
177
+ `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()`.
178
+
179
+ Public API:
180
+
181
+ - `dsn` — SQLAlchemy URL, e.g. `postgresql+psycopg://user:pwd@host/db` or `sqlite:///:memory:`.
182
+ - `engine_options` / `session_options` — dicts forwarded verbatim to `create_engine()` and `sessionmaker()`.
183
+ - `create_session()` — returns a fresh `Session` from the cached maker.
184
+ - `get_engine()` — returns the cached engine (creates it on first call).
185
+ - `dispose()` — calls `engine.dispose()` and resets both caches; intended for test teardown.
186
+ - `_engine_kwargs()` / `_session_kwargs()` — overridable hooks; by default they just copy the option dicts.
187
+
188
+ 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.
189
+
190
+ ### Repositories manager
191
+
192
+ src/wexample_orm/common/abstract_repositories_manager.py
193
+
194
+ `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.
195
+
196
+ Fields:
197
+
198
+ - `repository_classes` — the list of repository classes the manager may instantiate.
199
+ - `session` — the SQLAlchemy session shared across every repository the manager creates.
200
+ - `_instances` — private cache (`entity_type → repository instance`); a repository is built once and reused.
201
+
202
+ Methods:
203
+
204
+ - `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.
205
+ - `get_by_table_name(table_name)` — same lookup but keyed on `entity_type.get_entity_name()` (the snake-case table name).
206
+ - `get_classes()` — returns a copy of `repository_classes`.
207
+
208
+ ### Call path
209
+
210
+ 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.
211
+
212
+ A write follows the same path through `repo.save(entity)`, which calls `session.add` then `session.commit`.
213
+
214
+ ### Exceptions
215
+
216
+ src/wexample_orm/exception/unknown_repository_exception.py
217
+
218
+ `UnknownRepositoryException` (error code `ORM_UNKNOWN_REPOSITORY`) is raised by the manager when `get()` receives an entity type for which no repository class is registered.
219
+
220
+ src/wexample_orm/exception/repository_session_missing_exception.py
221
+
222
+ `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`.
223
+
224
+ Both extend `UndefinedException` from `wexample-helpers` and carry a structured `data` dict alongside the human-readable message.
225
+
226
+ ### Testing helper
227
+
228
+ src/wexample_orm/testing/sqlite.py
229
+
230
+ `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.
231
+
232
+ ```python
233
+ from wexample_orm.testing import in_memory_session
234
+ from wexample_orm.entity.abstract_entity import AbstractEntity
235
+
236
+ with in_memory_session(AbstractEntity) as session:
237
+ repo = UserRepository(session=session)
238
+ repo.save(User(name="alice"))
239
+ assert repo.find(1).name == "alice"
240
+ ```
241
+
242
+ The helper is excluded from the published wheel (`tool.setuptools.packages.find` omits `wexample_orm.testing*`) and is only available in a development install.
243
+
244
+ ### Source layout
245
+
246
+ ```
247
+ src/wexample_orm/
248
+ __init__.py # re-exports the three main abstractions
249
+ entity/abstract_entity.py # declarative base + PK convention
250
+ repository/abstract_repository.py # data-access object
251
+ session/abstract_session_factory.py# engine + sessionmaker cache
252
+ common/abstract_repositories_manager.py # repository registry
253
+ exception/
254
+ unknown_repository_exception.py
255
+ repository_session_missing_exception.py
256
+ testing/sqlite.py # in-memory SQLite helper
257
+ ```
258
+
259
+ ## Integration in the Suite
260
+
261
+ 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.
262
+
263
+ ### Related Packages
264
+
265
+ 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.
266
+
267
+ Visit the [Wexample Suite documentation](https://docs.wexample.com) for the complete package ecosystem.
268
+
269
+ ## Dependencies
270
+
271
+ - attrs: >=23.1.0
272
+ - psycopg: >=3.2
273
+ - sqlalchemy: <3,>=2
274
+ - wexample-helpers: >=20.0.0
275
+
276
+ ## Versioning & Compatibility Policy
277
+
278
+ Wexample packages follow **Semantic Versioning** (SemVer):
279
+
280
+ - **MAJOR**: Breaking changes
281
+ - **MINOR**: New features, backward compatible
282
+ - **PATCH**: Bug fixes, backward compatible
283
+
284
+ We maintain backward compatibility within major versions and provide clear migration guides for breaking changes.
285
+
286
+ ## License
287
+
288
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
289
+
290
+ Free to use in both personal and commercial projects.
291
+
292
+ ## About us
293
+
294
+ [Wexample](https://wexample.com) 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.
295
+
296
+ 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.
297
+
298
+ 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.
299
+
300
+ ## Known Limitations & Roadmap
301
+
302
+ Current limitations and planned features are tracked in the GitHub issues.
303
+
304
+ See the [project roadmap](https://github.com/wexample/python-orm/issues) for upcoming features and improvements.
305
+
306
+ ## Status & Compatibility
307
+
308
+ **Maturity**: Production-ready
309
+
310
+ **Python Support**: >=3.10
311
+
312
+ **OS Support**: Linux, macOS, Windows
313
+
314
+ **Status**: Actively maintained
315
+
316
+ ## Useful Links
317
+
318
+ - **Homepage**: https://github.com/wexample/python-orm
319
+ - **Documentation**: [docs.wexample.com](https://docs.wexample.com)
320
+ - **Issue Tracker**: https://github.com/wexample/python-orm/issues
321
+ - **Discussions**: https://github.com/wexample/python-orm/discussions
322
+ - **PyPI**: [pypi.org/project/wexample-orm](https://pypi.org/project/wexample-orm/)
323
+
324
+ ## Migration Notes
325
+
326
+ When upgrading between major versions, refer to the migration guides in the documentation.
327
+
328
+ Breaking changes are clearly documented with upgrade paths and examples.
@@ -6,8 +6,8 @@ build-backend = "pdm.backend"
6
6
 
7
7
  [project]
8
8
  name = "wexample-orm"
9
- version = "1.3.1"
10
- description = "ORM abstractions on top of SQLAlchemy, PostgreSQL-first (psycopg bundled)."
9
+ version = "1.3.3"
10
+ description = "Provides SQLAlchemy ORM abstractions — base entity, repository, session factory, and repository manager — with PostgreSQL-first defaults via psycopg"
11
11
  authors = [
12
12
  { name = "weeger", email = "contact@wexample.com" },
13
13
  ]
@@ -21,7 +21,7 @@ dependencies = [
21
21
  "attrs>=23.1.0",
22
22
  "psycopg[binary]>=3.2",
23
23
  "sqlalchemy>=2,<3",
24
- "wexample-helpers>=19.0.0",
24
+ "wexample-helpers>=20.0.0",
25
25
  ]
26
26
 
27
27
  [project.readme]
File without changes
@@ -1,172 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: wexample-orm
3
- Version: 1.3.1
4
- Summary: ORM abstractions on top of SQLAlchemy, PostgreSQL-first (psycopg bundled).
5
- Author-Email: weeger <contact@wexample.com>
6
- License: MIT
7
- Classifier: Programming Language :: Python :: 3
8
- Classifier: License :: OSI Approved :: MIT License
9
- Classifier: Operating System :: OS Independent
10
- Project-URL: homepage, https://github.com/wexample/python-orm
11
- Requires-Python: >=3.10
12
- Requires-Dist: attrs>=23.1.0
13
- Requires-Dist: psycopg[binary]>=3.2
14
- Requires-Dist: sqlalchemy<3,>=2
15
- Requires-Dist: wexample-helpers>=19.0.0
16
- Provides-Extra: dev
17
- Requires-Dist: pytest; extra == "dev"
18
- Requires-Dist: pytest-cov; extra == "dev"
19
- Description-Content-Type: text/markdown
20
-
21
- # orm
22
-
23
- Version: 1.3.1
24
-
25
- ORM abstractions on top of SQLAlchemy, PostgreSQL-first (psycopg bundled).
26
-
27
- ## Table of Contents
28
-
29
- - [Tests](#tests)
30
- - [Suite Integration](#suite-integration)
31
- - [Dependencies](#dependencies)
32
- - [Versioning](#versioning)
33
- - [License](#license)
34
- - [Suite Integration](#suite-integration)
35
- - [Suite Signature](#suite-signature)
36
- - [Introduction](#introduction)
37
- - [Roadmap](#roadmap)
38
- - [Status Compatibility](#status-compatibility)
39
- - [Useful Links](#useful-links)
40
- - [Migration Notes](#migration-notes)
41
-
42
- ## Tests
43
-
44
- This project uses `pytest` for testing and `pytest-cov` for code coverage analysis.
45
-
46
- ### Installation
47
-
48
- First, install the required testing dependencies:
49
- ```bash
50
- .venv/bin/python -m pip install pytest pytest-cov
51
- ```
52
-
53
- ### Basic Usage
54
-
55
- Run all tests with coverage:
56
- ```bash
57
- .venv/bin/python -m pytest --cov --cov-report=html
58
- ```
59
-
60
- ### Common Commands
61
- ```bash
62
- # Run tests with coverage for a specific module
63
- .venv/bin/python -m pytest --cov=your_module
64
-
65
- # Show which lines are not covered
66
- .venv/bin/python -m pytest --cov=your_module --cov-report=term-missing
67
-
68
- # Generate an HTML coverage report
69
- .venv/bin/python -m pytest --cov=your_module --cov-report=html
70
-
71
- # Combine terminal and HTML reports
72
- .venv/bin/python -m pytest --cov=your_module --cov-report=term-missing --cov-report=html
73
-
74
- # Run specific test file with coverage
75
- .venv/bin/python -m pytest tests/test_file.py --cov=your_module --cov-report=term-missing
76
- ```
77
-
78
- ### Viewing HTML Reports
79
-
80
- After generating an HTML report, open `htmlcov/index.html` in your browser to view detailed line-by-line coverage information.
81
-
82
- ### Coverage Threshold
83
-
84
- To enforce a minimum coverage percentage:
85
- ```bash
86
- .venv/bin/python -m pytest --cov=your_module --cov-fail-under=80
87
- ```
88
-
89
- This will cause the test suite to fail if coverage drops below 80%.
90
-
91
- ## Integration in the Suite
92
-
93
- 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.
94
-
95
- ### Related Packages
96
-
97
- 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.
98
-
99
- Visit the [Wexample Suite documentation](https://docs.wexample.com) for the complete package ecosystem.
100
-
101
- ## Dependencies
102
-
103
- - attrs: >=23.1.0
104
- - psycopg: >=3.2
105
- - sqlalchemy: <3,>=2
106
- - wexample-helpers: >=19.0.0
107
-
108
- ## Versioning & Compatibility Policy
109
-
110
- Wexample packages follow **Semantic Versioning** (SemVer):
111
-
112
- - **MAJOR**: Breaking changes
113
- - **MINOR**: New features, backward compatible
114
- - **PATCH**: Bug fixes, backward compatible
115
-
116
- We maintain backward compatibility within major versions and provide clear migration guides for breaking changes.
117
-
118
- ## License
119
-
120
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
121
-
122
- Free to use in both personal and commercial projects.
123
-
124
- ## Integration in the Suite
125
-
126
- 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.
127
-
128
- ### Related Packages
129
-
130
- 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.
131
-
132
- Visit the [Wexample Suite documentation](https://docs.wexample.com) for the complete package ecosystem.
133
-
134
- # About us
135
-
136
- [Wexample](https://wexample.com) 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.
137
-
138
- 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.
139
-
140
- 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.
141
-
142
- Python Doctrine inspired ORM
143
-
144
- ## Known Limitations & Roadmap
145
-
146
- Current limitations and planned features are tracked in the GitHub issues.
147
-
148
- See the [project roadmap](https://github.com/wexample/python-orm/issues) for upcoming features and improvements.
149
-
150
- ## Status & Compatibility
151
-
152
- **Maturity**: Production-ready
153
-
154
- **Python Support**: >=3.10
155
-
156
- **OS Support**: Linux, macOS, Windows
157
-
158
- **Status**: Actively maintained
159
-
160
- ## Useful Links
161
-
162
- - **Homepage**: https://github.com/wexample/python-orm
163
- - **Documentation**: [docs.wexample.com](https://docs.wexample.com)
164
- - **Issue Tracker**: https://github.com/wexample/python-orm/issues
165
- - **Discussions**: https://github.com/wexample/python-orm/discussions
166
- - **PyPI**: [pypi.org/project/orm](https://pypi.org/project/orm/)
167
-
168
- ## Migration Notes
169
-
170
- When upgrading between major versions, refer to the migration guides in the documentation.
171
-
172
- Breaking changes are clearly documented with upgrade paths and examples.
@@ -1,152 +0,0 @@
1
- # orm
2
-
3
- Version: 1.3.1
4
-
5
- ORM abstractions on top of SQLAlchemy, PostgreSQL-first (psycopg bundled).
6
-
7
- ## Table of Contents
8
-
9
- - [Tests](#tests)
10
- - [Suite Integration](#suite-integration)
11
- - [Dependencies](#dependencies)
12
- - [Versioning](#versioning)
13
- - [License](#license)
14
- - [Suite Integration](#suite-integration)
15
- - [Suite Signature](#suite-signature)
16
- - [Introduction](#introduction)
17
- - [Roadmap](#roadmap)
18
- - [Status Compatibility](#status-compatibility)
19
- - [Useful Links](#useful-links)
20
- - [Migration Notes](#migration-notes)
21
-
22
- ## Tests
23
-
24
- This project uses `pytest` for testing and `pytest-cov` for code coverage analysis.
25
-
26
- ### Installation
27
-
28
- First, install the required testing dependencies:
29
- ```bash
30
- .venv/bin/python -m pip install pytest pytest-cov
31
- ```
32
-
33
- ### Basic Usage
34
-
35
- Run all tests with coverage:
36
- ```bash
37
- .venv/bin/python -m pytest --cov --cov-report=html
38
- ```
39
-
40
- ### Common Commands
41
- ```bash
42
- # Run tests with coverage for a specific module
43
- .venv/bin/python -m pytest --cov=your_module
44
-
45
- # Show which lines are not covered
46
- .venv/bin/python -m pytest --cov=your_module --cov-report=term-missing
47
-
48
- # Generate an HTML coverage report
49
- .venv/bin/python -m pytest --cov=your_module --cov-report=html
50
-
51
- # Combine terminal and HTML reports
52
- .venv/bin/python -m pytest --cov=your_module --cov-report=term-missing --cov-report=html
53
-
54
- # Run specific test file with coverage
55
- .venv/bin/python -m pytest tests/test_file.py --cov=your_module --cov-report=term-missing
56
- ```
57
-
58
- ### Viewing HTML Reports
59
-
60
- After generating an HTML report, open `htmlcov/index.html` in your browser to view detailed line-by-line coverage information.
61
-
62
- ### Coverage Threshold
63
-
64
- To enforce a minimum coverage percentage:
65
- ```bash
66
- .venv/bin/python -m pytest --cov=your_module --cov-fail-under=80
67
- ```
68
-
69
- This will cause the test suite to fail if coverage drops below 80%.
70
-
71
- ## Integration in the Suite
72
-
73
- 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.
74
-
75
- ### Related Packages
76
-
77
- 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.
78
-
79
- Visit the [Wexample Suite documentation](https://docs.wexample.com) for the complete package ecosystem.
80
-
81
- ## Dependencies
82
-
83
- - attrs: >=23.1.0
84
- - psycopg: >=3.2
85
- - sqlalchemy: <3,>=2
86
- - wexample-helpers: >=19.0.0
87
-
88
- ## Versioning & Compatibility Policy
89
-
90
- Wexample packages follow **Semantic Versioning** (SemVer):
91
-
92
- - **MAJOR**: Breaking changes
93
- - **MINOR**: New features, backward compatible
94
- - **PATCH**: Bug fixes, backward compatible
95
-
96
- We maintain backward compatibility within major versions and provide clear migration guides for breaking changes.
97
-
98
- ## License
99
-
100
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
101
-
102
- Free to use in both personal and commercial projects.
103
-
104
- ## Integration in the Suite
105
-
106
- 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.
107
-
108
- ### Related Packages
109
-
110
- 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.
111
-
112
- Visit the [Wexample Suite documentation](https://docs.wexample.com) for the complete package ecosystem.
113
-
114
- # About us
115
-
116
- [Wexample](https://wexample.com) 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.
117
-
118
- 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.
119
-
120
- 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.
121
-
122
- Python Doctrine inspired ORM
123
-
124
- ## Known Limitations & Roadmap
125
-
126
- Current limitations and planned features are tracked in the GitHub issues.
127
-
128
- See the [project roadmap](https://github.com/wexample/python-orm/issues) for upcoming features and improvements.
129
-
130
- ## Status & Compatibility
131
-
132
- **Maturity**: Production-ready
133
-
134
- **Python Support**: >=3.10
135
-
136
- **OS Support**: Linux, macOS, Windows
137
-
138
- **Status**: Actively maintained
139
-
140
- ## Useful Links
141
-
142
- - **Homepage**: https://github.com/wexample/python-orm
143
- - **Documentation**: [docs.wexample.com](https://docs.wexample.com)
144
- - **Issue Tracker**: https://github.com/wexample/python-orm/issues
145
- - **Discussions**: https://github.com/wexample/python-orm/discussions
146
- - **PyPI**: [pypi.org/project/orm](https://pypi.org/project/orm/)
147
-
148
- ## Migration Notes
149
-
150
- When upgrading between major versions, refer to the migration guides in the documentation.
151
-
152
- Breaking changes are clearly documented with upgrade paths and examples.