wexample-orm 1.4.5__tar.gz → 2.0.1__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.
- wexample_orm-2.0.1/PKG-INFO +117 -0
- wexample_orm-2.0.1/README.md +96 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/pyproject.toml +3 -2
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/const/__init__.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/const/generator.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/const/import_export.py +0 -0
- wexample_orm-2.0.1/src/wexample_orm/entity/abstract_entity.py +40 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/exception/import_reference_not_found_exception.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/exception/import_unknown_entity_exception.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/generator/__init__.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/generator/entity_generator.py +3 -2
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/generator/entity_schema.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/generator/repository_generator.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/helper/__init__.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/helper/doctrine.py +2 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/import_export/__init__.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/import_export/entity_exporter.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/import_export/entity_importer.py +4 -2
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/import_export/import_references_bag.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/import_export/normalized_entity_hydrator.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/py.typed +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/repository/abstract_repository.py +6 -1
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/tests/.gitkeep +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/tests/test_abstract.py +19 -12
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/tests/test_entity_generator.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/tests/test_entity_importer.py +9 -7
- wexample_orm-1.4.5/PKG-INFO +0 -347
- wexample_orm-1.4.5/README.md +0 -327
- wexample_orm-1.4.5/src/wexample_orm/entity/abstract_entity.py +0 -41
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/__init__.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/common/__init__.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/common/abstract_repositories_manager.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/entity/__init__.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/exception/__init__.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/exception/repository_session_missing_exception.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/exception/unknown_repository_exception.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/repository/__init__.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/session/__init__.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/session/abstract_session_factory.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/testing/__init__.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/testing/sqlite.py +0 -0
- {wexample_orm-1.4.5 → wexample_orm-2.0.1}/tests/__init__.py +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: wexample-orm
|
|
3
|
+
Version: 2.0.1
|
|
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: uuid-utils>=0.11
|
|
16
|
+
Requires-Dist: wexample-helpers>=20.6.0
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: pytest; extra == "dev"
|
|
19
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# orm
|
|
23
|
+
|
|
24
|
+
Version: 2.0.1
|
|
25
|
+
|
|
26
|
+
Provides SQLAlchemy ORM abstractions — base entity, repository, session factory, and repository manager — with PostgreSQL-first defaults via psycopg
|
|
27
|
+
|
|
28
|
+
## Table of Contents
|
|
29
|
+
|
|
30
|
+
- [Introduction](#introduction)
|
|
31
|
+
- [Install](#install)
|
|
32
|
+
- [Tests](#tests)
|
|
33
|
+
- [Roadmap](#roadmap)
|
|
34
|
+
- [Status Compatibility](#status-compatibility)
|
|
35
|
+
- [Useful Links](#useful-links)
|
|
36
|
+
|
|
37
|
+
Python Doctrine inspired ORM
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install wexample-orm
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Requires Python >=3.10.
|
|
46
|
+
|
|
47
|
+
## Tests
|
|
48
|
+
|
|
49
|
+
This project uses `pytest` for testing and `pytest-cov` for code coverage analysis.
|
|
50
|
+
|
|
51
|
+
### Installation
|
|
52
|
+
|
|
53
|
+
First, install the required testing dependencies:
|
|
54
|
+
```bash
|
|
55
|
+
.venv/bin/python -m pip install pytest pytest-cov
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Basic Usage
|
|
59
|
+
|
|
60
|
+
Run all tests with coverage:
|
|
61
|
+
```bash
|
|
62
|
+
.venv/bin/python -m pytest --cov --cov-report=html
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Common Commands
|
|
66
|
+
```bash
|
|
67
|
+
# Run tests with coverage for a specific module
|
|
68
|
+
.venv/bin/python -m pytest --cov=your_module
|
|
69
|
+
|
|
70
|
+
# Show which lines are not covered
|
|
71
|
+
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing
|
|
72
|
+
|
|
73
|
+
# Generate an HTML coverage report
|
|
74
|
+
.venv/bin/python -m pytest --cov=your_module --cov-report=html
|
|
75
|
+
|
|
76
|
+
# Combine terminal and HTML reports
|
|
77
|
+
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing --cov-report=html
|
|
78
|
+
|
|
79
|
+
# Run specific test file with coverage
|
|
80
|
+
.venv/bin/python -m pytest tests/test_file.py --cov=your_module --cov-report=term-missing
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Viewing HTML Reports
|
|
84
|
+
|
|
85
|
+
After generating an HTML report, open `htmlcov/index.html` in your browser to view detailed line-by-line coverage information.
|
|
86
|
+
|
|
87
|
+
### Coverage Threshold
|
|
88
|
+
|
|
89
|
+
To enforce a minimum coverage percentage:
|
|
90
|
+
```bash
|
|
91
|
+
.venv/bin/python -m pytest --cov=your_module --cov-fail-under=80
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
This will cause the test suite to fail if coverage drops below 80%.
|
|
95
|
+
|
|
96
|
+
## Known Limitations & Roadmap
|
|
97
|
+
|
|
98
|
+
Current limitations and planned features are tracked in the GitHub issues.
|
|
99
|
+
|
|
100
|
+
See the [project roadmap](https://github.com/wexample/python-orm/issues) for upcoming features and improvements.
|
|
101
|
+
|
|
102
|
+
## Status & Compatibility
|
|
103
|
+
|
|
104
|
+
**Maturity**: Production-ready
|
|
105
|
+
|
|
106
|
+
**Python Support**: >=3.10
|
|
107
|
+
|
|
108
|
+
**OS Support**: Linux, macOS, Windows
|
|
109
|
+
|
|
110
|
+
**Status**: Actively maintained
|
|
111
|
+
|
|
112
|
+
## Useful Links
|
|
113
|
+
|
|
114
|
+
- **Homepage**: https://github.com/wexample/python-orm
|
|
115
|
+
- **Issue Tracker**: https://github.com/wexample/python-orm/issues
|
|
116
|
+
- **Discussions**: https://github.com/wexample/python-orm/discussions
|
|
117
|
+
- **PyPI**: [pypi.org/project/wexample-orm](https://pypi.org/project/wexample-orm/)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# orm
|
|
2
|
+
|
|
3
|
+
Version: 2.0.1
|
|
4
|
+
|
|
5
|
+
Provides SQLAlchemy ORM abstractions — base entity, repository, session factory, and repository manager — with PostgreSQL-first defaults via psycopg
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Introduction](#introduction)
|
|
10
|
+
- [Install](#install)
|
|
11
|
+
- [Tests](#tests)
|
|
12
|
+
- [Roadmap](#roadmap)
|
|
13
|
+
- [Status Compatibility](#status-compatibility)
|
|
14
|
+
- [Useful Links](#useful-links)
|
|
15
|
+
|
|
16
|
+
Python Doctrine inspired ORM
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install wexample-orm
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Requires Python >=3.10.
|
|
25
|
+
|
|
26
|
+
## Tests
|
|
27
|
+
|
|
28
|
+
This project uses `pytest` for testing and `pytest-cov` for code coverage analysis.
|
|
29
|
+
|
|
30
|
+
### Installation
|
|
31
|
+
|
|
32
|
+
First, install the required testing dependencies:
|
|
33
|
+
```bash
|
|
34
|
+
.venv/bin/python -m pip install pytest pytest-cov
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Basic Usage
|
|
38
|
+
|
|
39
|
+
Run all tests with coverage:
|
|
40
|
+
```bash
|
|
41
|
+
.venv/bin/python -m pytest --cov --cov-report=html
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Common Commands
|
|
45
|
+
```bash
|
|
46
|
+
# Run tests with coverage for a specific module
|
|
47
|
+
.venv/bin/python -m pytest --cov=your_module
|
|
48
|
+
|
|
49
|
+
# Show which lines are not covered
|
|
50
|
+
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing
|
|
51
|
+
|
|
52
|
+
# Generate an HTML coverage report
|
|
53
|
+
.venv/bin/python -m pytest --cov=your_module --cov-report=html
|
|
54
|
+
|
|
55
|
+
# Combine terminal and HTML reports
|
|
56
|
+
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing --cov-report=html
|
|
57
|
+
|
|
58
|
+
# Run specific test file with coverage
|
|
59
|
+
.venv/bin/python -m pytest tests/test_file.py --cov=your_module --cov-report=term-missing
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Viewing HTML Reports
|
|
63
|
+
|
|
64
|
+
After generating an HTML report, open `htmlcov/index.html` in your browser to view detailed line-by-line coverage information.
|
|
65
|
+
|
|
66
|
+
### Coverage Threshold
|
|
67
|
+
|
|
68
|
+
To enforce a minimum coverage percentage:
|
|
69
|
+
```bash
|
|
70
|
+
.venv/bin/python -m pytest --cov=your_module --cov-fail-under=80
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
This will cause the test suite to fail if coverage drops below 80%.
|
|
74
|
+
|
|
75
|
+
## Known Limitations & Roadmap
|
|
76
|
+
|
|
77
|
+
Current limitations and planned features are tracked in the GitHub issues.
|
|
78
|
+
|
|
79
|
+
See the [project roadmap](https://github.com/wexample/python-orm/issues) for upcoming features and improvements.
|
|
80
|
+
|
|
81
|
+
## Status & Compatibility
|
|
82
|
+
|
|
83
|
+
**Maturity**: Production-ready
|
|
84
|
+
|
|
85
|
+
**Python Support**: >=3.10
|
|
86
|
+
|
|
87
|
+
**OS Support**: Linux, macOS, Windows
|
|
88
|
+
|
|
89
|
+
**Status**: Actively maintained
|
|
90
|
+
|
|
91
|
+
## Useful Links
|
|
92
|
+
|
|
93
|
+
- **Homepage**: https://github.com/wexample/python-orm
|
|
94
|
+
- **Issue Tracker**: https://github.com/wexample/python-orm/issues
|
|
95
|
+
- **Discussions**: https://github.com/wexample/python-orm/discussions
|
|
96
|
+
- **PyPI**: [pypi.org/project/wexample-orm](https://pypi.org/project/wexample-orm/)
|
|
@@ -6,7 +6,7 @@ build-backend = "pdm.backend"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "wexample-orm"
|
|
9
|
-
version = "
|
|
9
|
+
version = "2.0.1"
|
|
10
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" },
|
|
@@ -21,7 +21,8 @@ dependencies = [
|
|
|
21
21
|
"attrs>=23.1.0",
|
|
22
22
|
"psycopg[binary]>=3.2",
|
|
23
23
|
"sqlalchemy>=2,<3",
|
|
24
|
-
"
|
|
24
|
+
"uuid-utils>=0.11",
|
|
25
|
+
"wexample-helpers>=20.6.0",
|
|
25
26
|
]
|
|
26
27
|
|
|
27
28
|
[project.readme]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
|
|
5
|
+
from sqlalchemy import Uuid
|
|
6
|
+
from sqlalchemy.orm import Mapped, as_declarative, declared_attr, mapped_column
|
|
7
|
+
from wexample_helpers.helper.string import string_to_snake_case
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@as_declarative()
|
|
11
|
+
class AbstractEntity:
|
|
12
|
+
"""Common ancestor for every wex-managed SQLAlchemy entity.
|
|
13
|
+
|
|
14
|
+
Imposes a single convention for the primary key across all projects: a
|
|
15
|
+
UUIDv7, as the PHP side gives every entity (symfony-helpers
|
|
16
|
+
``AbstractEntity``), so the same table is read and written from PHP and
|
|
17
|
+
Python alike.
|
|
18
|
+
|
|
19
|
+
The id is generated here, not by the database: an entity written from
|
|
20
|
+
Python has its id before the flush, and its timestamp prefix keeps the
|
|
21
|
+
creation order. ``Uuid`` is a native ``UUID`` on PostgreSQL and a
|
|
22
|
+
``CHAR(32)`` on SQLite, so in-memory sessions used by
|
|
23
|
+
``wexample_orm.testing.in_memory_session`` keep working as they are.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
__abstract__ = True
|
|
27
|
+
|
|
28
|
+
@declared_attr
|
|
29
|
+
def __tablename__(cls) -> str:
|
|
30
|
+
return cls.get_entity_name()
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def get_entity_name(cls) -> str:
|
|
34
|
+
return string_to_snake_case(cls.__name__)
|
|
35
|
+
|
|
36
|
+
@declared_attr
|
|
37
|
+
def id(cls) -> Mapped[UUID]:
|
|
38
|
+
from uuid_utils.compat import uuid7
|
|
39
|
+
|
|
40
|
+
return mapped_column(Uuid, primary_key=True, default=uuid7)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -124,10 +124,11 @@ class EntityGenerator(BaseClass):
|
|
|
124
124
|
self._attribute(
|
|
125
125
|
foreign_key,
|
|
126
126
|
"object",
|
|
127
|
-
f"mapped_column(
|
|
127
|
+
f"mapped_column(Uuid, {constraint}nullable={nullable})",
|
|
128
128
|
)
|
|
129
129
|
)
|
|
130
|
-
|
|
130
|
+
# Every id is a UUID (AbstractEntity), whatever entity it points to.
|
|
131
|
+
imports.add("Uuid")
|
|
131
132
|
field_names.add(foreign_key)
|
|
132
133
|
|
|
133
134
|
if target is not None:
|
|
File without changes
|
{wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/generator/repository_generator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
@@ -21,6 +21,8 @@ def doctrine_type_to_sqlalchemy(orm: dict[str, Any]) -> tuple[str, set[str]]:
|
|
|
21
21
|
the names it needs imported from `sqlalchemy`."""
|
|
22
22
|
doctrine_type = str(orm.get("type") or "string").lower()
|
|
23
23
|
|
|
24
|
+
if doctrine_type == "uuid":
|
|
25
|
+
return "Uuid", {"Uuid"}
|
|
24
26
|
if doctrine_type in {"integer", "int", "smallint", "bigint"}:
|
|
25
27
|
return "Integer", {"Integer"}
|
|
26
28
|
if doctrine_type in {"string", "guid"}:
|
|
File without changes
|
|
File without changes
|
|
@@ -35,7 +35,7 @@ class EntityImporter(BaseClass):
|
|
|
35
35
|
"""
|
|
36
36
|
|
|
37
37
|
assign_ids: bool = public_field(
|
|
38
|
-
description="Give every entity
|
|
38
|
+
description="Give every entity its UUID at once, for graphs that never reach a database.",
|
|
39
39
|
default=False,
|
|
40
40
|
)
|
|
41
41
|
entity_types: list[type[AbstractEntity]] = public_field(
|
|
@@ -159,7 +159,9 @@ class EntityImporter(BaseClass):
|
|
|
159
159
|
entity = entity_type()
|
|
160
160
|
self._entities.append(entity)
|
|
161
161
|
if self.assign_ids:
|
|
162
|
-
|
|
162
|
+
from uuid_utils.compat import uuid7
|
|
163
|
+
|
|
164
|
+
entity.id = uuid7()
|
|
163
165
|
return entity
|
|
164
166
|
|
|
165
167
|
def _find_column_attribute(self, mapper: Mapper, name: str) -> str | None:
|
{wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/import_export/import_references_bag.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/repository/abstract_repository.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING, Any
|
|
4
|
+
from uuid import UUID
|
|
4
5
|
|
|
5
6
|
from wexample_helpers.classes.base_class import BaseClass
|
|
6
7
|
from wexample_helpers.classes.field import public_field
|
|
@@ -23,9 +24,13 @@ class AbstractRepository(BaseClass):
|
|
|
23
24
|
def get_entity_type(cls) -> type[AbstractEntity]:
|
|
24
25
|
cls._raise_not_implemented_error()
|
|
25
26
|
|
|
26
|
-
def find(self, entity_id:
|
|
27
|
+
def find(self, entity_id: UUID | str) -> AbstractEntity | None:
|
|
27
28
|
if self.session is None:
|
|
28
29
|
return None
|
|
30
|
+
# Ids arrive as text as often as not (message bodies, command lines,
|
|
31
|
+
# API payloads); the Uuid column only binds UUID objects.
|
|
32
|
+
if not isinstance(entity_id, UUID):
|
|
33
|
+
entity_id = UUID(str(entity_id))
|
|
29
34
|
return self.session.get(self.get_entity_type(), entity_id)
|
|
30
35
|
|
|
31
36
|
def find_one_by(self, filter_clause: Any) -> AbstractEntity | None:
|
|
File without changes
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
|
|
3
5
|
import pytest
|
|
4
|
-
from sqlalchemy import
|
|
6
|
+
from sqlalchemy import Uuid
|
|
5
7
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
6
8
|
|
|
7
9
|
from wexample_orm.common.abstract_repositories_manager import (
|
|
@@ -28,18 +30,20 @@ def test_entity_name_is_snake_case_of_class_name() -> None:
|
|
|
28
30
|
# ---------------------------------------------------------------------------
|
|
29
31
|
|
|
30
32
|
|
|
31
|
-
def
|
|
32
|
-
"""Every entity gets a
|
|
33
|
-
|
|
34
|
-
Production schema (PG) uses ``GENERATED BY DEFAULT AS IDENTITY`` — the
|
|
35
|
-
DB owns id allocation, SQLAlchemy doesn't carry a Sequence default.
|
|
36
|
-
"""
|
|
33
|
+
def test_id_is_a_uuid_v7_given_by_python() -> None:
|
|
34
|
+
"""Every entity gets a UUID primary key, generated on the Python side: the
|
|
35
|
+
same identity the PHP entities have, available before the flush."""
|
|
37
36
|
id_col = User.__table__.c.id
|
|
38
|
-
assert isinstance(id_col.type,
|
|
39
|
-
assert id_col.default is None # no SQLAlchemy-managed Sequence
|
|
40
|
-
assert id_col.autoincrement is True
|
|
37
|
+
assert isinstance(id_col.type, Uuid)
|
|
41
38
|
assert id_col.primary_key is True
|
|
42
39
|
|
|
40
|
+
with in_memory_session(AbstractEntity) as session:
|
|
41
|
+
user = User(name="alice")
|
|
42
|
+
session.add(user)
|
|
43
|
+
session.flush()
|
|
44
|
+
assert isinstance(user.id, UUID)
|
|
45
|
+
assert user.id.version == 7
|
|
46
|
+
|
|
43
47
|
|
|
44
48
|
def test_manager_caches_repository_instances() -> None:
|
|
45
49
|
with in_memory_session(AbstractEntity) as session:
|
|
@@ -86,12 +90,15 @@ def test_repository_without_session_raises_on_query() -> None:
|
|
|
86
90
|
def test_save_and_find_roundtrip() -> None:
|
|
87
91
|
with in_memory_session(AbstractEntity) as session:
|
|
88
92
|
repo = UserRepository(session=session)
|
|
89
|
-
|
|
93
|
+
alice = User(name="alice")
|
|
94
|
+
repo.save(alice)
|
|
90
95
|
repo.save(User(name="bob"))
|
|
91
96
|
|
|
92
|
-
first = repo.find(
|
|
97
|
+
first = repo.find(alice.id)
|
|
93
98
|
assert first is not None
|
|
94
99
|
assert first.name == "alice"
|
|
100
|
+
# The text form, as ids come from messages and payloads.
|
|
101
|
+
assert repo.find(str(alice.id)) is first
|
|
95
102
|
|
|
96
103
|
|
|
97
104
|
class User(AbstractEntity):
|
|
File without changes
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
|
|
3
5
|
import pytest
|
|
4
|
-
from sqlalchemy import JSON, DateTime, ForeignKey,
|
|
6
|
+
from sqlalchemy import JSON, DateTime, ForeignKey, String, Uuid
|
|
5
7
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
6
8
|
|
|
7
9
|
from wexample_orm.entity.abstract_entity import AbstractEntity
|
|
@@ -157,9 +159,9 @@ class ImpFolder(AbstractEntity):
|
|
|
157
159
|
"ImpFile", foreign_keys="ImpFile.folder_id", back_populates="folder"
|
|
158
160
|
)
|
|
159
161
|
owner = relationship("ImpOwner", foreign_keys="ImpFolder.owner_id")
|
|
160
|
-
owner_id: Mapped[
|
|
162
|
+
owner_id: Mapped[UUID] = mapped_column(Uuid, ForeignKey("imp_owner.id"), nullable=True)
|
|
161
163
|
pinned = relationship("ImpFile", foreign_keys="ImpFolder.pinned_id")
|
|
162
|
-
pinned_id: Mapped[
|
|
164
|
+
pinned_id: Mapped[UUID] = mapped_column(Uuid, ForeignKey("imp_file.id"), nullable=True)
|
|
163
165
|
title: Mapped[str] = mapped_column(String(64), nullable=True)
|
|
164
166
|
|
|
165
167
|
|
|
@@ -169,9 +171,9 @@ class ImpFile(AbstractEntity):
|
|
|
169
171
|
folder = relationship(
|
|
170
172
|
"ImpFolder", foreign_keys="ImpFile.folder_id", back_populates="files"
|
|
171
173
|
)
|
|
172
|
-
folder_id: Mapped[
|
|
174
|
+
folder_id: Mapped[UUID] = mapped_column(Uuid, ForeignKey("imp_folder.id"), nullable=True)
|
|
173
175
|
name: Mapped[str] = mapped_column(String(64), nullable=True)
|
|
174
|
-
target: Mapped[
|
|
176
|
+
target: Mapped[UUID] = mapped_column(Uuid, nullable=True)
|
|
175
177
|
|
|
176
178
|
|
|
177
179
|
class ImpTag(AbstractEntity):
|
|
@@ -180,9 +182,9 @@ class ImpTag(AbstractEntity):
|
|
|
180
182
|
|
|
181
183
|
class ImpFolderTag(AbstractEntity):
|
|
182
184
|
folder = relationship("ImpFolder", foreign_keys="ImpFolderTag.folder_id")
|
|
183
|
-
folder_id: Mapped[
|
|
185
|
+
folder_id: Mapped[UUID] = mapped_column(Uuid, ForeignKey("imp_folder.id"), nullable=True)
|
|
184
186
|
tag = relationship("ImpTag", foreign_keys="ImpFolderTag.tag_id")
|
|
185
|
-
tag_id: Mapped[
|
|
187
|
+
tag_id: Mapped[UUID] = mapped_column(Uuid, ForeignKey("imp_tag.id"), nullable=True)
|
|
186
188
|
|
|
187
189
|
|
|
188
190
|
ENTITY_TYPES = [ImpOwner, ImpFolder, ImpFile, ImpTag, ImpFolderTag]
|
wexample_orm-1.4.5/PKG-INFO
DELETED
|
@@ -1,347 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: wexample-orm
|
|
3
|
-
Version: 1.4.5
|
|
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.5.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.4.5
|
|
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
|
-
## Dependencies
|
|
288
|
-
|
|
289
|
-
- attrs: >=23.1.0
|
|
290
|
-
- psycopg: >=3.2
|
|
291
|
-
- sqlalchemy: <3,>=2
|
|
292
|
-
- wexample-helpers: >=20.5.0
|
|
293
|
-
|
|
294
|
-
## Versioning & Compatibility Policy
|
|
295
|
-
|
|
296
|
-
Wexample packages follow **Semantic Versioning** (SemVer):
|
|
297
|
-
|
|
298
|
-
- **MAJOR**: Breaking changes
|
|
299
|
-
- **MINOR**: New features, backward compatible
|
|
300
|
-
- **PATCH**: Bug fixes, backward compatible
|
|
301
|
-
|
|
302
|
-
We maintain backward compatibility within major versions and provide clear migration guides for breaking changes.
|
|
303
|
-
|
|
304
|
-
## License
|
|
305
|
-
|
|
306
|
-
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
307
|
-
|
|
308
|
-
Free to use in both personal and commercial projects.
|
|
309
|
-
|
|
310
|
-
## About us
|
|
311
|
-
|
|
312
|
-
[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.
|
|
313
|
-
|
|
314
|
-
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.
|
|
315
|
-
|
|
316
|
-
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.
|
|
317
|
-
|
|
318
|
-
Built and maintained by [Romain Weeger](https://weeger.io).
|
|
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
|
-
- **Issue Tracker**: https://github.com/wexample/python-orm/issues
|
|
340
|
-
- **Discussions**: https://github.com/wexample/python-orm/discussions
|
|
341
|
-
- **PyPI**: [pypi.org/project/wexample-orm](https://pypi.org/project/wexample-orm/)
|
|
342
|
-
|
|
343
|
-
## Migration Notes
|
|
344
|
-
|
|
345
|
-
When upgrading between major versions, refer to the migration guides in the documentation.
|
|
346
|
-
|
|
347
|
-
Breaking changes are clearly documented with upgrade paths and examples.
|
wexample_orm-1.4.5/README.md
DELETED
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
# orm
|
|
2
|
-
|
|
3
|
-
Version: 1.4.5
|
|
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
|
-
## Dependencies
|
|
268
|
-
|
|
269
|
-
- attrs: >=23.1.0
|
|
270
|
-
- psycopg: >=3.2
|
|
271
|
-
- sqlalchemy: <3,>=2
|
|
272
|
-
- wexample-helpers: >=20.5.0
|
|
273
|
-
|
|
274
|
-
## Versioning & Compatibility Policy
|
|
275
|
-
|
|
276
|
-
Wexample packages follow **Semantic Versioning** (SemVer):
|
|
277
|
-
|
|
278
|
-
- **MAJOR**: Breaking changes
|
|
279
|
-
- **MINOR**: New features, backward compatible
|
|
280
|
-
- **PATCH**: Bug fixes, backward compatible
|
|
281
|
-
|
|
282
|
-
We maintain backward compatibility within major versions and provide clear migration guides for breaking changes.
|
|
283
|
-
|
|
284
|
-
## License
|
|
285
|
-
|
|
286
|
-
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
287
|
-
|
|
288
|
-
Free to use in both personal and commercial projects.
|
|
289
|
-
|
|
290
|
-
## About us
|
|
291
|
-
|
|
292
|
-
[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.
|
|
293
|
-
|
|
294
|
-
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.
|
|
295
|
-
|
|
296
|
-
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.
|
|
297
|
-
|
|
298
|
-
Built and maintained by [Romain Weeger](https://weeger.io).
|
|
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
|
-
- **Issue Tracker**: https://github.com/wexample/python-orm/issues
|
|
320
|
-
- **Discussions**: https://github.com/wexample/python-orm/discussions
|
|
321
|
-
- **PyPI**: [pypi.org/project/wexample-orm](https://pypi.org/project/wexample-orm/)
|
|
322
|
-
|
|
323
|
-
## Migration Notes
|
|
324
|
-
|
|
325
|
-
When upgrading between major versions, refer to the migration guides in the documentation.
|
|
326
|
-
|
|
327
|
-
Breaking changes are clearly documented with upgrade paths and examples.
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from sqlalchemy import BigInteger, Integer
|
|
4
|
-
from sqlalchemy.orm import Mapped, as_declarative, declared_attr, mapped_column
|
|
5
|
-
from wexample_helpers.helper.string import string_to_snake_case
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@as_declarative()
|
|
9
|
-
class AbstractEntity:
|
|
10
|
-
"""Common ancestor for every wex-managed SQLAlchemy entity.
|
|
11
|
-
|
|
12
|
-
Imposes a single convention for the primary key across all projects:
|
|
13
|
-
|
|
14
|
-
``id BIGINT GENERATED BY DEFAULT AS IDENTITY`` on PostgreSQL — modern
|
|
15
|
-
SQL standard (PG ≥ 10) and Doctrine's default when it owns the
|
|
16
|
-
schema, so the same table works seamlessly from PHP and Python.
|
|
17
|
-
|
|
18
|
-
A ``with_variant(Integer, "sqlite")`` is bolted on for tests: SQLite
|
|
19
|
-
only auto-increments a column declared as ``INTEGER PRIMARY KEY``
|
|
20
|
-
(ROWID alias), not ``BIGINT``. Production stays BIGINT, in-memory
|
|
21
|
-
sessions used by ``wexample_orm.testing.in_memory_session`` keep
|
|
22
|
-
working without per-project gymnastics.
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
__abstract__ = True
|
|
26
|
-
|
|
27
|
-
@declared_attr
|
|
28
|
-
def __tablename__(cls) -> str:
|
|
29
|
-
return cls.get_entity_name()
|
|
30
|
-
|
|
31
|
-
@classmethod
|
|
32
|
-
def get_entity_name(cls) -> str:
|
|
33
|
-
return string_to_snake_case(cls.__name__)
|
|
34
|
-
|
|
35
|
-
@declared_attr
|
|
36
|
-
def id(cls) -> Mapped[int]:
|
|
37
|
-
return mapped_column(
|
|
38
|
-
BigInteger().with_variant(Integer(), "sqlite"),
|
|
39
|
-
primary_key=True,
|
|
40
|
-
autoincrement=True,
|
|
41
|
-
)
|
|
File without changes
|
|
File without changes
|
{wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/common/abstract_repositories_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/exception/unknown_repository_exception.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{wexample_orm-1.4.5 → wexample_orm-2.0.1}/src/wexample_orm/session/abstract_session_factory.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|