replayx 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,26 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ *.egg
9
+
10
+ # Virtual environments
11
+ .venv/
12
+ venv/
13
+ env/
14
+
15
+ # Tooling caches
16
+ .pytest_cache/
17
+ .mypy_cache/
18
+ .ruff_cache/
19
+ .coverage
20
+ htmlcov/
21
+ coverage.xml
22
+
23
+ # Editors / OS
24
+ .idea/
25
+ .vscode/
26
+ .DS_Store
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format is based on
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - 2026-06-22
10
+
11
+ ### Added
12
+ - Initial release.
13
+ - `use_cassette` context manager that transparently patches `httpx` to record
14
+ and replay interactions.
15
+ - Synchronous (`ReplayTransport`) and asynchronous (`AsyncReplayTransport`)
16
+ transports for explicit, no-magic usage.
17
+ - Four record modes: `once`, `new_episodes`, `none`, `all`.
18
+ - Configurable request matchers (`method`, `url`, `host`, `path`, `query`,
19
+ `headers`, `body`, ...).
20
+ - Secret redaction via `filter_headers`, `filter_query_params`, and
21
+ `before_record_request` / `before_record_response` hooks.
22
+ - JSON cassettes by default; optional YAML cassettes via the `yaml` extra.
23
+ - A `pytest` plugin exposing the `replayx_cassette` fixture and a
24
+ `--replayx-record` flag.
25
+
26
+ [Unreleased]: https://github.com/mkusiappiah/replayx/compare/v0.1.0...HEAD
27
+ [0.1.0]: https://github.com/mkusiappiah/replayx/releases/tag/v0.1.0
replayx-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Michael Kusi-Appiah
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.
replayx-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,261 @@
1
+ Metadata-Version: 2.4
2
+ Name: replayx
3
+ Version: 0.1.0
4
+ Summary: Record and replay HTTP interactions for httpx — an async-native VCR for fast, deterministic tests.
5
+ Project-URL: Homepage, https://github.com/mkusiappiah/replayx
6
+ Project-URL: Repository, https://github.com/mkusiappiah/replayx
7
+ Project-URL: Issues, https://github.com/mkusiappiah/replayx/issues
8
+ Project-URL: Changelog, https://github.com/mkusiappiah/replayx/blob/main/CHANGELOG.md
9
+ Author-email: Michael Kusi-Appiah <appiah.michael@yahoo.com>
10
+ License: MIT License
11
+
12
+ Copyright (c) 2026 Michael Kusi-Appiah
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+ License-File: LICENSE
32
+ Keywords: async,asyncio,http,httpx,mock,pytest,record,replay,testing,vcr
33
+ Classifier: Development Status :: 4 - Beta
34
+ Classifier: Framework :: Pytest
35
+ Classifier: Intended Audience :: Developers
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Operating System :: OS Independent
38
+ Classifier: Programming Language :: Python :: 3
39
+ Classifier: Programming Language :: Python :: 3.9
40
+ Classifier: Programming Language :: Python :: 3.10
41
+ Classifier: Programming Language :: Python :: 3.11
42
+ Classifier: Programming Language :: Python :: 3.12
43
+ Classifier: Programming Language :: Python :: 3.13
44
+ Classifier: Topic :: Software Development :: Testing
45
+ Classifier: Topic :: Software Development :: Testing :: Mocking
46
+ Classifier: Typing :: Typed
47
+ Requires-Python: >=3.9
48
+ Requires-Dist: httpx>=0.23
49
+ Provides-Extra: dev
50
+ Requires-Dist: mypy>=1.8; extra == 'dev'
51
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
52
+ Requires-Dist: pytest>=7.4; extra == 'dev'
53
+ Requires-Dist: pyyaml>=6.0; extra == 'dev'
54
+ Requires-Dist: ruff>=0.5; extra == 'dev'
55
+ Requires-Dist: types-pyyaml; extra == 'dev'
56
+ Provides-Extra: yaml
57
+ Requires-Dist: pyyaml>=6.0; extra == 'yaml'
58
+ Description-Content-Type: text/markdown
59
+
60
+ # replayx
61
+
62
+ Record and replay HTTP interactions for [httpx](https://www.python-httpx.org/). Run your tests fast and offline.
63
+
64
+ [![CI](https://github.com/mkusiappiah/replayx/actions/workflows/ci.yml/badge.svg)](https://github.com/mkusiappiah/replayx/actions/workflows/ci.yml)
65
+ [![PyPI](https://img.shields.io/pypi/v/replayx.svg)](https://pypi.org/project/replayx/)
66
+ [![Python versions](https://img.shields.io/pypi/pyversions/replayx.svg)](https://pypi.org/project/replayx/)
67
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
68
+
69
+ replayx saves real HTTP responses to a cassette file on the first test run. Every later run reads from the cassette. No network calls. No flaky tests. No slow CI.
70
+
71
+ ```python
72
+ import httpx
73
+ from replayx import use_cassette
74
+
75
+ with use_cassette("cassettes/github.json"):
76
+ resp = httpx.get("https://api.github.com/users/octocat")
77
+ assert resp.json()["login"] == "octocat"
78
+ ```
79
+
80
+ The first run records. Later runs replay.
81
+
82
+ ## Why I built replayx
83
+
84
+ vcrpy brought record and replay to Python. vcrpy targets requests and the sync world. I built replayx for modern httpx code.
85
+
86
+ | Feature | replayx | vcrpy |
87
+ | --- | --- | --- |
88
+ | Async httpx.AsyncClient | yes | limited |
89
+ | Built for httpx | yes | through patches |
90
+ | Zero deps beyond httpx | yes (JSON) | needs PyYAML |
91
+ | Secret redaction for committed cassettes | yes | partial |
92
+ | Explicit transport API, no patching | yes | no |
93
+ | Modern typing with py.typed | yes | no |
94
+
95
+ ## Install
96
+
97
+ ```bash
98
+ pip install replayx
99
+ ```
100
+
101
+ Add YAML cassettes:
102
+
103
+ ```bash
104
+ pip install "replayx[yaml]"
105
+ ```
106
+
107
+ replayx needs Python 3.9 or newer and httpx 0.23 or newer.
108
+
109
+ ## Usage
110
+
111
+ ### Patch httpx with use_cassette
112
+
113
+ use_cassette patches httpx for the block. Your existing client code runs without changes. Sync and async both work.
114
+
115
+ ```python
116
+ import httpx
117
+ from replayx import use_cassette
118
+
119
+ async def fetch():
120
+ async with httpx.AsyncClient() as client:
121
+ return await client.get("https://api.example.com/data")
122
+
123
+ with use_cassette("cassettes/data.json"):
124
+ resp = await fetch()
125
+ ```
126
+
127
+ ### Build a transport yourself
128
+
129
+ Prefer no patching? Build a transport and pass the transport to your client. Nothing gets monkeypatched.
130
+
131
+ ```python
132
+ import httpx
133
+ from replayx import Cassette
134
+
135
+ cassette = Cassette.load("cassettes/data.json", record_mode="once")
136
+
137
+ with httpx.Client(transport=cassette.sync_transport()) as client:
138
+ resp = client.get("https://api.example.com/data")
139
+
140
+ cassette.save()
141
+ ```
142
+
143
+ Use `cassette.async_transport()` with `httpx.AsyncClient` for async code.
144
+
145
+ ### The pytest plugin
146
+
147
+ The plugin gives each test an auto-named cassette at `<test-dir>/cassettes/<test-name>.json`.
148
+
149
+ ```python
150
+ import httpx
151
+
152
+ def test_octocat(replayx_cassette):
153
+ with replayx_cassette():
154
+ resp = httpx.get("https://api.github.com/users/octocat")
155
+ assert resp.status_code == 200
156
+ ```
157
+
158
+ Re-record a whole run from the command line:
159
+
160
+ ```bash
161
+ pytest --replayx-record=all
162
+ ```
163
+
164
+ Set per-test defaults with the marker:
165
+
166
+ ```python
167
+ import pytest
168
+
169
+ @pytest.mark.replayx(match_on=("method", "url", "body"), filter_headers=["authorization"])
170
+ def test_create(replayx_cassette):
171
+ with replayx_cassette():
172
+ ...
173
+ ```
174
+
175
+ ## Record modes
176
+
177
+ | Mode | What happens |
178
+ | --- | --- |
179
+ | once (default) | Replay an existing cassette. Record everything when no cassette exists. A new request against an existing cassette raises an error. |
180
+ | new_episodes | Replay matches and append new interactions. |
181
+ | none | Replay only. No network. No writes. Good for CI. |
182
+ | all | Always reach the real backend and overwrite the cassette. Use to re-record. |
183
+
184
+ ```python
185
+ with use_cassette("cassettes/api.json", record_mode="none"):
186
+ ...
187
+ ```
188
+
189
+ ## Match requests
190
+
191
+ Requests match on method and url by default. Query order does not affect matching. Change the rules with match_on.
192
+
193
+ ```python
194
+ with use_cassette("cassettes/api.json", match_on=("method", "path", "body")):
195
+ ...
196
+ ```
197
+
198
+ Available matchers: method, scheme, host, port, path, query, url (alias uri), headers, body.
199
+
200
+ ## Redact secrets
201
+
202
+ Commit cassettes without leaking credentials. Redaction runs at record time. The live response your code receives stays intact.
203
+
204
+ ```python
205
+ with use_cassette(
206
+ "cassettes/api.json",
207
+ filter_headers=["authorization", "set-cookie"],
208
+ filter_query_params=["api_key", "token"],
209
+ ):
210
+ ...
211
+ ```
212
+
213
+ Use hooks for full control. Return a changed recording, or return None to skip the recording.
214
+
215
+ ```python
216
+ from dataclasses import replace
217
+
218
+ def scrub_body(response):
219
+ return replace(response, body=b'{"token": "REDACTED"}')
220
+
221
+ with use_cassette("cassettes/api.json", before_record_response=scrub_body):
222
+ ...
223
+ ```
224
+
225
+ ## Cassette format
226
+
227
+ Cassettes use plain JSON. YAML works with the yaml extra. Both read well in code review.
228
+
229
+ ```json
230
+ {
231
+ "version": 1,
232
+ "recorded_with": "replayx/0.1.0",
233
+ "interactions": [
234
+ {
235
+ "request": { "method": "GET", "url": "https://api.example.com/data", "headers": [], "body": null },
236
+ "response": { "status_code": 200, "headers": [["content-type", "application/json"]], "body": { "text": "{\"ok\":true}" } }
237
+ }
238
+ ]
239
+ }
240
+ ```
241
+
242
+ replayx stores binary bodies as base64.
243
+
244
+ ## Contribute
245
+
246
+ I welcome contributions. Set up a dev environment:
247
+
248
+ ```bash
249
+ git clone https://github.com/mkusiappiah/replayx
250
+ cd replayx
251
+ pip install -e ".[dev]"
252
+ pytest
253
+ ruff check .
254
+ mypy
255
+ ```
256
+
257
+ Open an issue before large changes.
258
+
259
+ ## License
260
+
261
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,202 @@
1
+ # replayx
2
+
3
+ Record and replay HTTP interactions for [httpx](https://www.python-httpx.org/). Run your tests fast and offline.
4
+
5
+ [![CI](https://github.com/mkusiappiah/replayx/actions/workflows/ci.yml/badge.svg)](https://github.com/mkusiappiah/replayx/actions/workflows/ci.yml)
6
+ [![PyPI](https://img.shields.io/pypi/v/replayx.svg)](https://pypi.org/project/replayx/)
7
+ [![Python versions](https://img.shields.io/pypi/pyversions/replayx.svg)](https://pypi.org/project/replayx/)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
9
+
10
+ replayx saves real HTTP responses to a cassette file on the first test run. Every later run reads from the cassette. No network calls. No flaky tests. No slow CI.
11
+
12
+ ```python
13
+ import httpx
14
+ from replayx import use_cassette
15
+
16
+ with use_cassette("cassettes/github.json"):
17
+ resp = httpx.get("https://api.github.com/users/octocat")
18
+ assert resp.json()["login"] == "octocat"
19
+ ```
20
+
21
+ The first run records. Later runs replay.
22
+
23
+ ## Why I built replayx
24
+
25
+ vcrpy brought record and replay to Python. vcrpy targets requests and the sync world. I built replayx for modern httpx code.
26
+
27
+ | Feature | replayx | vcrpy |
28
+ | --- | --- | --- |
29
+ | Async httpx.AsyncClient | yes | limited |
30
+ | Built for httpx | yes | through patches |
31
+ | Zero deps beyond httpx | yes (JSON) | needs PyYAML |
32
+ | Secret redaction for committed cassettes | yes | partial |
33
+ | Explicit transport API, no patching | yes | no |
34
+ | Modern typing with py.typed | yes | no |
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ pip install replayx
40
+ ```
41
+
42
+ Add YAML cassettes:
43
+
44
+ ```bash
45
+ pip install "replayx[yaml]"
46
+ ```
47
+
48
+ replayx needs Python 3.9 or newer and httpx 0.23 or newer.
49
+
50
+ ## Usage
51
+
52
+ ### Patch httpx with use_cassette
53
+
54
+ use_cassette patches httpx for the block. Your existing client code runs without changes. Sync and async both work.
55
+
56
+ ```python
57
+ import httpx
58
+ from replayx import use_cassette
59
+
60
+ async def fetch():
61
+ async with httpx.AsyncClient() as client:
62
+ return await client.get("https://api.example.com/data")
63
+
64
+ with use_cassette("cassettes/data.json"):
65
+ resp = await fetch()
66
+ ```
67
+
68
+ ### Build a transport yourself
69
+
70
+ Prefer no patching? Build a transport and pass the transport to your client. Nothing gets monkeypatched.
71
+
72
+ ```python
73
+ import httpx
74
+ from replayx import Cassette
75
+
76
+ cassette = Cassette.load("cassettes/data.json", record_mode="once")
77
+
78
+ with httpx.Client(transport=cassette.sync_transport()) as client:
79
+ resp = client.get("https://api.example.com/data")
80
+
81
+ cassette.save()
82
+ ```
83
+
84
+ Use `cassette.async_transport()` with `httpx.AsyncClient` for async code.
85
+
86
+ ### The pytest plugin
87
+
88
+ The plugin gives each test an auto-named cassette at `<test-dir>/cassettes/<test-name>.json`.
89
+
90
+ ```python
91
+ import httpx
92
+
93
+ def test_octocat(replayx_cassette):
94
+ with replayx_cassette():
95
+ resp = httpx.get("https://api.github.com/users/octocat")
96
+ assert resp.status_code == 200
97
+ ```
98
+
99
+ Re-record a whole run from the command line:
100
+
101
+ ```bash
102
+ pytest --replayx-record=all
103
+ ```
104
+
105
+ Set per-test defaults with the marker:
106
+
107
+ ```python
108
+ import pytest
109
+
110
+ @pytest.mark.replayx(match_on=("method", "url", "body"), filter_headers=["authorization"])
111
+ def test_create(replayx_cassette):
112
+ with replayx_cassette():
113
+ ...
114
+ ```
115
+
116
+ ## Record modes
117
+
118
+ | Mode | What happens |
119
+ | --- | --- |
120
+ | once (default) | Replay an existing cassette. Record everything when no cassette exists. A new request against an existing cassette raises an error. |
121
+ | new_episodes | Replay matches and append new interactions. |
122
+ | none | Replay only. No network. No writes. Good for CI. |
123
+ | all | Always reach the real backend and overwrite the cassette. Use to re-record. |
124
+
125
+ ```python
126
+ with use_cassette("cassettes/api.json", record_mode="none"):
127
+ ...
128
+ ```
129
+
130
+ ## Match requests
131
+
132
+ Requests match on method and url by default. Query order does not affect matching. Change the rules with match_on.
133
+
134
+ ```python
135
+ with use_cassette("cassettes/api.json", match_on=("method", "path", "body")):
136
+ ...
137
+ ```
138
+
139
+ Available matchers: method, scheme, host, port, path, query, url (alias uri), headers, body.
140
+
141
+ ## Redact secrets
142
+
143
+ Commit cassettes without leaking credentials. Redaction runs at record time. The live response your code receives stays intact.
144
+
145
+ ```python
146
+ with use_cassette(
147
+ "cassettes/api.json",
148
+ filter_headers=["authorization", "set-cookie"],
149
+ filter_query_params=["api_key", "token"],
150
+ ):
151
+ ...
152
+ ```
153
+
154
+ Use hooks for full control. Return a changed recording, or return None to skip the recording.
155
+
156
+ ```python
157
+ from dataclasses import replace
158
+
159
+ def scrub_body(response):
160
+ return replace(response, body=b'{"token": "REDACTED"}')
161
+
162
+ with use_cassette("cassettes/api.json", before_record_response=scrub_body):
163
+ ...
164
+ ```
165
+
166
+ ## Cassette format
167
+
168
+ Cassettes use plain JSON. YAML works with the yaml extra. Both read well in code review.
169
+
170
+ ```json
171
+ {
172
+ "version": 1,
173
+ "recorded_with": "replayx/0.1.0",
174
+ "interactions": [
175
+ {
176
+ "request": { "method": "GET", "url": "https://api.example.com/data", "headers": [], "body": null },
177
+ "response": { "status_code": 200, "headers": [["content-type", "application/json"]], "body": { "text": "{\"ok\":true}" } }
178
+ }
179
+ ]
180
+ }
181
+ ```
182
+
183
+ replayx stores binary bodies as base64.
184
+
185
+ ## Contribute
186
+
187
+ I welcome contributions. Set up a dev environment:
188
+
189
+ ```bash
190
+ git clone https://github.com/mkusiappiah/replayx
191
+ cd replayx
192
+ pip install -e ".[dev]"
193
+ pytest
194
+ ruff check .
195
+ mypy
196
+ ```
197
+
198
+ Open an issue before large changes.
199
+
200
+ ## License
201
+
202
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,85 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.18"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "replayx"
7
+ dynamic = ["version"]
8
+ description = "Record and replay HTTP interactions for httpx — an async-native VCR for fast, deterministic tests."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { file = "LICENSE" }
12
+ authors = [{ name = "Michael Kusi-Appiah", email = "appiah.michael@yahoo.com" }]
13
+ keywords = [
14
+ "httpx",
15
+ "testing",
16
+ "vcr",
17
+ "mock",
18
+ "http",
19
+ "record",
20
+ "replay",
21
+ "pytest",
22
+ "async",
23
+ "asyncio",
24
+ ]
25
+ classifiers = [
26
+ "Development Status :: 4 - Beta",
27
+ "Intended Audience :: Developers",
28
+ "License :: OSI Approved :: MIT License",
29
+ "Operating System :: OS Independent",
30
+ "Programming Language :: Python :: 3",
31
+ "Programming Language :: Python :: 3.9",
32
+ "Programming Language :: Python :: 3.10",
33
+ "Programming Language :: Python :: 3.11",
34
+ "Programming Language :: Python :: 3.12",
35
+ "Programming Language :: Python :: 3.13",
36
+ "Framework :: Pytest",
37
+ "Topic :: Software Development :: Testing",
38
+ "Topic :: Software Development :: Testing :: Mocking",
39
+ "Typing :: Typed",
40
+ ]
41
+ dependencies = ["httpx>=0.23"]
42
+
43
+ [project.optional-dependencies]
44
+ yaml = ["PyYAML>=6.0"]
45
+ dev = [
46
+ "pytest>=7.4",
47
+ "pytest-asyncio>=0.23",
48
+ "PyYAML>=6.0",
49
+ "ruff>=0.5",
50
+ "mypy>=1.8",
51
+ "types-PyYAML",
52
+ ]
53
+
54
+ [project.urls]
55
+ Homepage = "https://github.com/mkusiappiah/replayx"
56
+ Repository = "https://github.com/mkusiappiah/replayx"
57
+ Issues = "https://github.com/mkusiappiah/replayx/issues"
58
+ Changelog = "https://github.com/mkusiappiah/replayx/blob/main/CHANGELOG.md"
59
+
60
+ [project.entry-points.pytest11]
61
+ replayx = "replayx.pytest_plugin"
62
+
63
+ [tool.hatch.version]
64
+ path = "src/replayx/__init__.py"
65
+
66
+ [tool.hatch.build.targets.wheel]
67
+ packages = ["src/replayx"]
68
+
69
+ [tool.hatch.build.targets.sdist]
70
+ include = ["src/replayx", "tests", "README.md", "CHANGELOG.md", "LICENSE"]
71
+
72
+ [tool.pytest.ini_options]
73
+ asyncio_mode = "auto"
74
+ testpaths = ["tests"]
75
+
76
+ [tool.ruff]
77
+ line-length = 100
78
+ target-version = "py39"
79
+
80
+ [tool.ruff.lint]
81
+ select = ["E", "F", "I", "UP", "B"]
82
+
83
+ [tool.mypy]
84
+ strict = true
85
+ files = ["src/replayx"]
@@ -0,0 +1,33 @@
1
+ """replayx — record & replay HTTP interactions for httpx.
2
+
3
+ replayx is an async-native "VCR" for `httpx`: it records real HTTP responses
4
+ to a cassette file the first time your tests run, then replays them on every
5
+ subsequent run so tests are fast, offline and deterministic.
6
+
7
+ See https://github.com/mkusiappiah/replayx for documentation.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ __version__ = "0.1.0"
13
+
14
+ from ._types import RecordMode
15
+ from .cassette import Cassette, Interaction, RecordedRequest, RecordedResponse
16
+ from .errors import CassetteFormatError, ReplayxError, UnhandledRequestError
17
+ from .recorder import use_cassette
18
+ from .transport import AsyncReplayTransport, ReplayTransport
19
+
20
+ __all__ = [
21
+ "AsyncReplayTransport",
22
+ "Cassette",
23
+ "CassetteFormatError",
24
+ "Interaction",
25
+ "RecordMode",
26
+ "RecordedRequest",
27
+ "RecordedResponse",
28
+ "ReplayTransport",
29
+ "ReplayxError",
30
+ "UnhandledRequestError",
31
+ "__version__",
32
+ "use_cassette",
33
+ ]
@@ -0,0 +1,30 @@
1
+ """Shared types and enumerations for replayx."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import Enum
6
+
7
+
8
+ class RecordMode(str, Enum):
9
+ """Controls how a cassette records and replays interactions.
10
+
11
+ The semantics mirror the well-understood VCR record modes:
12
+
13
+ * ``ONCE`` — Replay interactions from an existing cassette. If the cassette
14
+ file does not yet exist, record everything. Once a cassette exists, new
15
+ (unmatched) requests raise :class:`~replayx.UnhandledRequestError`.
16
+ * ``NEW_EPISODES`` — Replay matching interactions and record any new ones,
17
+ appending them to the cassette.
18
+ * ``NONE`` — Replay only. Never touch the network and never write. New
19
+ requests raise :class:`~replayx.UnhandledRequestError`.
20
+ * ``ALL`` — Never replay. Always hit the real backend and (re)record,
21
+ overwriting the cassette.
22
+ """
23
+
24
+ ONCE = "once"
25
+ NEW_EPISODES = "new_episodes"
26
+ NONE = "none"
27
+ ALL = "all"
28
+
29
+ def __str__(self) -> str: # pragma: no cover - cosmetic
30
+ return self.value