proxy-mock 2.10.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 (44) hide show
  1. proxy_mock-2.10.1/.gitignore +8 -0
  2. proxy_mock-2.10.1/CHANGELOG.md +280 -0
  3. proxy_mock-2.10.1/LICENSE +21 -0
  4. proxy_mock-2.10.1/PKG-INFO +352 -0
  5. proxy_mock-2.10.1/README.md +318 -0
  6. proxy_mock-2.10.1/proxy_mock/__init__.py +10 -0
  7. proxy_mock-2.10.1/proxy_mock/any_catcher.py +8 -0
  8. proxy_mock-2.10.1/proxy_mock/api/__init__.py +0 -0
  9. proxy_mock-2.10.1/proxy_mock/api/deps.py +5 -0
  10. proxy_mock-2.10.1/proxy_mock/api/routes/__init__.py +0 -0
  11. proxy_mock-2.10.1/proxy_mock/api/routes/configure.py +50 -0
  12. proxy_mock-2.10.1/proxy_mock/api/routes/service.py +24 -0
  13. proxy_mock-2.10.1/proxy_mock/api/routes/storage.py +51 -0
  14. proxy_mock-2.10.1/proxy_mock/api/routes/traffic.py +79 -0
  15. proxy_mock-2.10.1/proxy_mock/api/schemas.py +10 -0
  16. proxy_mock-2.10.1/proxy_mock/app/__init__.py +3 -0
  17. proxy_mock-2.10.1/proxy_mock/app/factory.py +97 -0
  18. proxy_mock-2.10.1/proxy_mock/app/state.py +0 -0
  19. proxy_mock-2.10.1/proxy_mock/client/__init__.py +4 -0
  20. proxy_mock-2.10.1/proxy_mock/client/async_client.py +237 -0
  21. proxy_mock-2.10.1/proxy_mock/client/client.py +174 -0
  22. proxy_mock-2.10.1/proxy_mock/client/route.py +93 -0
  23. proxy_mock-2.10.1/proxy_mock/client/service_endpoints.py +14 -0
  24. proxy_mock-2.10.1/proxy_mock/core/__init__.py +0 -0
  25. proxy_mock-2.10.1/proxy_mock/core/logging.py +16 -0
  26. proxy_mock-2.10.1/proxy_mock/core/serializers.py +32 -0
  27. proxy_mock-2.10.1/proxy_mock/core/settings.py +50 -0
  28. proxy_mock-2.10.1/proxy_mock/core/time.py +6 -0
  29. proxy_mock-2.10.1/proxy_mock/domain/__init__.py +20 -0
  30. proxy_mock-2.10.1/proxy_mock/domain/constants.py +16 -0
  31. proxy_mock-2.10.1/proxy_mock/domain/models.py +94 -0
  32. proxy_mock-2.10.1/proxy_mock/repositories/__init__.py +0 -0
  33. proxy_mock-2.10.1/proxy_mock/repositories/mock_storage.py +76 -0
  34. proxy_mock-2.10.1/proxy_mock/repositories/traffic_store.py +69 -0
  35. proxy_mock-2.10.1/proxy_mock/services/__init__.py +0 -0
  36. proxy_mock-2.10.1/proxy_mock/services/cache_service.py +46 -0
  37. proxy_mock-2.10.1/proxy_mock/services/mock_service.py +102 -0
  38. proxy_mock-2.10.1/proxy_mock/services/proxy_service.py +104 -0
  39. proxy_mock-2.10.1/proxy_mock/services/request_parser.py +30 -0
  40. proxy_mock-2.10.1/proxy_mock/services/response_factory.py +25 -0
  41. proxy_mock-2.10.1/proxy_mock/services/rule_engine.py +84 -0
  42. proxy_mock-2.10.1/proxy_mock/services/traffic_service.py +26 -0
  43. proxy_mock-2.10.1/proxy_mock/utils.py +145 -0
  44. proxy_mock-2.10.1/pyproject.toml +77 -0
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.log
3
+ *.pytest_*
4
+ *.coverage*
5
+ .venv/
6
+ dist/
7
+ .DS_Store
8
+ .python-version
@@ -0,0 +1,280 @@
1
+ Migrating from 1.0.1
2
+ ====================
3
+
4
+ The previously published 1.0.1 (Flask) and the current 2.10.1 (FastAPI) are not compatible.
5
+ Five changes require edits in projects upgrading from 1.0.1:
6
+
7
+ * **`GET /status` was renamed to `GET /proxy_mock`.** There is no alias: `/status` is now
8
+ handled by the any-catcher (`404`, or a user mock if one is configured)
9
+ * **The client method `get_status()` was renamed to `get_proxy_mock()`.** There is no alias
10
+ * **The dedicated `POST /configure_mock/binary` endpoint is gone.** A binary body is sent to
11
+ the regular `POST /configure_mock` with `Content-Type: application/octet-stream` (msgpack)
12
+ * **The client method `configure_binary_mock()` was removed** — use `configure_mock()`, which
13
+ serialises the body to msgpack itself
14
+ * **Error messages are now in English.** A missing mock returns
15
+ `{"error": "No mock found for /<path>"}` instead of the previous Russian text; code that
16
+ matches on message text needs updating
17
+
18
+ Still compatible: `POST /configure_mock`, `GET /storage`, `POST /storage/clean`,
19
+ `GET /traffic`, `POST /traffic/clean` and the client methods `configure_mock()`,
20
+ `get_traffic()`, `get_storage()`, `clean_storage()`, `clean_traffic()`. `get_traffic()` gained
21
+ optional filters (`path`, `method`, `limit`); calling it without arguments is unchanged.
22
+
23
+ Environment requirements changed as well: Python >= 3.11 instead of 3.9, and uvicorn instead
24
+ of gunicorn.
25
+
26
+ Version 2.10.1
27
+ ==============
28
+
29
+ * First public release of the 2.x line. The changes since 1.0.1 are listed below, and the upgrade path is in "Migrating from 1.0.1"
30
+ * Functionality is unchanged relative to 2.10.0: code, API and clients were not modified
31
+ * Documentation, code comments and API error messages are now in English
32
+ * Added a license file (MIT) and a GitHub Actions build
33
+
34
+ Version 2.10.0
35
+ ==============
36
+
37
+ * **Breaking change.** The service endpoint `GET /status` was renamed to `GET /proxy_mock`. The old path is not even kept as an alias: `/status` is now handled by the any-catcher (`404` or a user mock)
38
+ * **Breaking change.** In the `ProxyMock` and `AsyncProxyMock` clients the `get_status()` method was renamed to `get_proxy_mock()` with no alias, so consumer projects need fixing on upgrade
39
+
40
+ * Fixed a `500` with a traceback when `proxy_host` was unreachable: the httpx2 transport error (DNS failure, refused connection, broken protocol) bubbled up to uvicorn. It now returns `502` with the reason, and `504` when the upstream host times out. Fixed in both proxy paths, for mocks and for rules. Failed responses are not cached and the request still lands in traffic
41
+ * `GET /proxy_mock` returns an instance summary: the Python version (`python_version`), the number of configured mocks (`mocks_count`), the number of traffic records (`traffic_count`) and the current store limit (`traffic_max_items`)
42
+ * The traffic store limit is adjustable at runtime through the `max_items` field of `POST /traffic/settings`. Lowering the limit trims the oldest records and keeps the freshest. The endpoint body is a partial update: any subset of fields is accepted, an empty body gives `422`
43
+
44
+ * Recording of unknown traffic (requests that matched no mock) became switchable. The `PROXY_MOCK_RECORD_UNKNOWN_TRAFFIC` environment variable (default `true`, preserving 2.9.0 behaviour) accepts `1/0`, `true/false`, `yes/no`, `on/off`; an unrecognised value falls back to the default
45
+ * New service endpoints `GET /traffic/settings` and `POST /traffic/settings` configure traffic capture at runtime, without restarting the service. The clients gained `get_traffic_settings()` and `set_traffic_settings()`
46
+ * The `404` response itself and its logging do not change when recording is off: the flag only controls whether the request lands in traffic
47
+
48
+ * The project moved from Poetry to uv: `uv.lock` instead of `poetry.lock`, dev dependencies in `[dependency-groups]` (PEP 735)
49
+ * The build backend changed from `poetry-core` to `hatchling`; the sdist contents are listed explicitly so tests and repository service files are not shipped
50
+ * The Dockerfile and the build moved to uv: `uv sync --locked` instead of `poetry sync`, `uv build` / `uv publish` instead of `poetry build` / `poetry publish`. The multi-stage layout did not change
51
+ * The Makefile gained an `install` target (`uv sync`); `.venv/` and `dist/` were added to `.gitignore`
52
+
53
+ Version 2.9.0
54
+ =============
55
+
56
+ * Requests that matched no mock (the 404 response) are recorded in traffic, with `status_code: 404` written to `extra_info`
57
+ * `requested_at_ts` was dropped from traffic; only `requested_at` remains (Europe/Moscow, ISO-8601)
58
+ * Protection against proxying "to self": the `x-proxy-mock-chain` marker header, with the loop aborted by a `508` response
59
+ * An allowlist of proxy targets via `PROXY_MOCK_ALLOWED_PROXY_HOSTS` (disabled by default, so any host is allowed)
60
+ * The traffic store limit was set to 1000 and made configurable through `PROXY_MOCK_TRAFFIC_MAX`
61
+ * An explicit timeout for outgoing proxied requests, configurable through `PROXY_MOCK_PROXY_TIMEOUT` (30s by default)
62
+ * A new `DELETE /storage` endpoint for deleting mocks: without `path` all of them, with `path` a single one (`404` if it does not exist). `POST /storage/clean` remains as a deprecated alias
63
+ * Moved from `httpx` to `httpx2` (the httpx successor maintained under Pydantic) for the server HTTP clients, the async client and the benchmark
64
+
65
+ * The Dockerfile became multi-stage: a slim server image without compilers, git and poetry, plus a separate `ci-image` target for CI tasks
66
+ * Service version detection works when proxy_mock is installed as a library: first proxy_mock's own pyproject.toml, then the package metadata (previously `create_app()` failed without a pyproject.toml in the working directory)
67
+ * README: a section on running without Docker (pip package plus a pytest fixture)
68
+ * Lower dependency bounds were relaxed for compatibility when installed as a library: Python `>=3.11`, pydantic `>=2.6`, uvicorn `>=0.27`, requests `>=2.31`, aiocache `>=0.12.2`. FastAPI stays at `>=0.136`, because below that the service endpoint protection breaks
69
+ * A regression test for service route protection (`tests/test_service_routes.py`)
70
+
71
+ * Fixed a bug in full mock cleanup: runtime routes are now removed as well (previously a path kept answering with its mock after cleanup)
72
+ * Fixed handling of malformed msgpack during configuration: it returns `400` instead of `500`
73
+ * Removed unreachable code in binary data serialisation
74
+
75
+ * Added a "Security" section to the README (no authentication, SSRF via `proxy_host`, sensitive data in traffic)
76
+ * The API section of the README was simplified and brought up to date, with a link to the auto-generated OpenAPI docs (`/docs`, `/redoc`)
77
+ * Increased test coverage, including the `AsyncProxyMock` async client
78
+
79
+ Version 2.8.0
80
+ =============
81
+
82
+ * Migration to Python 3.14.6 with support for the free-threaded build (3.14.6t)
83
+ * Updated FastAPI (>=0.136), uvicorn (>=0.44) and the remaining dependencies
84
+ * TOML reading replaced with the stdlib `tomllib` (writing goes through `tomlkit` in the CI scripts)
85
+
86
+ * The project structure was refactored:
87
+ * `app/` — application factory and lifespan
88
+ * `api/routes/` — HTTP routes
89
+ * `domain/` — schemas and domain constants
90
+ * `services/` — business logic (mock/proxy/rules/cache/parser/traffic)
91
+ * `repositories/` — in-memory stores
92
+ * `core/` — logging, settings, serialisation, time
93
+ * `any_catcher.py` became a thin entrypoint
94
+ * Reduced coupling and bloat in `utils.py`
95
+
96
+ * Async upstream proxying via `httpx` instead of the blocking `requests` in the server
97
+ * Traffic now stores the request time:
98
+ * `requested_at` (Europe/Moscow, ISO-8601)
99
+ * `requested_at_ts` (unix timestamp)
100
+ * The traffic store is bounded: only the last 100 requests
101
+ * Added concurrency protection (a lock) for the in-memory stores
102
+ * Improved rule logic:
103
+ * `priority` support
104
+ * more stable condition matching
105
+ * Added filters for `/traffic`: `path`, `method`, `limit`
106
+ * Configurable request logging through `PROXY_MOCK_LOG_REQUESTS=full|minimal|off`
107
+
108
+ * Sync client refactoring:
109
+ * less duplication between configure and patch
110
+ * better request/response handling
111
+ * Added a new async client `AsyncProxyMock` built on `httpx`
112
+
113
+ * Moved to public APIs, without relying on private fields such as `_dict` and `_url`
114
+ * Behavioural compatibility was preserved for existing tests and mocking scenarios
115
+
116
+ Version 2.5.4
117
+ =============
118
+
119
+ * Fixed the data type of the incoming request body
120
+
121
+ Version 2.5.3
122
+ =============
123
+
124
+ * Fixed rule sorting
125
+
126
+ Version 2.5.2
127
+ =============
128
+
129
+ * Mock rules are now sorted by creation time, newest first
130
+
131
+ Version 2.5.1
132
+ =============
133
+
134
+ * Added proxying and delays inside mock rules
135
+
136
+ Version 2.5.0
137
+ =============
138
+
139
+ * Updated helper packages and raised the minimum Python version to 3.10
140
+ * Added caching of mocked responses
141
+ * Added an endpoint for cache invalidation
142
+
143
+ Version 2.4.3
144
+ =============
145
+
146
+ * Added the ability to pass extra information about a mock rule into traffic
147
+
148
+ Version 2.4.2
149
+ =============
150
+
151
+ * Added the ability to pass HTTP request methods when configuring mock rules
152
+
153
+ Version 2.4.1
154
+ =============
155
+
156
+ * Added the ability to specify the HTTP request method during configuration
157
+ * Fixed coverage measurement for asynchronous code
158
+ * `pyproject.toml` was converted to the new format used by the newer poetry
159
+ * Added pre-commit to make linting easier
160
+
161
+ Version 2.4.0
162
+ =============
163
+
164
+ * Merged the JSON and binary mock configuration endpoints into a single `/configure_mock`. By default client requests compress the payload into bytes and the server unpacks it back into a dict
165
+ * Updated unit tests
166
+ * Added msgpack, a library for working with binary data
167
+
168
+ Version 2.3.1
169
+ =============
170
+
171
+ * Fixed automatic tag creation
172
+
173
+ Version 2.3.0
174
+ =============
175
+
176
+ * Added new PATCH endpoints for updating already created mocks
177
+
178
+ Version 2.2.0
179
+ =============
180
+
181
+ * Changed how rules work: when several conditions are given in one rule, the special response is returned only if all of them match
182
+ * Added the ability to specify query parameters in rules
183
+ * The Docker application now uses Python 3.13
184
+
185
+ Version 2.1.2
186
+ =============
187
+
188
+ * Fixed a bug in how extra information was shown in traffic
189
+
190
+ Version 2.1.1
191
+ =============
192
+
193
+ * Fixed formatting of incoming request bodies
194
+ * Traffic now includes information about query parameters and the request method
195
+ * Updated the returned content type for strings and protobufs
196
+
197
+ Version 2.1.0
198
+ =============
199
+
200
+ * Added the ability to return different responses for one mock based on given conditions
201
+
202
+ Version 2.0.0
203
+ =============
204
+
205
+ * Service routes were renamed:
206
+ /configure --> /configure_mock
207
+ /configure/binary --> /configure_mock/binary
208
+ /cleanup_params --> /traffic/clean
209
+ /cleanup_storage --> /storage/clean
210
+ /mock_params --> /traffic
211
+ * The binary body of a mock configuration request must now be an encoded dict
212
+ * Strict validation of POST requests
213
+ * The client was moved into a separate directory (`/client/client.py`)
214
+ * The server runs through uvicorn (ASGI) instead of gunicorn (WSGI)
215
+ * FastAPI was integrated in place of Flask
216
+ * Any request can now be mocked (the path is passed as a route argument)
217
+ * The internal mock storage now only serves an informational purpose, while the mocks themselves are added dynamically through FastAPI
218
+
219
+ Version 1.0.10
220
+ ==============
221
+
222
+ * When a request is intercepted, its headers are recorded in mock_params
223
+
224
+ Version 1.0.9
225
+ =============
226
+
227
+ * Removed unnecessary dependencies
228
+
229
+ Version 1.0.8
230
+ =============
231
+
232
+ * Interception of requests without endpoints and recording of their data in a dedicated place
233
+ * More methods supported for request interception
234
+ * Changed the log format
235
+
236
+ Version 1.0.7
237
+ =============
238
+
239
+ * Encode binary data as latin-1 for storage inside the proxy mock
240
+
241
+ Version 1.0.6
242
+ =============
243
+
244
+ * Fixed a bug when reading the request parameter storage
245
+
246
+ Version 1.0.5
247
+ =============
248
+
249
+ * Added the ability to proxy requests to a preconfigured host
250
+ * A new key appeared in the `/configure` and `/configure/binary` endpoints: `proxy_host`
251
+ * Added unit tests for the new and the existing functionality
252
+ * Updated the service documentation
253
+
254
+ Version 1.0.4
255
+ =============
256
+
257
+ * Added a new `/configure/binary` endpoint for configuring mocks with binary content
258
+ * Added the ability to read data from the stores by a given key
259
+ * Added the ability to delete data from the stores by a given key
260
+
261
+ Version 1.0.3
262
+ =============
263
+
264
+ * Added the git module to the base Docker image
265
+ * Hid unused dependencies of the client library
266
+
267
+ Version 1.0.2
268
+ =============
269
+
270
+ * Allowed Python from version 3.9 onwards
271
+ * Lowered dependency versions for maximum compatibility with other projects
272
+ * Raised the poetry version to 1.8.2
273
+
274
+ Version 1.0.1
275
+ =============
276
+
277
+ * Added the proxy mock client
278
+ * Added tests for the proxy mock server
279
+ * CI can run tests and linters, and build and publish the library
280
+ * Added code linting
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2026 ivi.ru
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,352 @@
1
+ Metadata-Version: 2.5
2
+ Name: proxy_mock
3
+ Version: 2.10.1
4
+ Summary: Tool for proxying and mocking HTTP traffic
5
+ Project-URL: Homepage, https://github.com/ivi-ru/proxy_mock
6
+ Project-URL: Repository, https://github.com/ivi-ru/proxy_mock
7
+ Project-URL: Changelog, https://github.com/ivi-ru/proxy_mock/blob/main/CHANGELOG.md
8
+ Project-URL: Issues, https://github.com/ivi-ru/proxy_mock/issues
9
+ Author-email: Andrey Ryabokon <aryabokon@ivi.ru>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: autotests,fastapi,http,mock,proxy,testing
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Framework :: FastAPI
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Internet :: WWW/HTTP
23
+ Classifier: Topic :: Software Development :: Testing
24
+ Requires-Python: >=3.11
25
+ Requires-Dist: aiocache<0.13.0,>=0.12.2
26
+ Requires-Dist: fastapi>=0.136.0
27
+ Requires-Dist: httpx2<3.0.0,>=2.0.0
28
+ Requires-Dist: msgpack<2.0.0,>=1.0.4
29
+ Requires-Dist: pydantic<3.0.0,>=2.6.0
30
+ Requires-Dist: requests<3.0.0,>=2.31.0
31
+ Requires-Dist: uvicorn>=0.27.0
32
+ Requires-Dist: yarl<2.0.0,>=1.8.0
33
+ Description-Content-Type: text/markdown
34
+
35
+ # 🛡️ Proxy Mock
36
+
37
+ **Proxy Mock** is a tool that combines a proxy server and a mock server.
38
+ It suits automated tests, integration scenarios and local debugging of service-to-service calls.
39
+
40
+ ---
41
+
42
+ ## 📋 Features
43
+
44
+ - **Request proxying** to an upstream host (`proxy_host`)
45
+ - **Endpoint mocking** with flexible response configuration
46
+ - **Rules** for returning different responses on the same path
47
+ - **Response delay** (`timeout`) and **caching** (`cache_time`)
48
+ - **Traffic capture** of incoming requests for later inspection
49
+ - **Bounded in-memory traffic storage** (the last 1000 records by default)
50
+
51
+ ---
52
+
53
+ ## ⚠️ Security
54
+
55
+ The tool is meant for a **trusted, isolated test environment** and is not designed to be exposed publicly. Before deploying it, keep in mind:
56
+
57
+ - **No authentication.** The service endpoints (`/configure_mock`, `/storage*`, `/traffic*`, `/cache/clean`) are open to anyone with network access. Anybody can create, read and delete mocks and traffic.
58
+ - **SSRF via `proxy_host`.** By default a request can be proxied to **any** host, including your internal network and the cloud metadata address (`169.254.169.254`). The host list can be restricted with `PROXY_MOCK_ALLOWED_PROXY_HOSTS` (disabled by default, meaning any host is allowed).
59
+ - **Traffic holds sensitive data.** `/traffic` stores the full headers and bodies of incoming requests (including `Authorization` and `Cookie`), and they can be read without authorisation. Requests that matched no mock (the `404` responses) are recorded as well.
60
+ - **Loop protection.** Proxying "to self" is detected through the `x-proxy-mock-chain` marker header and is aborted with `508 Loop Detected`.
61
+
62
+ **Recommendation:** run it inside a closed network perimeter only, never expose it to the internet, and narrow proxying with the allowlist where possible.
63
+
64
+ ---
65
+
66
+ ## ⬆️ Migrating from 1.0.1
67
+
68
+ The previously published 1.0.1 ran on Flask. The current 2.10.1 runs on FastAPI, and four
69
+ changes break compatibility. What to fix in a project upgrading from 1.0.1:
70
+
71
+ | In 1.0.1 | In 2.10.1 |
72
+ |---|---|
73
+ | `GET /status` | `GET /proxy_mock` — no alias, the old path returns `404` |
74
+ | `get_status()` | `get_proxy_mock()` — no alias |
75
+ | `POST /configure_mock/binary` | `POST /configure_mock` with `Content-Type: application/octet-stream` |
76
+ | `configure_binary_mock()` | `configure_mock()` — it serialises the body to msgpack itself |
77
+
78
+ Error messages are now in English: a missing mock returns `{"error": "No mock found for /<path>"}`
79
+ instead of the previous Russian text. Code that matches on the message text needs updating.
80
+
81
+ Everything else stays compatible: `POST /configure_mock`, `GET /storage`, `POST /storage/clean`,
82
+ `GET /traffic`, `POST /traffic/clean` and the methods `configure_mock()`, `get_traffic()`,
83
+ `get_storage()`, `clean_storage()`, `clean_traffic()` work as before. `get_traffic()` gained
84
+ optional filters (`path`, `method`, `limit`), and calling it without arguments is unchanged.
85
+
86
+ Environment requirements changed too: Python >= 3.11 instead of 3.9, and uvicorn instead of
87
+ gunicorn. The full history is in [CHANGELOG.md](CHANGELOG.md).
88
+
89
+ ---
90
+
91
+ ## 🚀 Getting started
92
+
93
+ There are three ways to run proxy-mock:
94
+
95
+ - **As a pip package** (simplest for automated tests, no Docker) — see "Running without Docker" below.
96
+ - **In Docker** — see "Running in Docker" below.
97
+ - **From source** (for working on proxy-mock itself) — see below.
98
+
99
+ ### 📦 Prerequisites (for running from source)
100
+
101
+ Make sure the following are installed:
102
+
103
+ - **Python** >= 3.11 (development happens on 3.14)
104
+ - **uv** >= 0.9
105
+
106
+ ### ⚙️ Install and run from source
107
+
108
+ 1. **Install dependencies**
109
+
110
+ Create a virtual environment and install everything, including the dev group:
111
+ ```bash
112
+ uv sync
113
+ ```
114
+
115
+ 2. **Activate the virtual environment**
116
+
117
+ Activate the generated `.venv` (or prefix commands with `uv run`):
118
+ ```bash
119
+ source .venv/bin/activate
120
+ ```
121
+
122
+ 3. **Run the service**
123
+
124
+ Use the Makefile:
125
+ ```bash
126
+ make run
127
+ ```
128
+
129
+ ### 🐳 Running in Docker
130
+
131
+ 1. **Build the image and start the service**
132
+
133
+ ```bash
134
+ make docker_run
135
+ ```
136
+
137
+ 2. **Reach the service**
138
+
139
+ Once it is up, the service listens on:
140
+ ```
141
+ http://localhost:5000
142
+ ```
143
+
144
+ ### 🐍 Running without Docker (as a pip package)
145
+
146
+ Docker is not required for automated tests: proxy-mock is published as an ordinary Python package containing both the server and the clients.
147
+
148
+ ```bash
149
+ pip install proxy_mock
150
+ uvicorn proxy_mock.any_catcher:app --host 0.0.0.0 --port 5000 --workers 1
151
+ ```
152
+
153
+ You can also start it straight from your tests with a session-scoped pytest fixture (free port, automatic shutdown):
154
+
155
+ ```python
156
+ import socket
157
+ import threading
158
+ import time
159
+
160
+ import pytest
161
+ import uvicorn
162
+
163
+ from proxy_mock.app import create_app
164
+ from proxy_mock.client import ProxyMock
165
+
166
+
167
+ def _free_port() -> int:
168
+ with socket.socket() as sock:
169
+ sock.bind(("127.0.0.1", 0))
170
+ return sock.getsockname()[1]
171
+
172
+
173
+ @pytest.fixture(scope="session")
174
+ def proxy_mock_url():
175
+ port = _free_port()
176
+ config = uvicorn.Config(create_app(), host="127.0.0.1", port=port, log_level="warning")
177
+ server = uvicorn.Server(config)
178
+ thread = threading.Thread(target=server.run, daemon=True)
179
+ thread.start()
180
+
181
+ deadline = time.time() + 10
182
+ while not server.started:
183
+ if time.time() > deadline:
184
+ raise RuntimeError("proxy-mock failed to start in time")
185
+ time.sleep(0.05)
186
+
187
+ yield f"http://127.0.0.1:{port}"
188
+
189
+ server.should_exit = True
190
+ thread.join(timeout=5)
191
+
192
+
193
+ @pytest.fixture(scope="session")
194
+ def proxy_mock(proxy_mock_url):
195
+ return ProxyMock(proxy_mock_url)
196
+ ```
197
+
198
+ Using it in a test:
199
+
200
+ ```python
201
+ def test_external_service(proxy_mock):
202
+ proxy_mock.configure_mock(path="/external/api", body={"answer": 42})
203
+ # ... point the application under test at proxy_mock_url and assert on its behaviour
204
+ traffic = proxy_mock.get_traffic(path="/external/api")
205
+ assert traffic["count"] == 1
206
+ ```
207
+
208
+ **Requirements and limitations:**
209
+
210
+ - Python **>= 3.11** in the test environment; on older interpreters pip will not find an installable version.
211
+ - The package pulls server dependencies (`fastapi>=0.136`, `pydantic>=2.6`, `uvicorn`, `httpx2`). If your test project pins older versions, the resolver may conflict. In that case install proxy-mock into a separate environment (`uv tool install` / `pipx`) and run it as a subprocess.
212
+ - Under parallel runs (pytest-xdist) start one instance per worker: the mock and traffic stores are global to an instance.
213
+
214
+ ### Free-threaded Python (optional)
215
+
216
+ The service also runs on a free-threaded build of the interpreter (3.14t). The official
217
+ `python` images on Docker Hub do not publish free-threaded variants, so the interpreter has to
218
+ be installed separately, for example with uv:
219
+
220
+ ```bash
221
+ uv python install 3.14t
222
+ uv sync --python 3.14t
223
+ ```
224
+
225
+ Uvicorn should be started with a single worker (`--workers 1`). To confirm the GIL is really
226
+ disabled:
227
+
228
+ ```bash
229
+ python scripts/check_gil.py
230
+ ```
231
+
232
+ ### Environment variables
233
+
234
+ | Variable | Values | Description |
235
+ |----------|--------|-------------|
236
+ | `PROXY_MOCK_LOG_REQUESTS` | `full` (default), `minimal`, `off` | Logging level for incoming requests |
237
+ | `PROXY_MOCK_TRAFFIC_MAX` | integer > 0 (default `1000`) | Maximum number of records in the in-memory traffic store. Changeable at runtime via `POST /traffic/settings` |
238
+ | `PROXY_MOCK_PROXY_TIMEOUT` | float, seconds (default `30`) | Timeout for outgoing proxied requests |
239
+ | `PROXY_MOCK_ALLOWED_PROXY_HOSTS` | comma-separated host list (default: empty) | Allowlist of proxy targets. Empty means any host is allowed |
240
+ | `PROXY_MOCK_RECORD_UNKNOWN_TRAFFIC` | `true` (default), `false` (`1/0`, `yes/no`, `on/off`) | Whether to record requests that matched no mock (the `404` response). Toggleable at runtime via `POST /traffic/settings` |
241
+
242
+ ---
243
+
244
+ # 📡 API
245
+
246
+ Full request and response schemas are available in the auto-generated documentation while the service is running:
247
+
248
+ - **Swagger UI** — `http://localhost:5000/docs`
249
+ - **ReDoc** — `http://localhost:5000/redoc`
250
+ - **OpenAPI JSON** — `http://localhost:5000/openapi.json`
251
+
252
+ A short reference and the key examples follow.
253
+
254
+ ## Service endpoints
255
+
256
+ | Method | URL | Purpose | Success response |
257
+ |--------|-----|---------|------------------|
258
+ | `GET` | `/proxy_mock` | Server availability and an instance summary | `200` — `{"success": true, "version": "...", "python_version": "...", "mocks_count": N, "traffic_count": N, "traffic_max_items": N}` |
259
+ | `POST` | `/configure_mock` | Create a mock | `201` — `{"success": true, "path": "...", "data": {...}}` |
260
+ | `PATCH` | `/configure_mock` | Amend an existing mock (it must already exist) | `200` — same shape as POST |
261
+ | `GET` | `/storage` | List mocks. Query: `path` filters by path | `200` — `{"success": true, "data": {...}}` |
262
+ | `DELETE` | `/storage` | Delete mocks. Query: `path` for one mock; without `path` all of them | `200` — `{"success": true, "data": {...}}`; `404` if the given mock does not exist |
263
+ | `POST` | `/storage/clean` | Deprecated alias of `DELETE /storage` (same effect) | `200` — `{"success": true, "data": {...}}` |
264
+ | `GET` | `/traffic` | Show captured traffic. Query: `path`, `method`, `limit` | `200` — `{"success": true, "count": N, "data": [...]}` |
265
+ | `POST` | `/traffic/clean` | Clear the traffic store | `200` — `{"success": true, "data": []}` |
266
+ | `GET` | `/traffic/settings` | Current traffic recording settings | `200` — `{"success": true, "data": {"record_unknown_traffic": true, "max_items": 1000}}` |
267
+ | `POST` | `/traffic/settings` | Change traffic settings. Body: `{"record_unknown_traffic": bool}` and/or `{"max_items": int > 0}`; the update is partial | `200` — same shape as GET; `400` on malformed JSON, `422` on an invalid or empty body |
268
+ | `POST` | `/cache/clean` | Invalidate the entire cache | `200` — `{"success": true}` |
269
+ | `*` | `/<any path>` | Catch-all: returns a mock, proxies, or `404` | depends on the configuration (see below) |
270
+
271
+ **`/configure_mock` errors:**
272
+
273
+ - `400` — empty body or malformed JSON/msgpack: `{"success": false, "error": "..."}`
274
+ - `415` — unsupported `Content-Type` (`application/json` or `application/octet-stream` is required)
275
+ - `422` — the body failed validation (for example the required `path` is missing): `{"success": false, "error": [...]}`
276
+
277
+ ## `/configure_mock` body
278
+
279
+ Content type: `application/json` or `application/octet-stream` (msgpack). The PATCH body is identical to POST.
280
+
281
+ | Field | Type | Description |
282
+ |-------|------|-------------|
283
+ | `path` (required) | `string` | Path the mock applies to |
284
+ | `methods` | `list[string]` | HTTP methods (all by default) |
285
+ | `mock_data.body` | `string \| dict \| list \| bytes \| null` | Response body |
286
+ | `mock_data.status_code` | `int` | Response code (default `200`) |
287
+ | `mock_data.headers` | `dict` | Response headers |
288
+ | `extra_info` | `dict` | Arbitrary metadata (ends up in traffic) |
289
+ | `proxy_host` | `string` (**absolute URL**) | Proxy the request to this host |
290
+ | `timeout` | `float` | Delay before responding, seconds |
291
+ | `cache_time` | `int` | Response cache lifetime, seconds |
292
+ | `rules` | `list[dict]` | Rules producing different responses on one path |
293
+
294
+ Each entry in `rules`:
295
+
296
+ - **`input_data`** — the match condition: `methods`, `body`, `headers`, `query`, `proxy_host` (absolute URL), `timeout`.
297
+ - **`output_data`** — the response: `body`, `status_code`, `headers`.
298
+ - **`extra_info`** — rule metadata (appears in traffic as `rule_extra_info`).
299
+ - **`priority`** — `int`, higher values are checked earlier (default `0`).
300
+
301
+ Rules are sorted by descending `priority`, then by insertion order; the first match wins.
302
+
303
+ #### Example
304
+
305
+ ```json
306
+ {
307
+ "path": "/test/endpoint",
308
+ "mock_data": {
309
+ "body": {"message": "Hello, World!"},
310
+ "status_code": 200,
311
+ "headers": {"Content-Type": "application/json"}
312
+ },
313
+ "extra_info": {"service": "example_service"},
314
+ "timeout": 1.5,
315
+ "cache_time": 600,
316
+ "rules": [
317
+ {
318
+ "input_data": {
319
+ "methods": ["POST", "PUT"],
320
+ "body": {"message": "any data"},
321
+ "headers": {"X-App-Version": "870"},
322
+ "query": {"user": "1"}
323
+ },
324
+ "output_data": {
325
+ "body": {"message": "other data"},
326
+ "status_code": 201,
327
+ "headers": {"X-Request-ID": "123456"}
328
+ },
329
+ "extra_info": {"rule_request_id": "Request-id"},
330
+ "priority": 10
331
+ }
332
+ ]
333
+ }
334
+ ```
335
+
336
+ ## Catching requests on `/<path>`
337
+
338
+ For any path that has a mock configured, the server processes the request in this order:
339
+
340
+ 1. Records the request in traffic.
341
+ 2. Checks the method, otherwise `405 Method Not Allowed`.
342
+ 3. Returns a cached response when `cache_time` is set and there is a cache hit.
343
+ 4. When `proxy_host` is set, proxies to the upstream host and returns its response (proxying "to self" is aborted with `508`). If the host is unreachable or does not resolve — `502`; if it did not answer within `PROXY_MOCK_PROXY_TIMEOUT` — `504`. Failed responses are not cached.
344
+ 5. Otherwise applies `timeout`, then `rules`, then the default `mock_data`.
345
+
346
+ If no mock is configured for the path, the response is `404` with the body `{"error": "No mock found for /<path>"}`. By default such a request is recorded in traffic too (with `extra_info.status_code = 404`). Recording unknown traffic can be turned off with `PROXY_MOCK_RECORD_UNKNOWN_TRAFFIC=false` or at runtime via `POST /traffic/settings`.
347
+
348
+ ---
349
+
350
+ ## 📄 License
351
+
352
+ [MIT](LICENSE)