pytest-httpchain 0.1.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 aeresov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,245 @@
1
+ Metadata-Version: 2.4
2
+ Name: pytest-httpchain
3
+ Version: 0.1.0
4
+ Summary: pytest plugin for HTTP testing using JSON files
5
+ Keywords: testing,pytest,requests
6
+ Author: Alexander Eresov
7
+ Author-email: Alexander Eresov <aeresov@gmail.com>
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Framework :: Pytest
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Software Development :: Testing
16
+ Requires-Dist: pydantic>=2.11.7
17
+ Requires-Dist: pytest-httpchain-jsonref
18
+ Requires-Dist: pytest-httpchain-models
19
+ Requires-Dist: pytest-order>=1.3.0
20
+ Requires-Dist: rich>=13.7.0
21
+ Requires-Dist: pytest-httpchain-mcp ; extra == 'mcp'
22
+ Requires-Python: >=3.13, <4.0
23
+ Provides-Extra: mcp
24
+ Description-Content-Type: text/markdown
25
+
26
+ [![image](https://img.shields.io/pypi/v/pytest-httpchain)](https://pypi.python.org/pypi/pytest-httpchain)
27
+ [![image](https://img.shields.io/pypi/l/pytest-httpchain)](https://github.com/aeresov/pytest-httpchain/blob/main/LICENSE)
28
+ [![image](https://img.shields.io/pypi/pyversions/pytest-httpchain)](https://pypi.python.org/pypi/pytest-httpchain)
29
+
30
+ # pytest-httpchain
31
+
32
+ A pytest plugin for testing HTTP endpoints.
33
+
34
+ ## Overview
35
+
36
+ `pytest-httpchain` is an integration testing framework for HTTP APIs based on battle-hardened [requests](https://requests.readthedocs.io) lib.\
37
+ It aims at helping with common HTTP API testing scenarios, where user needs to make several calls in specific order using data obtained along the way, like auth tokens or resource ids.
38
+
39
+ ## Installation
40
+
41
+ Install normally via package manager of your choice from PyPi:
42
+
43
+ ```bash
44
+ pip install pytest-httpchain
45
+ ```
46
+
47
+ or directly from Github, in case you need a particular ref:
48
+
49
+ ```bash
50
+ pip install 'git+https://github.com/aeresov/pytest-httpchain@main'
51
+ ```
52
+
53
+ ### Optional dependencies
54
+
55
+ The following optional dependencies are available:
56
+
57
+ - `mcp`: installs MCP server package and its starting script. Details in [MCP Server](#mcp-server).
58
+
59
+ ## Features
60
+
61
+ ### Pytest integration
62
+
63
+ Most of pytest magic can be used: markers, fixtures, other plugins.\
64
+
65
+ > NOTE: parametrization is not yet implemented, therefore `parametrize` marker won't have any effect.
66
+
67
+ ### Declarative format
68
+
69
+ Test scenarios are written declaratively in JSON files.\
70
+ `pytest-httpchain` supports JSONRef, so use can reuse arbitrary parts of your scenarios with `$ref` directive.\
71
+ Properties are merged in a greedy way with type checking.
72
+
73
+ ### Multi-stage tests
74
+
75
+ Each test scenario contains 1+ stages; each stage is a single HTTP call.\
76
+ `pytest-httpchain` executes stages in the order they are listed in scenario file; one stage failure stops the execution chain.
77
+
78
+ ### Common data context and variable substitution
79
+
80
+ `pytest-httpchain` maintains key-value data storage throughout the execution.\
81
+ This storage ("common data context") is populated with declared variables, fixtures and data saved by stages. The data remains there throughout the scenario execution.\
82
+ Writing scenarios, you can use Jinja-style expressions like `"{{ var }}"` for JSON values. `pytest-httpchain` does variable substitution dynamically right before executing a stage, and uses common data context keys as variables in these expressions.\
83
+ Values from common data context also might be verified during verified/asserted.
84
+
85
+ ### User functions
86
+
87
+ `pytest-httpchain` can import and call regular python functions:
88
+
89
+ - to extract data from HTTP response
90
+ - to verify HTTP response and values in common data context
91
+ - to provide [custom authentication for requests](https://requests.readthedocs.io/en/latest/user/advanced/#custom-authentication)
92
+
93
+ ### JMESPath support
94
+
95
+ `pytest-httpchain` can extract values from JSON responses using JMESPath expressions directly.
96
+
97
+ ### JSON schema support
98
+
99
+ `pytest-httpchain` can verify JSON reponses against user-defined JSON schema.
100
+
101
+ ## Quick Start
102
+
103
+ Create a JSON test file named like `test_<name>.<suffix>.json` (default suffix is `http`):
104
+
105
+ ```python
106
+ # conftest.py
107
+ import pytest
108
+ from datetime import datetime
109
+
110
+ @pytest.fixture
111
+ def now_utc():
112
+ return datetime.now()
113
+ ```
114
+
115
+ ```json
116
+ {
117
+ "vars": {
118
+ "user_id": 1
119
+ },
120
+ "stages": [
121
+ {
122
+ "name": "get_user",
123
+ "request": {
124
+ "url": "https://api.example.com/users/{{ user_id }}"
125
+ },
126
+ "response": [
127
+ {
128
+ "verify": {
129
+ "status": 200
130
+ }
131
+ },
132
+ {
133
+ "save": {
134
+ "vars": {
135
+ "user_name": "user.name"
136
+ }
137
+ }
138
+ }
139
+ ]
140
+ },
141
+ {
142
+ "name": "update_user",
143
+ "fixtures": ["now_utc"],
144
+ "request": {
145
+ "url": "https://api.example.com/users/{{ user_id }}",
146
+ "method": "PUT",
147
+ "body": {
148
+ "json": {
149
+ "user": {
150
+ "name": "{{ user_name }}_updated",
151
+ "timestamp": "{{ str(now_utc) }}"
152
+ }
153
+ }
154
+ }
155
+ },
156
+ "response": [
157
+ {
158
+ "verify": {
159
+ "status": 200
160
+ }
161
+ }
162
+ ]
163
+ },
164
+ {
165
+ "name": "cleanup",
166
+ "always_run": true,
167
+ "request": {
168
+ "url": "https://api.example.com/cleanup",
169
+ "method": "POST"
170
+ }
171
+ }
172
+ ]
173
+ }
174
+ ```
175
+
176
+ Scenario we created:
177
+
178
+ - common data context is seeded with the first variable `user_id`
179
+ - **get_user**\
180
+ url is assembled using `user_id` variable from common data context\
181
+ HTTP GET call is made\
182
+ we verify the call returned code 200\
183
+ assuming JSON body is returned, we extract a value by JMESPath expression `user.name` and save it to common data context under `user_name` key
184
+ - **update_user**\
185
+ `now_utc` fixture value is injected into common data context\
186
+ url is assembled using `user_id` variable from common data context\
187
+ we create JSON body in place using values from common data context, note that `now_utc` is converted to string in place\
188
+ HTTP PUT call with body is made\
189
+ we verify the call returned code 200\
190
+ - **cleanup**\
191
+ finalizing call meant for graceful exit\
192
+ `always_run` parameter means this stage will be executed regardless of errors in previous stages
193
+
194
+ For detailed examples see [USAGE.md](USAGE.md).
195
+
196
+ ## Configuration
197
+
198
+ - Test file discovery is based on this name pattern: `test_<name>.<suffix>.json`.\
199
+ The `suffix` is configurable as pytest ini option, default value is **http**.
200
+ - `$ref` instructions can point to other files; absolute and relative paths are supported.\
201
+ You can limit the depth of relative path traversal using `ref_parent_traversal_depth` ini option, default value is **3**.
202
+
203
+ ## MCP Server
204
+
205
+ `pytest-httpchain` includes an MCP (Model Context Protocol) server to aid AI code assistants.
206
+
207
+ ### Installation
208
+
209
+ The optional dependency `mcp` installs MCP server's package and `pytest-httpchain-mcp` script.\
210
+ Use this script as call target for your MCP configuration.
211
+
212
+ Claude Code `.mcp.json` example:
213
+
214
+ ```json
215
+ {
216
+ "mcpServers": {
217
+ "pytest-httpchain": {
218
+ "type": "stdio",
219
+ "command": "uv",
220
+ "args": ["run", "pytest-httpchain-mcp"],
221
+ "env": {}
222
+ }
223
+ }
224
+ }
225
+ ```
226
+
227
+ ### Features
228
+
229
+ The MCP server provides:
230
+
231
+ - **Scenario validation** - validate test scenario and scan for possible problems
232
+
233
+ ## Documentation
234
+
235
+ - [Usage Examples](USAGE.md) - Practical code examples
236
+ - [Full Documentation](https://aeresov.github.io/pytest-httpchain) - Complete guide
237
+ - [Changelog](CHANGELOG.md) - Release notes
238
+
239
+ ## Thanks
240
+
241
+ `pytest-httpchain` was heavily inspired by [Tavern](https://github.com/taverntesting/tavern) and [pytest-play](https://github.com/davidemoro/pytest-play).
242
+ [requests](https://requests.readthedocs.io) does the comms.
243
+ [Pydantic](https://docs.pydantic.dev) keeps the structure.
244
+ [pytest-order](https://github.com/pytest-dev/pytest-order) powers the chaining.
245
+ [pytest-datadir](https://github.com/gabrielcnr/pytest-datadir) saved me a lot of elbow grease.
@@ -0,0 +1,220 @@
1
+ [![image](https://img.shields.io/pypi/v/pytest-httpchain)](https://pypi.python.org/pypi/pytest-httpchain)
2
+ [![image](https://img.shields.io/pypi/l/pytest-httpchain)](https://github.com/aeresov/pytest-httpchain/blob/main/LICENSE)
3
+ [![image](https://img.shields.io/pypi/pyversions/pytest-httpchain)](https://pypi.python.org/pypi/pytest-httpchain)
4
+
5
+ # pytest-httpchain
6
+
7
+ A pytest plugin for testing HTTP endpoints.
8
+
9
+ ## Overview
10
+
11
+ `pytest-httpchain` is an integration testing framework for HTTP APIs based on battle-hardened [requests](https://requests.readthedocs.io) lib.\
12
+ It aims at helping with common HTTP API testing scenarios, where user needs to make several calls in specific order using data obtained along the way, like auth tokens or resource ids.
13
+
14
+ ## Installation
15
+
16
+ Install normally via package manager of your choice from PyPi:
17
+
18
+ ```bash
19
+ pip install pytest-httpchain
20
+ ```
21
+
22
+ or directly from Github, in case you need a particular ref:
23
+
24
+ ```bash
25
+ pip install 'git+https://github.com/aeresov/pytest-httpchain@main'
26
+ ```
27
+
28
+ ### Optional dependencies
29
+
30
+ The following optional dependencies are available:
31
+
32
+ - `mcp`: installs MCP server package and its starting script. Details in [MCP Server](#mcp-server).
33
+
34
+ ## Features
35
+
36
+ ### Pytest integration
37
+
38
+ Most of pytest magic can be used: markers, fixtures, other plugins.\
39
+
40
+ > NOTE: parametrization is not yet implemented, therefore `parametrize` marker won't have any effect.
41
+
42
+ ### Declarative format
43
+
44
+ Test scenarios are written declaratively in JSON files.\
45
+ `pytest-httpchain` supports JSONRef, so use can reuse arbitrary parts of your scenarios with `$ref` directive.\
46
+ Properties are merged in a greedy way with type checking.
47
+
48
+ ### Multi-stage tests
49
+
50
+ Each test scenario contains 1+ stages; each stage is a single HTTP call.\
51
+ `pytest-httpchain` executes stages in the order they are listed in scenario file; one stage failure stops the execution chain.
52
+
53
+ ### Common data context and variable substitution
54
+
55
+ `pytest-httpchain` maintains key-value data storage throughout the execution.\
56
+ This storage ("common data context") is populated with declared variables, fixtures and data saved by stages. The data remains there throughout the scenario execution.\
57
+ Writing scenarios, you can use Jinja-style expressions like `"{{ var }}"` for JSON values. `pytest-httpchain` does variable substitution dynamically right before executing a stage, and uses common data context keys as variables in these expressions.\
58
+ Values from common data context also might be verified during verified/asserted.
59
+
60
+ ### User functions
61
+
62
+ `pytest-httpchain` can import and call regular python functions:
63
+
64
+ - to extract data from HTTP response
65
+ - to verify HTTP response and values in common data context
66
+ - to provide [custom authentication for requests](https://requests.readthedocs.io/en/latest/user/advanced/#custom-authentication)
67
+
68
+ ### JMESPath support
69
+
70
+ `pytest-httpchain` can extract values from JSON responses using JMESPath expressions directly.
71
+
72
+ ### JSON schema support
73
+
74
+ `pytest-httpchain` can verify JSON reponses against user-defined JSON schema.
75
+
76
+ ## Quick Start
77
+
78
+ Create a JSON test file named like `test_<name>.<suffix>.json` (default suffix is `http`):
79
+
80
+ ```python
81
+ # conftest.py
82
+ import pytest
83
+ from datetime import datetime
84
+
85
+ @pytest.fixture
86
+ def now_utc():
87
+ return datetime.now()
88
+ ```
89
+
90
+ ```json
91
+ {
92
+ "vars": {
93
+ "user_id": 1
94
+ },
95
+ "stages": [
96
+ {
97
+ "name": "get_user",
98
+ "request": {
99
+ "url": "https://api.example.com/users/{{ user_id }}"
100
+ },
101
+ "response": [
102
+ {
103
+ "verify": {
104
+ "status": 200
105
+ }
106
+ },
107
+ {
108
+ "save": {
109
+ "vars": {
110
+ "user_name": "user.name"
111
+ }
112
+ }
113
+ }
114
+ ]
115
+ },
116
+ {
117
+ "name": "update_user",
118
+ "fixtures": ["now_utc"],
119
+ "request": {
120
+ "url": "https://api.example.com/users/{{ user_id }}",
121
+ "method": "PUT",
122
+ "body": {
123
+ "json": {
124
+ "user": {
125
+ "name": "{{ user_name }}_updated",
126
+ "timestamp": "{{ str(now_utc) }}"
127
+ }
128
+ }
129
+ }
130
+ },
131
+ "response": [
132
+ {
133
+ "verify": {
134
+ "status": 200
135
+ }
136
+ }
137
+ ]
138
+ },
139
+ {
140
+ "name": "cleanup",
141
+ "always_run": true,
142
+ "request": {
143
+ "url": "https://api.example.com/cleanup",
144
+ "method": "POST"
145
+ }
146
+ }
147
+ ]
148
+ }
149
+ ```
150
+
151
+ Scenario we created:
152
+
153
+ - common data context is seeded with the first variable `user_id`
154
+ - **get_user**\
155
+ url is assembled using `user_id` variable from common data context\
156
+ HTTP GET call is made\
157
+ we verify the call returned code 200\
158
+ assuming JSON body is returned, we extract a value by JMESPath expression `user.name` and save it to common data context under `user_name` key
159
+ - **update_user**\
160
+ `now_utc` fixture value is injected into common data context\
161
+ url is assembled using `user_id` variable from common data context\
162
+ we create JSON body in place using values from common data context, note that `now_utc` is converted to string in place\
163
+ HTTP PUT call with body is made\
164
+ we verify the call returned code 200\
165
+ - **cleanup**\
166
+ finalizing call meant for graceful exit\
167
+ `always_run` parameter means this stage will be executed regardless of errors in previous stages
168
+
169
+ For detailed examples see [USAGE.md](USAGE.md).
170
+
171
+ ## Configuration
172
+
173
+ - Test file discovery is based on this name pattern: `test_<name>.<suffix>.json`.\
174
+ The `suffix` is configurable as pytest ini option, default value is **http**.
175
+ - `$ref` instructions can point to other files; absolute and relative paths are supported.\
176
+ You can limit the depth of relative path traversal using `ref_parent_traversal_depth` ini option, default value is **3**.
177
+
178
+ ## MCP Server
179
+
180
+ `pytest-httpchain` includes an MCP (Model Context Protocol) server to aid AI code assistants.
181
+
182
+ ### Installation
183
+
184
+ The optional dependency `mcp` installs MCP server's package and `pytest-httpchain-mcp` script.\
185
+ Use this script as call target for your MCP configuration.
186
+
187
+ Claude Code `.mcp.json` example:
188
+
189
+ ```json
190
+ {
191
+ "mcpServers": {
192
+ "pytest-httpchain": {
193
+ "type": "stdio",
194
+ "command": "uv",
195
+ "args": ["run", "pytest-httpchain-mcp"],
196
+ "env": {}
197
+ }
198
+ }
199
+ }
200
+ ```
201
+
202
+ ### Features
203
+
204
+ The MCP server provides:
205
+
206
+ - **Scenario validation** - validate test scenario and scan for possible problems
207
+
208
+ ## Documentation
209
+
210
+ - [Usage Examples](USAGE.md) - Practical code examples
211
+ - [Full Documentation](https://aeresov.github.io/pytest-httpchain) - Complete guide
212
+ - [Changelog](CHANGELOG.md) - Release notes
213
+
214
+ ## Thanks
215
+
216
+ `pytest-httpchain` was heavily inspired by [Tavern](https://github.com/taverntesting/tavern) and [pytest-play](https://github.com/davidemoro/pytest-play).
217
+ [requests](https://requests.readthedocs.io) does the comms.
218
+ [Pydantic](https://docs.pydantic.dev) keeps the structure.
219
+ [pytest-order](https://github.com/pytest-dev/pytest-order) powers the chaining.
220
+ [pytest-datadir](https://github.com/gabrielcnr/pytest-datadir) saved me a lot of elbow grease.
@@ -0,0 +1,112 @@
1
+ [project]
2
+ name = "pytest-httpchain"
3
+ version = "0.1.0"
4
+ description = "pytest plugin for HTTP testing using JSON files"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13,<4.0"
7
+ authors = [{ name = "Alexander Eresov", email = "aeresov@gmail.com" }]
8
+ dependencies = [
9
+ "pydantic>=2.11.7",
10
+ "pytest-httpchain-jsonref",
11
+ "pytest-httpchain-models",
12
+ "pytest-order>=1.3.0",
13
+ "rich>=13.7.0",
14
+ ]
15
+ keywords = ["testing", "pytest", "requests"]
16
+ license-files = ["LICENSE"]
17
+ classifiers = [
18
+ "Development Status :: 5 - Production/Stable",
19
+ "Framework :: Pytest",
20
+ "Intended Audience :: Developers",
21
+ "License :: OSI Approved :: MIT License",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Software Development :: Testing",
25
+ ]
26
+
27
+ [project.optional-dependencies]
28
+ mcp = ["pytest-httpchain-mcp"]
29
+
30
+ [dependency-groups]
31
+ dev = [
32
+ "flask-httpauth>=4.8.0",
33
+ "http-server-mock>=1.7",
34
+ "pytest-xdist>=3.8.0",
35
+ "responses>=0.25.7",
36
+ "ruff>=0.12.1",
37
+ ]
38
+ docs = [
39
+ "mkdocs>=1.6.1",
40
+ "mkdocs-material>=9.6.16",
41
+ "mkdocstrings>=0.30.0",
42
+ "mkdocstrings-python>=1.16.12",
43
+ ]
44
+
45
+ [tool.uv]
46
+ default-groups = ["docs"]
47
+
48
+ [tool.uv.workspace]
49
+ members = ["packages/*"]
50
+
51
+ [tool.uv.sources]
52
+ pytest-httpchain-jsonref = { workspace = true }
53
+ pytest-httpchain-templates = { workspace = true }
54
+ pytest-httpchain-mcp = { workspace = true }
55
+ pytest-httpchain-models = { workspace = true }
56
+ pytest-httpchain-userfunc = { workspace = true }
57
+
58
+ [build-system]
59
+ requires = ["uv_build>=0.7.21,<0.8.0"]
60
+ build-backend = "uv_build"
61
+
62
+ [project.entry-points.pytest11]
63
+ pytest_httpchain = "pytest_httpchain.plugin"
64
+
65
+ [tool.ruff]
66
+ line-length = 180
67
+ target-version = "py313"
68
+
69
+ [tool.ruff.format]
70
+ quote-style = "double"
71
+ line-ending = "lf"
72
+
73
+ [tool.ruff.lint]
74
+ select = [
75
+ "E", # pycodestyle errors
76
+ "W", # pycodestyle warnings
77
+ "F", # pyflakes
78
+ "I", # isort
79
+ "B", # flake8-bugbear
80
+ "C4", # flake8-comprehensions
81
+ "UP", # pyupgrade
82
+ ]
83
+ ignore = []
84
+
85
+ [tool.ruff.lint.isort]
86
+ known-first-party = ["tests", "utils"]
87
+
88
+ [tool.pytest.ini_options]
89
+ log_cli = true
90
+ log_cli_level = "INFO"
91
+ # testpaths = ["tests"]
92
+ norecursedirs = ["examples"]
93
+ pytester_example_dir = "tests/integration/examples"
94
+ addopts = ["--log-disable=werkzeug", "--import-mode=importlib"]
95
+ pythonpath = [
96
+ ".",
97
+ "packages/pytest-httpchain-jsonref/src",
98
+ "packages/pytest-httpchain-templates/src",
99
+ "packages/pytest-httpchain-mcp/src",
100
+ "packages/pytest-httpchain-models/src",
101
+ "packages/pytest-httpchain-userfunc/src",
102
+ ]
103
+ testpaths = ["tests", "packages/*/tests"]
104
+
105
+ [tool.pyright]
106
+ exclude = [".tox/"]
107
+ ignore = [".venv/"]
108
+ reportMissingImports = "error"
109
+ reportMissingTypeStubs = false
110
+ reportExplicitAny = false
111
+ typeCheckingMode = "off"
112
+ pythonPlatform = "Linux"