interposition-http-adapter 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.
- interposition_http_adapter-0.1.0/LICENSE +21 -0
- interposition_http_adapter-0.1.0/PKG-INFO +104 -0
- interposition_http_adapter-0.1.0/README.md +79 -0
- interposition_http_adapter-0.1.0/pyproject.toml +124 -0
- interposition_http_adapter-0.1.0/setup.cfg +4 -0
- interposition_http_adapter-0.1.0/src/interposition_http_adapter/__init__.py +6 -0
- interposition_http_adapter-0.1.0/src/interposition_http_adapter/_version.py +3 -0
- interposition_http_adapter-0.1.0/src/interposition_http_adapter/app.py +143 -0
- interposition_http_adapter-0.1.0/src/interposition_http_adapter/cli.py +18 -0
- interposition_http_adapter-0.1.0/src/interposition_http_adapter/py.typed +0 -0
- interposition_http_adapter-0.1.0/src/interposition_http_adapter.egg-info/PKG-INFO +104 -0
- interposition_http_adapter-0.1.0/src/interposition_http_adapter.egg-info/SOURCES.txt +14 -0
- interposition_http_adapter-0.1.0/src/interposition_http_adapter.egg-info/dependency_links.txt +1 -0
- interposition_http_adapter-0.1.0/src/interposition_http_adapter.egg-info/entry_points.txt +2 -0
- interposition_http_adapter-0.1.0/src/interposition_http_adapter.egg-info/requires.txt +3 -0
- interposition_http_adapter-0.1.0/src/interposition_http_adapter.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Osoekawa IT Laboratory
|
|
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,104 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: interposition_http_adapter
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: HTTP adapter for Interposition.
|
|
5
|
+
Author: osoken
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/osoekawaitlab/interposition-http-adapter
|
|
8
|
+
Project-URL: Repository, https://github.com/osoekawaitlab/interposition-http-adapter
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Operating System :: POSIX
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: interposition>=0.3.0
|
|
22
|
+
Requires-Dist: starlette>=0.52.1
|
|
23
|
+
Requires-Dist: uvicorn>=0.40.0
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# interposition-http-adapter
|
|
27
|
+
|
|
28
|
+
HTTP adapter for [Interposition](https://github.com/osoekawaitlab/interposition).
|
|
29
|
+
Serves recorded HTTP interactions from an Interposition `Cassette` as a real HTTP server, allowing you to replay captured API responses for testing and development.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install interposition-http-adapter
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
`InterpositionHttpAdapter` is an ASGI application (based on Starlette) that replays HTTP interactions through an Interposition `Broker`.
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from interposition import (
|
|
43
|
+
Broker,
|
|
44
|
+
Cassette,
|
|
45
|
+
Interaction,
|
|
46
|
+
InteractionRequest,
|
|
47
|
+
ResponseChunk,
|
|
48
|
+
)
|
|
49
|
+
import uvicorn
|
|
50
|
+
|
|
51
|
+
from interposition_http_adapter import InterpositionHttpAdapter
|
|
52
|
+
|
|
53
|
+
# 1. Build an InteractionRequest describing the HTTP request to match
|
|
54
|
+
request = InteractionRequest(
|
|
55
|
+
protocol="http",
|
|
56
|
+
action="GET",
|
|
57
|
+
target="/api/data",
|
|
58
|
+
headers=(),
|
|
59
|
+
body=b"",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# 2. Build ResponseChunks with status_code in the first chunk's metadata
|
|
63
|
+
response_chunks = (
|
|
64
|
+
ResponseChunk(
|
|
65
|
+
data=b'{"message": "hello"}',
|
|
66
|
+
sequence=0,
|
|
67
|
+
metadata=(("status_code", "200"),),
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# 3. Create a Cassette containing the interaction
|
|
72
|
+
interaction = Interaction(
|
|
73
|
+
request=request,
|
|
74
|
+
fingerprint=request.fingerprint(),
|
|
75
|
+
response_chunks=response_chunks,
|
|
76
|
+
)
|
|
77
|
+
cassette = Cassette(interactions=(interaction,))
|
|
78
|
+
|
|
79
|
+
# 4. Create a Broker in replay mode
|
|
80
|
+
broker = Broker(cassette=cassette, mode="replay")
|
|
81
|
+
|
|
82
|
+
# 5. Create the adapter and serve it
|
|
83
|
+
app = InterpositionHttpAdapter(broker=broker)
|
|
84
|
+
uvicorn.run(app, host="127.0.0.1", port=8000)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Once the server is running, a `GET` request to `http://127.0.0.1:8000/api/data` returns the recorded response with status code 200 and body `{"message": "hello"}`.
|
|
88
|
+
Requests that do not match any recorded interaction return a `500 Internal Server Error` response.
|
|
89
|
+
|
|
90
|
+
## API Reference
|
|
91
|
+
|
|
92
|
+
Detailed documentation is available in MkDocs: <https://osoekawaitlab.github.io/interposition-http-adapter/>.
|
|
93
|
+
|
|
94
|
+
## CLI
|
|
95
|
+
|
|
96
|
+
The package provides a command-line interface:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
interposition_http_adapter --version
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
MIT
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# interposition-http-adapter
|
|
2
|
+
|
|
3
|
+
HTTP adapter for [Interposition](https://github.com/osoekawaitlab/interposition).
|
|
4
|
+
Serves recorded HTTP interactions from an Interposition `Cassette` as a real HTTP server, allowing you to replay captured API responses for testing and development.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install interposition-http-adapter
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
`InterpositionHttpAdapter` is an ASGI application (based on Starlette) that replays HTTP interactions through an Interposition `Broker`.
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from interposition import (
|
|
18
|
+
Broker,
|
|
19
|
+
Cassette,
|
|
20
|
+
Interaction,
|
|
21
|
+
InteractionRequest,
|
|
22
|
+
ResponseChunk,
|
|
23
|
+
)
|
|
24
|
+
import uvicorn
|
|
25
|
+
|
|
26
|
+
from interposition_http_adapter import InterpositionHttpAdapter
|
|
27
|
+
|
|
28
|
+
# 1. Build an InteractionRequest describing the HTTP request to match
|
|
29
|
+
request = InteractionRequest(
|
|
30
|
+
protocol="http",
|
|
31
|
+
action="GET",
|
|
32
|
+
target="/api/data",
|
|
33
|
+
headers=(),
|
|
34
|
+
body=b"",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# 2. Build ResponseChunks with status_code in the first chunk's metadata
|
|
38
|
+
response_chunks = (
|
|
39
|
+
ResponseChunk(
|
|
40
|
+
data=b'{"message": "hello"}',
|
|
41
|
+
sequence=0,
|
|
42
|
+
metadata=(("status_code", "200"),),
|
|
43
|
+
),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# 3. Create a Cassette containing the interaction
|
|
47
|
+
interaction = Interaction(
|
|
48
|
+
request=request,
|
|
49
|
+
fingerprint=request.fingerprint(),
|
|
50
|
+
response_chunks=response_chunks,
|
|
51
|
+
)
|
|
52
|
+
cassette = Cassette(interactions=(interaction,))
|
|
53
|
+
|
|
54
|
+
# 4. Create a Broker in replay mode
|
|
55
|
+
broker = Broker(cassette=cassette, mode="replay")
|
|
56
|
+
|
|
57
|
+
# 5. Create the adapter and serve it
|
|
58
|
+
app = InterpositionHttpAdapter(broker=broker)
|
|
59
|
+
uvicorn.run(app, host="127.0.0.1", port=8000)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Once the server is running, a `GET` request to `http://127.0.0.1:8000/api/data` returns the recorded response with status code 200 and body `{"message": "hello"}`.
|
|
63
|
+
Requests that do not match any recorded interaction return a `500 Internal Server Error` response.
|
|
64
|
+
|
|
65
|
+
## API Reference
|
|
66
|
+
|
|
67
|
+
Detailed documentation is available in MkDocs: <https://osoekawaitlab.github.io/interposition-http-adapter/>.
|
|
68
|
+
|
|
69
|
+
## CLI
|
|
70
|
+
|
|
71
|
+
The package provides a command-line interface:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
interposition_http_adapter --version
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
MIT
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "interposition_http_adapter"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "HTTP adapter for Interposition."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "osoken" }
|
|
9
|
+
]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"Operating System :: POSIX",
|
|
14
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
15
|
+
"Programming Language :: Python :: 3.10",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
"Typing :: Typed",
|
|
20
|
+
]
|
|
21
|
+
requires-python = ">=3.10"
|
|
22
|
+
dependencies = [
|
|
23
|
+
"interposition>=0.3.0",
|
|
24
|
+
"starlette>=0.52.1",
|
|
25
|
+
"uvicorn>=0.40.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/osoekawaitlab/interposition-http-adapter"
|
|
30
|
+
Repository = "https://github.com/osoekawaitlab/interposition-http-adapter"
|
|
31
|
+
|
|
32
|
+
[build-system]
|
|
33
|
+
requires = ["setuptools>=70.1"]
|
|
34
|
+
build-backend = "setuptools.build_meta"
|
|
35
|
+
|
|
36
|
+
[tool.setuptools.dynamic]
|
|
37
|
+
version = {attr = "interposition_http_adapter._version.__version__"}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
[project.scripts]
|
|
41
|
+
interposition_http_adapter = "interposition_http_adapter.cli:main"
|
|
42
|
+
|
|
43
|
+
[dependency-groups]
|
|
44
|
+
dev = [
|
|
45
|
+
"getgauge>=0.5.0",
|
|
46
|
+
"httpx>=0.28.1",
|
|
47
|
+
"mypy>=1.18.2",
|
|
48
|
+
"nox>=2025.11.12",
|
|
49
|
+
"pytest>=9.0.1",
|
|
50
|
+
"pytest-cov>=7.0.0",
|
|
51
|
+
"pytest-mock>=3.15.1",
|
|
52
|
+
"pytest-randomly>=4.0.1",
|
|
53
|
+
"ruff>=0.14.5",
|
|
54
|
+
]
|
|
55
|
+
docs = [
|
|
56
|
+
"mkdocs>=1.6.1",
|
|
57
|
+
"mkdocs-awesome-nav>=3.2.0",
|
|
58
|
+
"mkdocs-material>=9.7.0",
|
|
59
|
+
"mkdocstrings>=0.30.1",
|
|
60
|
+
"mkdocstrings-python>=1.19.0",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[tool.ruff]
|
|
64
|
+
indent-width = 4
|
|
65
|
+
target-version = "py312"
|
|
66
|
+
line-length = 88
|
|
67
|
+
|
|
68
|
+
[tool.ruff.lint]
|
|
69
|
+
select = ["ALL"]
|
|
70
|
+
ignore = [
|
|
71
|
+
"COM812", # trailing-comma-missing (conflicts with formatter)
|
|
72
|
+
"ISC001", # single-line-implicit-string-concatenation (conflicts with formatter)
|
|
73
|
+
"D203", # one-blank-line-before-class (conflicts with D211)
|
|
74
|
+
"D213", # multi-line-summary-second-line (conflicts with D212)
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
[tool.ruff.lint.per-file-ignores]
|
|
78
|
+
"tests/**/*.py" = [
|
|
79
|
+
"S101", # assert-used (pytest uses asserts)
|
|
80
|
+
]
|
|
81
|
+
"e2e/**/*.py" = [
|
|
82
|
+
"S101", # assert-used (test uses asserts)
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
[tool.ruff.lint.pydocstyle]
|
|
86
|
+
convention = "google"
|
|
87
|
+
|
|
88
|
+
[tool.ruff.format]
|
|
89
|
+
quote-style = "double"
|
|
90
|
+
indent-style = "space"
|
|
91
|
+
skip-magic-trailing-comma = false
|
|
92
|
+
line-ending = "auto"
|
|
93
|
+
|
|
94
|
+
[tool.mypy]
|
|
95
|
+
python_version = "3.12"
|
|
96
|
+
strict = true
|
|
97
|
+
check_untyped_defs = true
|
|
98
|
+
disallow_any_explicit = true
|
|
99
|
+
disallow_any_generics = true
|
|
100
|
+
disallow_incomplete_defs = true
|
|
101
|
+
disallow_untyped_decorators = true
|
|
102
|
+
disallow_untyped_defs = true
|
|
103
|
+
no_implicit_optional = true
|
|
104
|
+
show_error_codes = true
|
|
105
|
+
strict_equality = true
|
|
106
|
+
warn_no_return = true
|
|
107
|
+
warn_redundant_casts = true
|
|
108
|
+
warn_return_any = true
|
|
109
|
+
warn_unreachable = true
|
|
110
|
+
warn_unused_configs = true
|
|
111
|
+
warn_unused_ignores = true
|
|
112
|
+
mypy_path = ["stubs"]
|
|
113
|
+
|
|
114
|
+
[tool.pytest.ini_options]
|
|
115
|
+
testpaths = ["tests", "src"]
|
|
116
|
+
python_files = ["test_*.py", "*_test.py"]
|
|
117
|
+
python_functions = ["test_*"]
|
|
118
|
+
addopts = [
|
|
119
|
+
"--strict-markers",
|
|
120
|
+
"--strict-config",
|
|
121
|
+
"--doctest-modules",
|
|
122
|
+
]
|
|
123
|
+
markers = [
|
|
124
|
+
]
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""HTTP adapter application for Interposition."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Awaitable, Callable
|
|
4
|
+
|
|
5
|
+
from interposition import (
|
|
6
|
+
Broker,
|
|
7
|
+
Interaction,
|
|
8
|
+
InteractionNotFoundError,
|
|
9
|
+
InteractionRequest,
|
|
10
|
+
ResponseChunk,
|
|
11
|
+
)
|
|
12
|
+
from starlette.applications import Starlette
|
|
13
|
+
from starlette.datastructures import Headers
|
|
14
|
+
from starlette.requests import Request
|
|
15
|
+
from starlette.responses import Response
|
|
16
|
+
from starlette.routing import Route
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _create_get_handler(
|
|
20
|
+
broker: Broker,
|
|
21
|
+
) -> Callable[[Request], Awaitable[Response]]:
|
|
22
|
+
"""Create a GET request handler bound to the given broker."""
|
|
23
|
+
|
|
24
|
+
async def handle_get(request: Request) -> Response:
|
|
25
|
+
target = request.url.path
|
|
26
|
+
if request.url.query:
|
|
27
|
+
target = f"{target}?{request.url.query}"
|
|
28
|
+
|
|
29
|
+
body = await request.body()
|
|
30
|
+
chunks = _replay_matching_get(
|
|
31
|
+
broker=broker,
|
|
32
|
+
request_headers=request.headers,
|
|
33
|
+
target=target,
|
|
34
|
+
body=body,
|
|
35
|
+
)
|
|
36
|
+
if chunks is None:
|
|
37
|
+
return Response(status_code=500, content=b"Interaction Not Found")
|
|
38
|
+
|
|
39
|
+
status_code = 200
|
|
40
|
+
if chunks:
|
|
41
|
+
for key, value in chunks[0].metadata:
|
|
42
|
+
if key == "status_code":
|
|
43
|
+
status_code = int(value)
|
|
44
|
+
|
|
45
|
+
response_body = b"".join(chunk.data for chunk in chunks)
|
|
46
|
+
return Response(status_code=status_code, content=response_body)
|
|
47
|
+
|
|
48
|
+
return handle_get
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _replay_matching_get(
|
|
52
|
+
broker: Broker,
|
|
53
|
+
request_headers: Headers,
|
|
54
|
+
target: str,
|
|
55
|
+
body: bytes,
|
|
56
|
+
) -> tuple[ResponseChunk, ...] | None:
|
|
57
|
+
"""Try replay candidates built from stored interaction header schemas."""
|
|
58
|
+
candidates = _build_get_replay_candidates(
|
|
59
|
+
interactions=broker.cassette.interactions,
|
|
60
|
+
request_headers=request_headers,
|
|
61
|
+
target=target,
|
|
62
|
+
body=body,
|
|
63
|
+
)
|
|
64
|
+
for candidate in candidates:
|
|
65
|
+
try:
|
|
66
|
+
return tuple(broker.replay(candidate))
|
|
67
|
+
except InteractionNotFoundError:
|
|
68
|
+
continue
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _build_get_replay_candidates(
|
|
73
|
+
interactions: tuple[Interaction, ...],
|
|
74
|
+
request_headers: Headers,
|
|
75
|
+
target: str,
|
|
76
|
+
body: bytes,
|
|
77
|
+
) -> tuple[InteractionRequest, ...]:
|
|
78
|
+
"""Create candidate requests using the stored header fields per interaction."""
|
|
79
|
+
candidates: list[InteractionRequest] = []
|
|
80
|
+
seen_fingerprints: set[str] = set()
|
|
81
|
+
|
|
82
|
+
for interaction in interactions:
|
|
83
|
+
recorded_request = interaction.request
|
|
84
|
+
if recorded_request.protocol != "http":
|
|
85
|
+
continue
|
|
86
|
+
if recorded_request.action != "GET":
|
|
87
|
+
continue
|
|
88
|
+
if recorded_request.target != target:
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
candidate_headers: list[tuple[str, str]] = []
|
|
92
|
+
missing_header = False
|
|
93
|
+
for key, _ in recorded_request.headers:
|
|
94
|
+
value = request_headers.get(key)
|
|
95
|
+
if value is None:
|
|
96
|
+
missing_header = True
|
|
97
|
+
break
|
|
98
|
+
candidate_headers.append((key, value))
|
|
99
|
+
|
|
100
|
+
if missing_header:
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
candidate = InteractionRequest(
|
|
104
|
+
protocol="http",
|
|
105
|
+
action="GET",
|
|
106
|
+
target=target,
|
|
107
|
+
headers=tuple(candidate_headers),
|
|
108
|
+
body=body,
|
|
109
|
+
)
|
|
110
|
+
candidate_fingerprint = candidate.fingerprint().value
|
|
111
|
+
if candidate_fingerprint in seen_fingerprints:
|
|
112
|
+
continue
|
|
113
|
+
seen_fingerprints.add(candidate_fingerprint)
|
|
114
|
+
candidates.append(candidate)
|
|
115
|
+
|
|
116
|
+
if not candidates:
|
|
117
|
+
candidates.append(
|
|
118
|
+
InteractionRequest(
|
|
119
|
+
protocol="http",
|
|
120
|
+
action="GET",
|
|
121
|
+
target=target,
|
|
122
|
+
headers=(),
|
|
123
|
+
body=body,
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return tuple(candidates)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class InterpositionHttpAdapter(Starlette):
|
|
131
|
+
"""ASGI application that replays HTTP interactions via an Interposition Broker."""
|
|
132
|
+
|
|
133
|
+
def __init__(self, broker: Broker) -> None:
|
|
134
|
+
"""Initialize the adapter with a Broker.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
broker: The Interposition Broker to use for replaying interactions.
|
|
138
|
+
"""
|
|
139
|
+
self._broker = broker
|
|
140
|
+
routes = [
|
|
141
|
+
Route("/{path:path}", _create_get_handler(broker), methods=["GET"]),
|
|
142
|
+
]
|
|
143
|
+
super().__init__(routes=routes)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""CLI module for interposition_http_adapter."""
|
|
2
|
+
|
|
3
|
+
from argparse import ArgumentParser
|
|
4
|
+
|
|
5
|
+
from interposition_http_adapter._version import __version__
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def generate_cli_parser() -> ArgumentParser:
|
|
9
|
+
"""Generate the argument parser for the interposition_http_adapter CLI."""
|
|
10
|
+
parser = ArgumentParser(description="HTTP adapter for Interposition.")
|
|
11
|
+
parser.add_argument("--version", action="version", version=__version__)
|
|
12
|
+
return parser
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main() -> None:
|
|
16
|
+
"""Entry point for the interposition_http_adapter command-line interface."""
|
|
17
|
+
parser = generate_cli_parser()
|
|
18
|
+
_ = parser.parse_args()
|
|
File without changes
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: interposition_http_adapter
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: HTTP adapter for Interposition.
|
|
5
|
+
Author: osoken
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/osoekawaitlab/interposition-http-adapter
|
|
8
|
+
Project-URL: Repository, https://github.com/osoekawaitlab/interposition-http-adapter
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Operating System :: POSIX
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: interposition>=0.3.0
|
|
22
|
+
Requires-Dist: starlette>=0.52.1
|
|
23
|
+
Requires-Dist: uvicorn>=0.40.0
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# interposition-http-adapter
|
|
27
|
+
|
|
28
|
+
HTTP adapter for [Interposition](https://github.com/osoekawaitlab/interposition).
|
|
29
|
+
Serves recorded HTTP interactions from an Interposition `Cassette` as a real HTTP server, allowing you to replay captured API responses for testing and development.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install interposition-http-adapter
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
`InterpositionHttpAdapter` is an ASGI application (based on Starlette) that replays HTTP interactions through an Interposition `Broker`.
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from interposition import (
|
|
43
|
+
Broker,
|
|
44
|
+
Cassette,
|
|
45
|
+
Interaction,
|
|
46
|
+
InteractionRequest,
|
|
47
|
+
ResponseChunk,
|
|
48
|
+
)
|
|
49
|
+
import uvicorn
|
|
50
|
+
|
|
51
|
+
from interposition_http_adapter import InterpositionHttpAdapter
|
|
52
|
+
|
|
53
|
+
# 1. Build an InteractionRequest describing the HTTP request to match
|
|
54
|
+
request = InteractionRequest(
|
|
55
|
+
protocol="http",
|
|
56
|
+
action="GET",
|
|
57
|
+
target="/api/data",
|
|
58
|
+
headers=(),
|
|
59
|
+
body=b"",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# 2. Build ResponseChunks with status_code in the first chunk's metadata
|
|
63
|
+
response_chunks = (
|
|
64
|
+
ResponseChunk(
|
|
65
|
+
data=b'{"message": "hello"}',
|
|
66
|
+
sequence=0,
|
|
67
|
+
metadata=(("status_code", "200"),),
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# 3. Create a Cassette containing the interaction
|
|
72
|
+
interaction = Interaction(
|
|
73
|
+
request=request,
|
|
74
|
+
fingerprint=request.fingerprint(),
|
|
75
|
+
response_chunks=response_chunks,
|
|
76
|
+
)
|
|
77
|
+
cassette = Cassette(interactions=(interaction,))
|
|
78
|
+
|
|
79
|
+
# 4. Create a Broker in replay mode
|
|
80
|
+
broker = Broker(cassette=cassette, mode="replay")
|
|
81
|
+
|
|
82
|
+
# 5. Create the adapter and serve it
|
|
83
|
+
app = InterpositionHttpAdapter(broker=broker)
|
|
84
|
+
uvicorn.run(app, host="127.0.0.1", port=8000)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Once the server is running, a `GET` request to `http://127.0.0.1:8000/api/data` returns the recorded response with status code 200 and body `{"message": "hello"}`.
|
|
88
|
+
Requests that do not match any recorded interaction return a `500 Internal Server Error` response.
|
|
89
|
+
|
|
90
|
+
## API Reference
|
|
91
|
+
|
|
92
|
+
Detailed documentation is available in MkDocs: <https://osoekawaitlab.github.io/interposition-http-adapter/>.
|
|
93
|
+
|
|
94
|
+
## CLI
|
|
95
|
+
|
|
96
|
+
The package provides a command-line interface:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
interposition_http_adapter --version
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
MIT
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/interposition_http_adapter/__init__.py
|
|
5
|
+
src/interposition_http_adapter/_version.py
|
|
6
|
+
src/interposition_http_adapter/app.py
|
|
7
|
+
src/interposition_http_adapter/cli.py
|
|
8
|
+
src/interposition_http_adapter/py.typed
|
|
9
|
+
src/interposition_http_adapter.egg-info/PKG-INFO
|
|
10
|
+
src/interposition_http_adapter.egg-info/SOURCES.txt
|
|
11
|
+
src/interposition_http_adapter.egg-info/dependency_links.txt
|
|
12
|
+
src/interposition_http_adapter.egg-info/entry_points.txt
|
|
13
|
+
src/interposition_http_adapter.egg-info/requires.txt
|
|
14
|
+
src/interposition_http_adapter.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
interposition_http_adapter
|