pytest-httpchain 0.14.0__tar.gz → 0.14.2__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 (62) hide show
  1. {pytest_httpchain-0.14.0 → pytest_httpchain-0.14.2}/PKG-INFO +17 -9
  2. {pytest_httpchain-0.14.0 → pytest_httpchain-0.14.2}/README.md +13 -4
  3. {pytest_httpchain-0.14.0 → pytest_httpchain-0.14.2}/pyproject.toml +56 -41
  4. pytest_httpchain-0.14.2/pyproject.toml.orig +205 -0
  5. pytest_httpchain-0.14.2/src/pytest_httpchain/__init__.py +9 -0
  6. pytest_httpchain-0.14.2/src/pytest_httpchain/carrier.py +662 -0
  7. {pytest_httpchain-0.14.0 → pytest_httpchain-0.14.2}/src/pytest_httpchain/cli.py +28 -17
  8. pytest_httpchain-0.14.2/src/pytest_httpchain/constants.py +23 -0
  9. {pytest_httpchain-0.14.0 → pytest_httpchain-0.14.2}/src/pytest_httpchain/dataflow.py +32 -35
  10. {pytest_httpchain-0.14.0 → pytest_httpchain-0.14.2}/src/pytest_httpchain/errors.py +11 -12
  11. pytest_httpchain-0.14.2/src/pytest_httpchain/factory.py +136 -0
  12. {pytest_httpchain-0.14.0 → pytest_httpchain-0.14.2}/src/pytest_httpchain/har_writer.py +46 -96
  13. pytest_httpchain-0.14.2/src/pytest_httpchain/jsonref/__init__.py +18 -0
  14. pytest_httpchain-0.14.2/src/pytest_httpchain/jsonref/exceptions.py +10 -0
  15. pytest_httpchain-0.14.2/src/pytest_httpchain/jsonref/loader.py +23 -0
  16. pytest_httpchain-0.14.2/src/pytest_httpchain/jsonref/plumbing/circular.py +47 -0
  17. pytest_httpchain-0.14.2/src/pytest_httpchain/jsonref/plumbing/path.py +81 -0
  18. {pytest_httpchain-0.14.0 → pytest_httpchain-0.14.2}/src/pytest_httpchain/jsonref/plumbing/reference.py +35 -89
  19. {pytest_httpchain-0.14.0 → pytest_httpchain-0.14.2}/src/pytest_httpchain/models/__init__.py +2 -1
  20. {pytest_httpchain-0.14.0 → pytest_httpchain-0.14.2}/src/pytest_httpchain/models/entities.py +41 -112
  21. {pytest_httpchain-0.14.0 → pytest_httpchain-0.14.2}/src/pytest_httpchain/models/types.py +45 -102
  22. pytest_httpchain-0.14.2/src/pytest_httpchain/plugin.py +404 -0
  23. pytest_httpchain-0.14.2/src/pytest_httpchain/report_formatter.py +72 -0
  24. pytest_httpchain-0.14.2/src/pytest_httpchain/request_builder.py +131 -0
  25. pytest_httpchain-0.14.2/src/pytest_httpchain/response_steps.py +205 -0
  26. {pytest_httpchain-0.14.0 → pytest_httpchain-0.14.2}/src/pytest_httpchain/schema.py +27 -45
  27. pytest_httpchain-0.14.2/src/pytest_httpchain/scoping.py +378 -0
  28. pytest_httpchain-0.14.2/src/pytest_httpchain/templates/__init__.py +23 -0
  29. pytest_httpchain-0.14.2/src/pytest_httpchain/templates/expressions.py +30 -0
  30. pytest_httpchain-0.14.2/src/pytest_httpchain/templates/substitution.py +192 -0
  31. pytest_httpchain-0.14.2/src/pytest_httpchain/userfunc.py +113 -0
  32. pytest_httpchain-0.14.2/src/pytest_httpchain/utils.py +118 -0
  33. pytest_httpchain-0.14.2/src/pytest_httpchain/validation/__init__.py +31 -0
  34. pytest_httpchain-0.14.2/src/pytest_httpchain/validation/deep.py +187 -0
  35. pytest_httpchain-0.14.2/src/pytest_httpchain/validation/diagnostics.py +92 -0
  36. pytest_httpchain-0.14.2/src/pytest_httpchain/validation/loader.py +99 -0
  37. pytest_httpchain-0.14.2/src/pytest_httpchain/validation/semantic.py +493 -0
  38. pytest_httpchain-0.14.2/src/pytest_httpchain/validation/validate.py +88 -0
  39. pytest_httpchain-0.14.2/src/pytest_httpchain/warnings.py +17 -0
  40. pytest_httpchain-0.14.0/src/pytest_httpchain/__init__.py +0 -30
  41. pytest_httpchain-0.14.0/src/pytest_httpchain/carrier.py +0 -988
  42. pytest_httpchain-0.14.0/src/pytest_httpchain/constants.py +0 -47
  43. pytest_httpchain-0.14.0/src/pytest_httpchain/factory.py +0 -152
  44. pytest_httpchain-0.14.0/src/pytest_httpchain/jsonref/__init__.py +0 -25
  45. pytest_httpchain-0.14.0/src/pytest_httpchain/jsonref/exceptions.py +0 -14
  46. pytest_httpchain-0.14.0/src/pytest_httpchain/jsonref/loader.py +0 -40
  47. pytest_httpchain-0.14.0/src/pytest_httpchain/jsonref/plumbing/circular.py +0 -86
  48. pytest_httpchain-0.14.0/src/pytest_httpchain/jsonref/plumbing/path.py +0 -115
  49. pytest_httpchain-0.14.0/src/pytest_httpchain/plugin.py +0 -433
  50. pytest_httpchain-0.14.0/src/pytest_httpchain/report_formatter.py +0 -92
  51. pytest_httpchain-0.14.0/src/pytest_httpchain/scoping.py +0 -441
  52. pytest_httpchain-0.14.0/src/pytest_httpchain/templates/__init__.py +0 -29
  53. pytest_httpchain-0.14.0/src/pytest_httpchain/templates/expressions.py +0 -37
  54. pytest_httpchain-0.14.0/src/pytest_httpchain/templates/substitution.py +0 -222
  55. pytest_httpchain-0.14.0/src/pytest_httpchain/userfunc.py +0 -170
  56. pytest_httpchain-0.14.0/src/pytest_httpchain/utils.py +0 -98
  57. pytest_httpchain-0.14.0/src/pytest_httpchain/validation.py +0 -1092
  58. pytest_httpchain-0.14.0/src/pytest_httpchain/warnings.py +0 -25
  59. {pytest_httpchain-0.14.0 → pytest_httpchain-0.14.2}/LICENSE +0 -0
  60. {pytest_httpchain-0.14.0 → pytest_httpchain-0.14.2}/src/pytest_httpchain/jsonref/plumbing/__init__.py +0 -0
  61. {pytest_httpchain-0.14.0 → pytest_httpchain-0.14.2}/src/pytest_httpchain/py.typed +0 -0
  62. {pytest_httpchain-0.14.0 → pytest_httpchain-0.14.2}/src/pytest_httpchain/templates/exceptions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytest-httpchain
3
- Version: 0.14.0
3
+ Version: 0.14.2
4
4
  Summary: pytest plugin for HTTP testing using JSON files
5
5
  Keywords: testing,pytest,httpx,http,api,integration-testing
6
6
  Author: Alexander Eresov
@@ -10,11 +10,11 @@ License-File: LICENSE
10
10
  Classifier: Development Status :: 5 - Production/Stable
11
11
  Classifier: Framework :: Pytest
12
12
  Classifier: Intended Audience :: Developers
13
- Classifier: License :: OSI Approved :: MIT License
14
13
  Classifier: Programming Language :: Python :: 3
15
14
  Classifier: Programming Language :: Python :: 3.13
16
15
  Classifier: Programming Language :: Python :: 3.14
17
16
  Classifier: Topic :: Software Development :: Testing
17
+ Classifier: Typing :: Typed
18
18
  Requires-Dist: deepmerge>=2.0
19
19
  Requires-Dist: graphql-core>=3.0.0
20
20
  Requires-Dist: httpx[http2]>=0.27.0
@@ -24,18 +24,19 @@ Requires-Dist: pydantic>=2.13.4
24
24
  Requires-Dist: referencing>=0.28.4
25
25
  Requires-Dist: pyrate-limiter>=4.2.0
26
26
  Requires-Dist: pytest>=9.0
27
- Requires-Dist: pytest-order>=1.3.0
28
27
  Requires-Dist: simpleeval>=1.0.3
29
28
  Requires-Dist: typer>=0.16.0
30
29
  Requires-Python: >=3.13
31
- Project-URL: Changelog, https://github.com/aeresov/pytest-httpchain/blob/main/CHANGELOG.md
32
- Project-URL: Documentation, https://aeresov.github.io/pytest-httpchain/
33
30
  Project-URL: Repository, https://github.com/aeresov/pytest-httpchain
31
+ Project-URL: Documentation, https://aeresov.github.io/pytest-httpchain/
32
+ Project-URL: Changelog, https://github.com/aeresov/pytest-httpchain/blob/main/CHANGELOG.md
34
33
  Description-Content-Type: text/markdown
35
34
 
36
35
  [![image](https://img.shields.io/pypi/v/pytest-httpchain)](https://pypi.python.org/pypi/pytest-httpchain)
37
36
  [![image](https://img.shields.io/pypi/l/pytest-httpchain)](https://github.com/aeresov/pytest-httpchain/blob/main/LICENSE)
38
37
  [![image](https://img.shields.io/pypi/pyversions/pytest-httpchain)](https://pypi.python.org/pypi/pytest-httpchain)
38
+ [![Test](https://github.com/aeresov/pytest-httpchain/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/aeresov/pytest-httpchain/actions/workflows/test.yml)
39
+ [![Docs](https://github.com/aeresov/pytest-httpchain/actions/workflows/docs.yml/badge.svg?branch=main)](https://aeresov.github.io/pytest-httpchain/)
39
40
 
40
41
  # pytest-httpchain
41
42
 
@@ -73,7 +74,7 @@ Each scenario contains 1+ stages executed in order. One stage failure stops the
73
74
 
74
75
  ### Common data context
75
76
 
76
- A key-value store persists throughout scenario execution. Variables, fixtures, and saved response data all live here. Use template expressions (`{{ var }}`) anywhere in your requests — substitution happens dynamically before each stage.
77
+ A key-value store persists throughout scenario execution. Variables, fixtures, and saved response data all live here. Use template expressions (`{{ var }}`) in any request **value** — substitution happens dynamically before each stage. (Dict keys are not substituted; `HTTPCHAIN029` flags a template in a key.)
77
78
 
78
79
  ### Response processing
79
80
 
@@ -199,7 +200,7 @@ pip install 'git+https://github.com/aeresov/pytest-httpchain@main'
199
200
 
200
201
  - Test file discovery is based on this name pattern: `test_<name>.<suffix>.json`.
201
202
  The suffix is configurable via the `httpchain_suffix` pytest ini option, default value is **http**.
202
- - `$ref` instructions can point to other files using relative paths; absolute paths are rejected for security.
203
+ - `$ref` instructions can point to other files using relative paths; absolute paths are rejected for security, and every reference must resolve inside the root path (pytest's `rootdir` when collecting; `--root-path` for the CLI).
203
204
  You can limit the depth of relative path traversal using the `httpchain_ref_parent_traversal_depth` ini option, default value is **3**.
204
205
  - Template expressions support list/dict comprehensions. You can limit the maximum comprehension length using the `httpchain_max_comprehension_length` ini option, default value is **50000**.
205
206
  - Parallel stage iterations (repeat/foreach) have a safety limit configurable via the `httpchain_max_parallel_iterations` ini option, default value is **10000**.
@@ -250,7 +251,15 @@ A JSON Schema is published for as-you-type validation and autocomplete. Referenc
250
251
  }
251
252
  ```
252
253
 
253
- The hosted schema tracks the `main` branch (it is redeployed on every push, so it may describe unreleased changes); to pin the schema matching your installed version (e.g. for CI), emit it locally:
254
+ The hosted schema at the unversioned URL tracks the `main` branch (it is redeployed on every push, so it may describe unreleased changes). To pin the schema for a release, use its versioned URL:
255
+
256
+ ```json
257
+ {
258
+ "$schema": "https://aeresov.github.io/pytest-httpchain/schema/v0.14.0/scenario.schema.json"
259
+ }
260
+ ```
261
+
262
+ or emit the schema matching your installed version locally:
254
263
 
255
264
  ```bash
256
265
  uvx pytest-httpchain schema > scenario.schema.json
@@ -283,5 +292,4 @@ This project was inspired by [Tavern](https://github.com/taverntesting/tavern) a
283
292
  [httpx](https://www.python-httpx.org) does comms.
284
293
  [Pydantic](https://docs.pydantic.dev) keeps structure.
285
294
  [simpleeval](https://github.com/danthedeckie/simpleeval) powers templates.
286
- [pytest-order](https://github.com/pytest-dev/pytest-order) sorts chain.
287
295
  [pytest-datadir](https://github.com/gabrielcnr/pytest-datadir) saved me a lot of elbow grease while testing.
@@ -1,6 +1,8 @@
1
1
  [![image](https://img.shields.io/pypi/v/pytest-httpchain)](https://pypi.python.org/pypi/pytest-httpchain)
2
2
  [![image](https://img.shields.io/pypi/l/pytest-httpchain)](https://github.com/aeresov/pytest-httpchain/blob/main/LICENSE)
3
3
  [![image](https://img.shields.io/pypi/pyversions/pytest-httpchain)](https://pypi.python.org/pypi/pytest-httpchain)
4
+ [![Test](https://github.com/aeresov/pytest-httpchain/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/aeresov/pytest-httpchain/actions/workflows/test.yml)
5
+ [![Docs](https://github.com/aeresov/pytest-httpchain/actions/workflows/docs.yml/badge.svg?branch=main)](https://aeresov.github.io/pytest-httpchain/)
4
6
 
5
7
  # pytest-httpchain
6
8
 
@@ -38,7 +40,7 @@ Each scenario contains 1+ stages executed in order. One stage failure stops the
38
40
 
39
41
  ### Common data context
40
42
 
41
- A key-value store persists throughout scenario execution. Variables, fixtures, and saved response data all live here. Use template expressions (`{{ var }}`) anywhere in your requests — substitution happens dynamically before each stage.
43
+ A key-value store persists throughout scenario execution. Variables, fixtures, and saved response data all live here. Use template expressions (`{{ var }}`) in any request **value** — substitution happens dynamically before each stage. (Dict keys are not substituted; `HTTPCHAIN029` flags a template in a key.)
42
44
 
43
45
  ### Response processing
44
46
 
@@ -164,7 +166,7 @@ pip install 'git+https://github.com/aeresov/pytest-httpchain@main'
164
166
 
165
167
  - Test file discovery is based on this name pattern: `test_<name>.<suffix>.json`.
166
168
  The suffix is configurable via the `httpchain_suffix` pytest ini option, default value is **http**.
167
- - `$ref` instructions can point to other files using relative paths; absolute paths are rejected for security.
169
+ - `$ref` instructions can point to other files using relative paths; absolute paths are rejected for security, and every reference must resolve inside the root path (pytest's `rootdir` when collecting; `--root-path` for the CLI).
168
170
  You can limit the depth of relative path traversal using the `httpchain_ref_parent_traversal_depth` ini option, default value is **3**.
169
171
  - Template expressions support list/dict comprehensions. You can limit the maximum comprehension length using the `httpchain_max_comprehension_length` ini option, default value is **50000**.
170
172
  - Parallel stage iterations (repeat/foreach) have a safety limit configurable via the `httpchain_max_parallel_iterations` ini option, default value is **10000**.
@@ -215,7 +217,15 @@ A JSON Schema is published for as-you-type validation and autocomplete. Referenc
215
217
  }
216
218
  ```
217
219
 
218
- The hosted schema tracks the `main` branch (it is redeployed on every push, so it may describe unreleased changes); to pin the schema matching your installed version (e.g. for CI), emit it locally:
220
+ The hosted schema at the unversioned URL tracks the `main` branch (it is redeployed on every push, so it may describe unreleased changes). To pin the schema for a release, use its versioned URL:
221
+
222
+ ```json
223
+ {
224
+ "$schema": "https://aeresov.github.io/pytest-httpchain/schema/v0.14.0/scenario.schema.json"
225
+ }
226
+ ```
227
+
228
+ or emit the schema matching your installed version locally:
219
229
 
220
230
  ```bash
221
231
  uvx pytest-httpchain schema > scenario.schema.json
@@ -248,5 +258,4 @@ This project was inspired by [Tavern](https://github.com/taverntesting/tavern) a
248
258
  [httpx](https://www.python-httpx.org) does comms.
249
259
  [Pydantic](https://docs.pydantic.dev) keeps structure.
250
260
  [simpleeval](https://github.com/danthedeckie/simpleeval) powers templates.
251
- [pytest-order](https://github.com/pytest-dev/pytest-order) sorts chain.
252
261
  [pytest-datadir](https://github.com/gabrielcnr/pytest-datadir) saved me a lot of elbow grease while testing.
@@ -1,10 +1,9 @@
1
1
  [project]
2
2
  name = "pytest-httpchain"
3
- version = "0.14.0"
3
+ version = "0.14.2"
4
4
  description = "pytest plugin for HTTP testing using JSON files"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
7
- authors = [{ name = "Alexander Eresov", email = "aeresov@gmail.com" }]
8
7
  dependencies = [
9
8
  "deepmerge>=2.0",
10
9
  "graphql-core>=3.0.0",
@@ -15,39 +14,56 @@ dependencies = [
15
14
  "referencing>=0.28.4",
16
15
  "pyrate-limiter>=4.2.0",
17
16
  "pytest>=9.0",
18
- "pytest-order>=1.3.0",
19
17
  "simpleeval>=1.0.3",
20
18
  "typer>=0.16.0",
21
19
  ]
22
- keywords = ["testing", "pytest", "httpx", "http", "api", "integration-testing"]
20
+ keywords = [
21
+ "testing",
22
+ "pytest",
23
+ "httpx",
24
+ "http",
25
+ "api",
26
+ "integration-testing",
27
+ ]
23
28
  license = "MIT"
24
29
  license-files = ["LICENSE"]
25
30
  classifiers = [
26
31
  "Development Status :: 5 - Production/Stable",
27
32
  "Framework :: Pytest",
28
33
  "Intended Audience :: Developers",
29
- "License :: OSI Approved :: MIT License",
30
34
  "Programming Language :: Python :: 3",
31
35
  "Programming Language :: Python :: 3.13",
32
36
  "Programming Language :: Python :: 3.14",
33
37
  "Topic :: Software Development :: Testing",
38
+ "Typing :: Typed",
34
39
  ]
35
40
 
41
+ [[project.authors]]
42
+ name = "Alexander Eresov"
43
+ email = "aeresov@gmail.com"
44
+
36
45
  [project.urls]
37
46
  Repository = "https://github.com/aeresov/pytest-httpchain"
38
47
  Documentation = "https://aeresov.github.io/pytest-httpchain/"
39
48
  Changelog = "https://github.com/aeresov/pytest-httpchain/blob/main/CHANGELOG.md"
40
49
 
50
+ [project.scripts]
51
+ pytest-httpchain = "pytest_httpchain.cli:app"
52
+
53
+ [project.entry-points.pytest11]
54
+ pytest_httpchain = "pytest_httpchain.plugin"
55
+
41
56
  [dependency-groups]
42
57
  dev = [
58
+ "flask>=3.0",
43
59
  "flask-httpauth>=4.8.0",
44
- "http-server-mock>=1.7",
45
60
  "import-linter>=2.0",
46
61
  "ruff>=0.12.1",
47
- "pytest-cov>=6.0.0",
48
- "coverage[toml]>=7.0",
62
+ "coverage[toml]>=7.10",
49
63
  "pytest-datadir>=1.7.2",
64
+ "pytest-order>=1.3.0",
50
65
  "pytest-xdist>=3.8.0",
66
+ "trustme>=1.1.0",
51
67
  ]
52
68
  docs = [
53
69
  "mkdocs>=1.6.1",
@@ -55,21 +71,14 @@ docs = [
55
71
  ]
56
72
 
57
73
  [tool.uv]
58
- default-groups = ["dev", "docs"]
59
-
60
- [build-system]
61
- requires = ["uv_build>=0.7.21,<0.8.0"]
62
- build-backend = "uv_build"
74
+ default-groups = [
75
+ "dev",
76
+ "docs",
77
+ ]
63
78
 
64
79
  [tool.uv.build-backend]
65
80
  source-exclude = ["src/pytest_httpchain/**/CLAUDE.md"]
66
81
 
67
- [project.scripts]
68
- pytest-httpchain = "pytest_httpchain.cli:app"
69
-
70
- [project.entry-points.pytest11]
71
- pytest_httpchain = "pytest_httpchain.plugin"
72
-
73
82
  [tool.ruff]
74
83
  line-length = 180
75
84
  target-version = "py313"
@@ -80,14 +89,15 @@ line-ending = "lf"
80
89
 
81
90
  [tool.ruff.lint]
82
91
  select = [
83
- "E", # pycodestyle errors
84
- "W", # pycodestyle warnings
85
- "F", # pyflakes
86
- "I", # isort
87
- "B", # flake8-bugbear
88
- "C4", # flake8-comprehensions
89
- "UP", # pyupgrade
90
- "TID252", # flake8-tidy-imports: ban relative imports
92
+ "E",
93
+ "W",
94
+ "F",
95
+ "I",
96
+ "B",
97
+ "C4",
98
+ "UP",
99
+ "PT",
100
+ "TID252",
91
101
  ]
92
102
  ignore = []
93
103
 
@@ -103,10 +113,6 @@ python-version = "3.13"
103
113
  [tool.ty.terminal]
104
114
  error-on-warning = true
105
115
 
106
- # Test suites (especially the models package) deliberately construct models with
107
- # invalid types to assert ValidationError, and access attributes on unions/mocks
108
- # the checker can't narrow. Relax those specific rules for test code only; src/
109
- # stays strict and uses pinpoint `# ty: ignore[...]` comments instead.
110
116
  [[tool.ty.overrides]]
111
117
  include = ["tests/**"]
112
118
 
@@ -119,31 +125,38 @@ missing-argument = "ignore"
119
125
 
120
126
  [tool.pytest]
121
127
  minversion = "9.0"
122
- log_cli = true
128
+ strict = true
129
+ log_cli = false
123
130
  log_cli_level = "INFO"
124
131
  norecursedirs = ["examples"]
125
132
  pytester_example_dir = "tests/integration/examples"
126
- addopts = ["--log-disable=werkzeug", "--import-mode=importlib"]
127
- pythonpath = [
128
- ".",
133
+ addopts = [
134
+ "--log-disable=werkzeug",
135
+ "--import-mode=importlib",
129
136
  ]
137
+ pythonpath = ["."]
130
138
  testpaths = ["tests"]
139
+ markers = ["slow: subprocess-spawning or sleep-bound integration test; deselect with -m 'not slow'"]
140
+ filterwarnings = [
141
+ "error",
142
+ "default::pytest_httpchain.ScenarioValidationWarning",
143
+ ]
131
144
 
132
145
  [tool.coverage.run]
133
146
  source = ["src"]
134
147
  branch = true
148
+ parallel = true
149
+ patch = ["subprocess"]
135
150
 
136
151
  [tool.coverage.report]
137
152
  precision = 2
138
153
  show_missing = true
139
154
  skip_covered = false
155
+ fail_under = 88
140
156
 
141
157
  [tool.importlinter]
142
158
  root_package = "pytest_httpchain"
143
159
 
144
- # Exhaustive vertical layering: every top-level module must appear in exactly one
145
- # layer; a new module fails lint-imports until it is placed. Modules on the same
146
- # layer (separated by ":") are additionally kept mutually independent.
147
160
  [[tool.importlinter.contracts]]
148
161
  name = "package layering"
149
162
  type = "layers"
@@ -153,7 +166,7 @@ layers = [
153
166
  "plugin : cli",
154
167
  "factory",
155
168
  "carrier : dataflow : schema",
156
- "validation : har_writer : report_formatter",
169
+ "validation : har_writer : report_formatter : request_builder : response_steps",
157
170
  "utils : scoping",
158
171
  "userfunc",
159
172
  "models",
@@ -161,10 +174,12 @@ layers = [
161
174
  "errors : constants : warnings",
162
175
  ]
163
176
 
164
- # Horizontal refinement the layers contract cannot express: models sits above the
165
- # domain trio and may import templates/userfunc, but must never import jsonref.
166
177
  [[tool.importlinter.contracts]]
167
178
  name = "models does not import jsonref"
168
179
  type = "forbidden"
169
180
  source_modules = ["pytest_httpchain.models"]
170
181
  forbidden_modules = ["pytest_httpchain.jsonref"]
182
+
183
+ [build-system]
184
+ requires = ["uv_build>=0.11,<0.12"]
185
+ build-backend = "uv_build"
@@ -0,0 +1,205 @@
1
+ [project]
2
+ name = "pytest-httpchain"
3
+ version = "0.14.2"
4
+ description = "pytest plugin for HTTP testing using JSON files"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ authors = [{ name = "Alexander Eresov", email = "aeresov@gmail.com" }]
8
+ dependencies = [
9
+ "deepmerge>=2.0",
10
+ "graphql-core>=3.0.0",
11
+ "httpx[http2]>=0.27.0",
12
+ "jmespath>=1.0.0",
13
+ "jsonschema>=4.18.0",
14
+ "pydantic>=2.13.4",
15
+ "referencing>=0.28.4",
16
+ "pyrate-limiter>=4.2.0",
17
+ "pytest>=9.0",
18
+ "simpleeval>=1.0.3",
19
+ "typer>=0.16.0",
20
+ ]
21
+ keywords = ["testing", "pytest", "httpx", "http", "api", "integration-testing"]
22
+ license = "MIT"
23
+ license-files = ["LICENSE"]
24
+ classifiers = [
25
+ "Development Status :: 5 - Production/Stable",
26
+ "Framework :: Pytest",
27
+ "Intended Audience :: Developers",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python :: 3.13",
30
+ "Programming Language :: Python :: 3.14",
31
+ "Topic :: Software Development :: Testing",
32
+ "Typing :: Typed",
33
+ ]
34
+
35
+ [project.urls]
36
+ Repository = "https://github.com/aeresov/pytest-httpchain"
37
+ Documentation = "https://aeresov.github.io/pytest-httpchain/"
38
+ Changelog = "https://github.com/aeresov/pytest-httpchain/blob/main/CHANGELOG.md"
39
+
40
+ [dependency-groups]
41
+ dev = [
42
+ "flask>=3.0",
43
+ "flask-httpauth>=4.8.0",
44
+ "import-linter>=2.0",
45
+ "ruff>=0.12.1",
46
+ "coverage[toml]>=7.10", # `patch = ["subprocess"]` needs 7.10
47
+
48
+ "pytest-datadir>=1.7.2",
49
+ # Not a runtime dependency: the plugin's own collection hooks own stage
50
+ # ordering. Installed in dev so the ordering regression tests can exercise
51
+ # the regroup's defense against a user-installed pytest-order.
52
+ "pytest-order>=1.3.0",
53
+ "pytest-xdist>=3.8.0",
54
+ "trustme>=1.1.0",
55
+ ]
56
+ docs = [
57
+ "mkdocs>=1.6.1",
58
+ "mkdocs-material>=9.6.16",
59
+ ]
60
+
61
+ [tool.uv]
62
+ default-groups = ["dev", "docs"]
63
+
64
+ [build-system]
65
+ requires = ["uv_build>=0.11,<0.12"]
66
+ build-backend = "uv_build"
67
+
68
+ [tool.uv.build-backend]
69
+ source-exclude = ["src/pytest_httpchain/**/CLAUDE.md"]
70
+
71
+ [project.scripts]
72
+ pytest-httpchain = "pytest_httpchain.cli:app"
73
+
74
+ [project.entry-points.pytest11]
75
+ pytest_httpchain = "pytest_httpchain.plugin"
76
+
77
+ [tool.ruff]
78
+ line-length = 180
79
+ target-version = "py313"
80
+
81
+ [tool.ruff.format]
82
+ quote-style = "double"
83
+ line-ending = "lf"
84
+
85
+ [tool.ruff.lint]
86
+ select = [
87
+ "E", # pycodestyle errors
88
+ "W", # pycodestyle warnings
89
+ "F", # pyflakes
90
+ "I", # isort
91
+ "B", # flake8-bugbear
92
+ "C4", # flake8-comprehensions
93
+ "UP", # pyupgrade
94
+ "PT", # flake8-pytest-style
95
+ "TID252", # flake8-tidy-imports: ban relative imports
96
+ ]
97
+ ignore = []
98
+
99
+ [tool.ruff.lint.flake8-tidy-imports]
100
+ ban-relative-imports = "all"
101
+
102
+ [tool.ruff.lint.isort]
103
+ known-first-party = ["tests"]
104
+
105
+ [tool.ty.environment]
106
+ python-version = "3.13"
107
+
108
+ [tool.ty.terminal]
109
+ error-on-warning = true
110
+
111
+ # Test suites (especially the models package) deliberately construct models with
112
+ # invalid types to assert ValidationError, and access attributes on unions/mocks
113
+ # the checker can't narrow. Relax those specific rules for test code only; src/
114
+ # stays strict and uses pinpoint `# ty: ignore[...]` comments instead.
115
+ [[tool.ty.overrides]]
116
+ include = ["tests/**"]
117
+
118
+ [tool.ty.overrides.rules]
119
+ invalid-argument-type = "ignore"
120
+ unknown-argument = "ignore"
121
+ unresolved-attribute = "ignore"
122
+ not-subscriptable = "ignore"
123
+ missing-argument = "ignore"
124
+
125
+ [tool.pytest]
126
+ minversion = "9.0"
127
+ # pytest 9 umbrella: strict_config, strict_markers, strict_xfail,
128
+ # strict_parametrization_ids — unknown ini keys and unregistered markers
129
+ # become errors instead of warnings.
130
+ strict = true
131
+ # Logs are captured and shown only for failing tests (pytest's default). Streaming
132
+ # live INFO for every passing test buried real output ~10-27x; opt back in per-run
133
+ # with `-o log_cli=true --log-cli-level=INFO` when debugging.
134
+ log_cli = false
135
+ log_cli_level = "INFO"
136
+ norecursedirs = ["examples"]
137
+ pytester_example_dir = "tests/integration/examples"
138
+ addopts = ["--log-disable=werkzeug", "--import-mode=importlib"]
139
+ pythonpath = [
140
+ ".",
141
+ ]
142
+ testpaths = ["tests"]
143
+ markers = [
144
+ "slow: subprocess-spawning or sleep-bound integration test; deselect with -m 'not slow'",
145
+ ]
146
+ # Treat warnings as errors so dependency deprecations (e.g. across the 3.13/3.14
147
+ # and lowest-floor matrix) fail loudly instead of rotting. The plugin's own
148
+ # ScenarioValidationWarning is intentional behavior that many tests assert on,
149
+ # so it stays a warning; individual tests still escalate it via -W when needed.
150
+ filterwarnings = [
151
+ "error",
152
+ "default::pytest_httpchain.ScenarioValidationWarning",
153
+ ]
154
+
155
+ [tool.coverage.run]
156
+ source = ["src"]
157
+ branch = true
158
+ # Measure pytester subprocess children too (runpytest_subprocess families):
159
+ # `patch` injects startup into every child with this config serialized —
160
+ # data_file/source absolutized, so children report into this project's data
161
+ # files regardless of their CWD. Requires the pid-suffixed parallel data
162
+ # files; run `coverage combine` before `coverage report`.
163
+ parallel = true
164
+ patch = ["subprocess"]
165
+
166
+ [tool.coverage.report]
167
+ precision = 2
168
+ show_missing = true
169
+ skip_covered = false
170
+ # Regression floor for the CI `coverage report` step (full suite under
171
+ # `coverage run -m pytest`, ~95%). Kept a few points below the real number to
172
+ # tolerate platform/Python-version branch differences; ratchet upward as
173
+ # coverage improves rather than letting it silently erode.
174
+ fail_under = 88
175
+
176
+ [tool.importlinter]
177
+ root_package = "pytest_httpchain"
178
+
179
+ # Exhaustive vertical layering: every top-level module must appear in exactly one
180
+ # layer; a new module fails lint-imports until it is placed. Modules on the same
181
+ # layer (separated by ":") are additionally kept mutually independent.
182
+ [[tool.importlinter.contracts]]
183
+ name = "package layering"
184
+ type = "layers"
185
+ containers = ["pytest_httpchain"]
186
+ exhaustive = true
187
+ layers = [
188
+ "plugin : cli",
189
+ "factory",
190
+ "carrier : dataflow : schema",
191
+ "validation : har_writer : report_formatter : request_builder : response_steps",
192
+ "utils : scoping",
193
+ "userfunc",
194
+ "models",
195
+ "templates : jsonref",
196
+ "errors : constants : warnings",
197
+ ]
198
+
199
+ # Horizontal refinement the layers contract cannot express: models sits above the
200
+ # domain trio and may import templates/userfunc, but must never import jsonref.
201
+ [[tool.importlinter.contracts]]
202
+ name = "models does not import jsonref"
203
+ type = "forbidden"
204
+ source_modules = ["pytest_httpchain.models"]
205
+ forbidden_modules = ["pytest_httpchain.jsonref"]
@@ -0,0 +1,9 @@
1
+ """pytest-httpchain: declarative HTTP API integration testing.
2
+
3
+ Test scenarios are JSON files (``test_<name>.http.json``) with ``$ref`` support,
4
+ ``{{ expr }}`` templates, and multi-stage request chaining.
5
+ """
6
+
7
+ from pytest_httpchain.warnings import ScenarioValidationWarning
8
+
9
+ __all__ = ["ScenarioValidationWarning"]