pytest-orm-boundaries 0.6.0__tar.gz → 0.7.0__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.0}/PKG-INFO +12 -7
  2. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.0}/README.md +11 -6
  3. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.0}/pyproject.toml +1 -1
  4. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.0}/src/pytest_orm_boundaries/config.py +35 -5
  5. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.0}/src/pytest_orm_boundaries/report.py +2 -2
  6. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.0}/LICENSE +0 -0
  7. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.0}/src/pytest_orm_boundaries/__init__.py +0 -0
  8. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.0}/src/pytest_orm_boundaries/allows.py +0 -0
  9. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.0}/src/pytest_orm_boundaries/callstack.py +0 -0
  10. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.0}/src/pytest_orm_boundaries/crossings.py +0 -0
  11. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.0}/src/pytest_orm_boundaries/guard.py +0 -0
  12. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.0}/src/pytest_orm_boundaries/ignores.py +0 -0
  13. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.0}/src/pytest_orm_boundaries/model_resolution.py +0 -0
  14. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.0}/src/pytest_orm_boundaries/plugin.py +0 -0
  15. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.0}/src/pytest_orm_boundaries/prefetch_resolution.py +0 -0
  16. {pytest_orm_boundaries-0.6.0 → pytest_orm_boundaries-0.7.0}/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.0
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
@@ -60,14 +60,19 @@ pytest discovers the plugin automatically.
60
60
 
61
61
  ## Configure
62
62
 
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):
63
+ Declare your aggregates and their Django models in `boundaries.toml` at the project
64
+ root (or point at the file with `--boundaries-config` / the
65
+ `boundaries_config` ini option):
65
66
 
66
67
  ```toml
67
- [aggregates]
68
- client = ["bookshop.Client"]
69
- book = ["bookshop.Book"]
70
- purchase = ["bookshop.Purchase", "bookshop.PurchaseLine"]
68
+ [aggregates.client]
69
+ models = ["bookshop.Client"]
70
+
71
+ [aggregates.book]
72
+ models = ["bookshop.Book"]
73
+
74
+ [aggregates.purchase]
75
+ models = ["bookshop.Purchase", "bookshop.PurchaseLine"]
71
76
  ```
72
77
 
73
78
  Models are written as `app_label.Model`. Models not listed in any aggregate are
@@ -31,14 +31,19 @@ pytest discovers the plugin automatically.
31
31
 
32
32
  ## Configure
33
33
 
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):
34
+ Declare your aggregates and their Django models in `boundaries.toml` at the project
35
+ root (or point at the file with `--boundaries-config` / the
36
+ `boundaries_config` ini option):
36
37
 
37
38
  ```toml
38
- [aggregates]
39
- client = ["bookshop.Client"]
40
- book = ["bookshop.Book"]
41
- purchase = ["bookshop.Purchase", "bookshop.PurchaseLine"]
39
+ [aggregates.client]
40
+ models = ["bookshop.Client"]
41
+
42
+ [aggregates.book]
43
+ models = ["bookshop.Book"]
44
+
45
+ [aggregates.purchase]
46
+ models = ["bookshop.Purchase", "bookshop.PurchaseLine"]
42
47
  ```
43
48
 
44
49
  Models are written as `app_label.Model`. Models not listed in any aggregate are
@@ -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.0"
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: