pytest-orm-boundaries 0.6.0__tar.gz → 0.7.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.
Files changed (16) hide show
  1. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.1}/PKG-INFO +33 -18
  2. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.1}/README.md +32 -17
  3. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.1}/pyproject.toml +1 -1
  4. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.1}/src/pytest_orm_boundaries/config.py +35 -5
  5. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.1}/src/pytest_orm_boundaries/report.py +2 -2
  6. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.1}/LICENSE +0 -0
  7. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.1}/src/pytest_orm_boundaries/__init__.py +0 -0
  8. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.1}/src/pytest_orm_boundaries/allows.py +0 -0
  9. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.1}/src/pytest_orm_boundaries/callstack.py +0 -0
  10. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.1}/src/pytest_orm_boundaries/crossings.py +0 -0
  11. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.1}/src/pytest_orm_boundaries/guard.py +0 -0
  12. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.1}/src/pytest_orm_boundaries/ignores.py +0 -0
  13. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.1}/src/pytest_orm_boundaries/model_resolution.py +0 -0
  14. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.1}/src/pytest_orm_boundaries/plugin.py +0 -0
  15. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.1}/src/pytest_orm_boundaries/prefetch_resolution.py +0 -0
  16. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.1}/src/pytest_orm_boundaries/sql_parsing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytest-orm-boundaries
3
- Version: 0.6.0
3
+ Version: 0.7.1
4
4
  Summary: Pytest plugin that fails tests when ORM queries cross DDD aggregate boundaries (Django supported today).
5
5
  Keywords: django,pytest,plugin,orm,ddd,aggregate,boundaries,architecture
6
6
  Author: Evgeniia Chibisova
@@ -50,24 +50,37 @@ and reports access that crosses a configured boundary - through `__` lookups,
50
50
 
51
51
  ## Install
52
52
 
53
- Install the plugin with Django support:
53
+ Install the plugin in your Django project:
54
54
 
55
55
  ```bash
56
- pip install "pytest-orm-boundaries[django]"
56
+ pip install pytest-orm-boundaries
57
57
  ```
58
58
 
59
59
  pytest discovers the plugin automatically.
60
60
 
61
+ The plugin uses the Django version already installed by your project. If you are
62
+ installing into an environment without Django and want pip to install it too,
63
+ use the optional extra:
64
+
65
+ ```bash
66
+ pip install "pytest-orm-boundaries[django]"
67
+ ```
68
+
61
69
  ## Configure
62
70
 
63
- Declare your aggregates in `boundaries.toml` at the project root (or point at
64
- the file with `--boundaries-config` / the `boundaries_config` ini option):
71
+ Declare your aggregates and their Django models in `boundaries.toml` at the project
72
+ root (or point at the file with `--boundaries-config` / the
73
+ `boundaries_config` ini option):
65
74
 
66
75
  ```toml
67
- [aggregates]
68
- client = ["bookshop.Client"]
69
- book = ["bookshop.Book"]
70
- purchase = ["bookshop.Purchase", "bookshop.PurchaseLine"]
76
+ [aggregates.client]
77
+ models = ["bookshop.Client"]
78
+
79
+ [aggregates.book]
80
+ models = ["bookshop.Book"]
81
+
82
+ [aggregates.purchase]
83
+ models = ["bookshop.Purchase", "bookshop.PurchaseLine"]
71
84
  ```
72
85
 
73
86
  Models are written as `app_label.Model`. Models not listed in any aggregate are
@@ -146,18 +159,21 @@ Places are ordered by how many tests they affect. Pass `-v` to see every affecte
146
159
 
147
160
  ## Allow and ignore
148
161
 
149
- CQRS read models may cross boundaries intentionally, also existing application code may contain crossings you want to fix over time. [allow] and [ignore] let you tell the plugin which is which:
162
+ CQRS read models may cross aggregate boundaries by design, while existing
163
+ application code may contain crossings you want to fix over time. `[allow]` and
164
+ `[ignore]` let you tell the plugin which is which:
150
165
 
151
166
  - `[allow]` - the crossing is **intentional**. Use it for code that is meant to
152
- span aggregates, such as CQRS read models or cross-aggregate reports. An
153
- allowed crossing is suppressed and never reported.
167
+ span aggregates, such as CQRS read models. An allowed crossing is suppressed
168
+ and never reported.
154
169
  - `[ignore]` - the crossing is **known debt** you plan to fix. It is suppressed
155
- for now, and the plugin reminds you when an entry is no longer needed.
170
+ and the plugin reports the entry when its matching code runs without a
171
+ crossing.
156
172
 
157
173
  ```toml
158
174
  [allow]
159
175
  files = [
160
- "app/reports/sales_summary.py",
176
+ "app/read_models/sales.py",
161
177
  ]
162
178
 
163
179
  [ignore]
@@ -173,13 +189,12 @@ resolved relative to pytest's root directory and matched against either:
173
189
  - the file that issues the query, or
174
190
  - the test file.
175
191
 
176
- An `[ignore]` whose file runs through the whole suite without ever crossing a
177
- boundary is stale — it is clean now, so the plugin lists it for removal:
192
+ An `[ignore]` whose matching code runs through the whole suite without crossing
193
+ a boundary is stale, so the plugin lists it for removal:
178
194
 
179
195
  ```
180
196
  ======================= orm-boundaries: stale ignores ========================
181
- These [ignore] entries no longer suppress any boundary crossing - their files are clean now.
182
- Remove them from boundaries.toml:
197
+ These [ignore] entries matched files that ran without crossing a boundary. Remove them from boundaries.toml:
183
198
  - app/billing.py
184
199
  ```
185
200
 
@@ -21,24 +21,37 @@ and reports access that crosses a configured boundary - through `__` lookups,
21
21
 
22
22
  ## Install
23
23
 
24
- Install the plugin with Django support:
24
+ Install the plugin in your Django project:
25
25
 
26
26
  ```bash
27
- pip install "pytest-orm-boundaries[django]"
27
+ pip install pytest-orm-boundaries
28
28
  ```
29
29
 
30
30
  pytest discovers the plugin automatically.
31
31
 
32
+ The plugin uses the Django version already installed by your project. If you are
33
+ installing into an environment without Django and want pip to install it too,
34
+ use the optional extra:
35
+
36
+ ```bash
37
+ pip install "pytest-orm-boundaries[django]"
38
+ ```
39
+
32
40
  ## Configure
33
41
 
34
- Declare your aggregates in `boundaries.toml` at the project root (or point at
35
- the file with `--boundaries-config` / the `boundaries_config` ini option):
42
+ Declare your aggregates and their Django models in `boundaries.toml` at the project
43
+ root (or point at the file with `--boundaries-config` / the
44
+ `boundaries_config` ini option):
36
45
 
37
46
  ```toml
38
- [aggregates]
39
- client = ["bookshop.Client"]
40
- book = ["bookshop.Book"]
41
- purchase = ["bookshop.Purchase", "bookshop.PurchaseLine"]
47
+ [aggregates.client]
48
+ models = ["bookshop.Client"]
49
+
50
+ [aggregates.book]
51
+ models = ["bookshop.Book"]
52
+
53
+ [aggregates.purchase]
54
+ models = ["bookshop.Purchase", "bookshop.PurchaseLine"]
42
55
  ```
43
56
 
44
57
  Models are written as `app_label.Model`. Models not listed in any aggregate are
@@ -117,18 +130,21 @@ Places are ordered by how many tests they affect. Pass `-v` to see every affecte
117
130
 
118
131
  ## Allow and ignore
119
132
 
120
- CQRS read models may cross boundaries intentionally, also existing application code may contain crossings you want to fix over time. [allow] and [ignore] let you tell the plugin which is which:
133
+ CQRS read models may cross aggregate boundaries by design, while existing
134
+ application code may contain crossings you want to fix over time. `[allow]` and
135
+ `[ignore]` let you tell the plugin which is which:
121
136
 
122
137
  - `[allow]` - the crossing is **intentional**. Use it for code that is meant to
123
- span aggregates, such as CQRS read models or cross-aggregate reports. An
124
- allowed crossing is suppressed and never reported.
138
+ span aggregates, such as CQRS read models. An allowed crossing is suppressed
139
+ and never reported.
125
140
  - `[ignore]` - the crossing is **known debt** you plan to fix. It is suppressed
126
- for now, and the plugin reminds you when an entry is no longer needed.
141
+ and the plugin reports the entry when its matching code runs without a
142
+ crossing.
127
143
 
128
144
  ```toml
129
145
  [allow]
130
146
  files = [
131
- "app/reports/sales_summary.py",
147
+ "app/read_models/sales.py",
132
148
  ]
133
149
 
134
150
  [ignore]
@@ -144,13 +160,12 @@ resolved relative to pytest's root directory and matched against either:
144
160
  - the file that issues the query, or
145
161
  - the test file.
146
162
 
147
- An `[ignore]` whose file runs through the whole suite without ever crossing a
148
- boundary is stale — it is clean now, so the plugin lists it for removal:
163
+ An `[ignore]` whose matching code runs through the whole suite without crossing
164
+ a boundary is stale, so the plugin lists it for removal:
149
165
 
150
166
  ```
151
167
  ======================= orm-boundaries: stale ignores ========================
152
- These [ignore] entries no longer suppress any boundary crossing - their files are clean now.
153
- Remove them from boundaries.toml:
168
+ These [ignore] entries matched files that ran without crossing a boundary. Remove them from boundaries.toml:
154
169
  - app/billing.py
155
170
  ```
156
171
 
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "pytest-orm-boundaries"
7
- version = "0.6.0"
7
+ version = "0.7.1"
8
8
  description = "Pytest plugin that fails tests when ORM queries cross DDD aggregate boundaries (Django supported today)."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -62,13 +62,41 @@ def _parse_aggregates(*, data: dict[str, Any], path: Path) -> dict[str, str]:
62
62
  Model labels are lower-cased ("app_label.modelname") for case-insensitive
63
63
  matching.
64
64
  """
65
+ aggregate_definitions = data.get("aggregates", {})
66
+ if not isinstance(aggregate_definitions, dict):
67
+ raise BoundariesConfigError(
68
+ f"{path}: define aggregates as named sections, for example "
69
+ "[aggregates.order] with models = [...]"
70
+ )
71
+
65
72
  aggregates_by_model: dict[str, str] = {}
66
- for aggregate, members in data.get("aggregates", {}).items():
67
- if isinstance(members, dict):
68
- members = members.get("models", [])
73
+ for aggregate, definition in aggregate_definitions.items():
74
+ if not isinstance(definition, dict):
75
+ raise BoundariesConfigError(
76
+ f"{path}: aggregate '{aggregate}' must define models in its own "
77
+ "section: "
78
+ f"[aggregates.{aggregate}] with models = [...]"
79
+ )
80
+
81
+ unknown_fields = set(definition) - {"models"}
82
+ if unknown_fields:
83
+ fields = ", ".join(sorted(unknown_fields))
84
+ raise BoundariesConfigError(
85
+ f"{path}: aggregate '{aggregate}' has unknown field(s): {fields}"
86
+ )
87
+
88
+ if "models" not in definition:
89
+ raise BoundariesConfigError(
90
+ f"{path}: aggregate '{aggregate}' is missing required 'models'"
91
+ )
92
+ members = definition["models"]
69
93
  if not isinstance(members, list):
70
94
  raise BoundariesConfigError(
71
- f"{path}: aggregate '{aggregate}' must be a list of model labels"
95
+ f"{path}: aggregate '{aggregate}' models must be a list"
96
+ )
97
+ if not members:
98
+ raise BoundariesConfigError(
99
+ f"{path}: aggregate '{aggregate}' models must not be empty"
72
100
  )
73
101
  for label in members:
74
102
  if not isinstance(label, str):
@@ -90,7 +118,9 @@ def _parse_file_globs(
90
118
  """Read ``[allow]`` and ``[ignore] files`` into a list of glob patterns."""
91
119
  section_data = data.get(section_name, {})
92
120
  if not isinstance(section_data, dict):
93
- raise BoundariesConfigError(f"{path}: [{section_name}] must be a table")
121
+ raise BoundariesConfigError(
122
+ f"{path}: [{section_name}] must be a section with a 'files' list"
123
+ )
94
124
 
95
125
  files = section_data.get("files", [])
96
126
  if not isinstance(files, list):
@@ -88,8 +88,8 @@ def report_stale_ignores(
88
88
  return
89
89
  terminalreporter.section("orm-boundaries: stale ignores", yellow=True, bold=True)
90
90
  terminalreporter.write_line(
91
- "These [ignore] entries no longer suppress any boundary crossing - "
92
- f"their files are clean now. Remove them from {CONFIG_FILE_NAME}:",
91
+ "These [ignore] entries matched files that ran without crossing a "
92
+ f"boundary. Remove them from {CONFIG_FILE_NAME}:",
93
93
  yellow=True,
94
94
  )
95
95
  for pattern in stale: