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.
- proxy_mock-2.10.1/.gitignore +8 -0
- proxy_mock-2.10.1/CHANGELOG.md +280 -0
- proxy_mock-2.10.1/LICENSE +21 -0
- proxy_mock-2.10.1/PKG-INFO +352 -0
- proxy_mock-2.10.1/README.md +318 -0
- proxy_mock-2.10.1/proxy_mock/__init__.py +10 -0
- proxy_mock-2.10.1/proxy_mock/any_catcher.py +8 -0
- proxy_mock-2.10.1/proxy_mock/api/__init__.py +0 -0
- proxy_mock-2.10.1/proxy_mock/api/deps.py +5 -0
- proxy_mock-2.10.1/proxy_mock/api/routes/__init__.py +0 -0
- proxy_mock-2.10.1/proxy_mock/api/routes/configure.py +50 -0
- proxy_mock-2.10.1/proxy_mock/api/routes/service.py +24 -0
- proxy_mock-2.10.1/proxy_mock/api/routes/storage.py +51 -0
- proxy_mock-2.10.1/proxy_mock/api/routes/traffic.py +79 -0
- proxy_mock-2.10.1/proxy_mock/api/schemas.py +10 -0
- proxy_mock-2.10.1/proxy_mock/app/__init__.py +3 -0
- proxy_mock-2.10.1/proxy_mock/app/factory.py +97 -0
- proxy_mock-2.10.1/proxy_mock/app/state.py +0 -0
- proxy_mock-2.10.1/proxy_mock/client/__init__.py +4 -0
- proxy_mock-2.10.1/proxy_mock/client/async_client.py +237 -0
- proxy_mock-2.10.1/proxy_mock/client/client.py +174 -0
- proxy_mock-2.10.1/proxy_mock/client/route.py +93 -0
- proxy_mock-2.10.1/proxy_mock/client/service_endpoints.py +14 -0
- proxy_mock-2.10.1/proxy_mock/core/__init__.py +0 -0
- proxy_mock-2.10.1/proxy_mock/core/logging.py +16 -0
- proxy_mock-2.10.1/proxy_mock/core/serializers.py +32 -0
- proxy_mock-2.10.1/proxy_mock/core/settings.py +50 -0
- proxy_mock-2.10.1/proxy_mock/core/time.py +6 -0
- proxy_mock-2.10.1/proxy_mock/domain/__init__.py +20 -0
- proxy_mock-2.10.1/proxy_mock/domain/constants.py +16 -0
- proxy_mock-2.10.1/proxy_mock/domain/models.py +94 -0
- proxy_mock-2.10.1/proxy_mock/repositories/__init__.py +0 -0
- proxy_mock-2.10.1/proxy_mock/repositories/mock_storage.py +76 -0
- proxy_mock-2.10.1/proxy_mock/repositories/traffic_store.py +69 -0
- proxy_mock-2.10.1/proxy_mock/services/__init__.py +0 -0
- proxy_mock-2.10.1/proxy_mock/services/cache_service.py +46 -0
- proxy_mock-2.10.1/proxy_mock/services/mock_service.py +102 -0
- proxy_mock-2.10.1/proxy_mock/services/proxy_service.py +104 -0
- proxy_mock-2.10.1/proxy_mock/services/request_parser.py +30 -0
- proxy_mock-2.10.1/proxy_mock/services/response_factory.py +25 -0
- proxy_mock-2.10.1/proxy_mock/services/rule_engine.py +84 -0
- proxy_mock-2.10.1/proxy_mock/services/traffic_service.py +26 -0
- proxy_mock-2.10.1/proxy_mock/utils.py +145 -0
- proxy_mock-2.10.1/pyproject.toml +77 -0
|
@@ -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)
|