mockstack 0.4.1__tar.gz → 0.4.2__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 (60) hide show
  1. {mockstack-0.4.1/mockstack.egg-info → mockstack-0.4.2}/PKG-INFO +1 -1
  2. mockstack-0.4.2/mockstack/middleware.py +55 -0
  3. mockstack-0.4.2/mockstack/routers/catchall.py +14 -0
  4. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/telemetry.py +19 -0
  5. {mockstack-0.4.1 → mockstack-0.4.2/mockstack.egg-info}/PKG-INFO +1 -1
  6. mockstack-0.4.1/mockstack/middleware.py +0 -19
  7. mockstack-0.4.1/mockstack/routers/catchall.py +0 -33
  8. {mockstack-0.4.1 → mockstack-0.4.2}/.env.example +0 -0
  9. {mockstack-0.4.1 → mockstack-0.4.2}/.github/workflows/ci.yml +0 -0
  10. {mockstack-0.4.1 → mockstack-0.4.2}/.github/workflows/publish-docs.yml +0 -0
  11. {mockstack-0.4.1 → mockstack-0.4.2}/.github/workflows/publish-to-pypi.yml +0 -0
  12. {mockstack-0.4.1 → mockstack-0.4.2}/.gitignore +0 -0
  13. {mockstack-0.4.1 → mockstack-0.4.2}/.pre-commit-config.yaml +0 -0
  14. {mockstack-0.4.1 → mockstack-0.4.2}/CODE_OF_CONDUCT.md +0 -0
  15. {mockstack-0.4.1 → mockstack-0.4.2}/LICENSE +0 -0
  16. {mockstack-0.4.1 → mockstack-0.4.2}/README.md +0 -0
  17. {mockstack-0.4.1 → mockstack-0.4.2}/SECURITY.md +0 -0
  18. {mockstack-0.4.1 → mockstack-0.4.2}/docs/assets/favicon.ico +0 -0
  19. {mockstack-0.4.1 → mockstack-0.4.2}/docs/assets/logo.png +0 -0
  20. {mockstack-0.4.1 → mockstack-0.4.2}/docs/assets/mockstack.png +0 -0
  21. {mockstack-0.4.1 → mockstack-0.4.2}/mkdocs.yml +0 -0
  22. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/__init__.py +0 -0
  23. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/config.py +0 -0
  24. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/constants.py +0 -0
  25. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/display.py +0 -0
  26. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/identifiers.py +0 -0
  27. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/intent.py +0 -0
  28. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/lifespan.py +0 -0
  29. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/main.py +0 -0
  30. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/routers/__init__.py +0 -0
  31. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/routers/homepage.py +0 -0
  32. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/strategies/__init__.py +0 -0
  33. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/strategies/base.py +0 -0
  34. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/strategies/create_mixin.py +0 -0
  35. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/strategies/factory.py +0 -0
  36. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/strategies/filefixtures.py +0 -0
  37. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/strategies/proxyrules.py +0 -0
  38. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/templating.py +0 -0
  39. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/tests/__init__.py +0 -0
  40. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/tests/conftest.py +0 -0
  41. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/tests/fixtures/proxyrules.yml +0 -0
  42. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/tests/fixtures/templates/__init__.py +0 -0
  43. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/tests/fixtures/templates/example-template.j2 +0 -0
  44. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/tests/routers/__init__.py +0 -0
  45. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/tests/routers/test_catchall.py +0 -0
  46. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/tests/routers/test_homepage.py +0 -0
  47. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/tests/strategies/test_filefixtures.py +0 -0
  48. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/tests/strategies/test_proxyrules.py +0 -0
  49. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/tests/test_display.py +0 -0
  50. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/tests/test_identifiers.py +0 -0
  51. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/tests/test_middleware.py +0 -0
  52. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack/tests/test_templating.py +0 -0
  53. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack.egg-info/SOURCES.txt +0 -0
  54. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack.egg-info/dependency_links.txt +0 -0
  55. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack.egg-info/entry_points.txt +0 -0
  56. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack.egg-info/requires.txt +0 -0
  57. {mockstack-0.4.1 → mockstack-0.4.2}/mockstack.egg-info/top_level.txt +0 -0
  58. {mockstack-0.4.1 → mockstack-0.4.2}/pyproject.toml +0 -0
  59. {mockstack-0.4.1 → mockstack-0.4.2}/setup.cfg +0 -0
  60. {mockstack-0.4.1 → mockstack-0.4.2}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mockstack
3
- Version: 0.4.1
3
+ Version: 0.4.2
4
4
  Summary: An API mocking workhorse
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/adamhadani/mockstack
@@ -0,0 +1,55 @@
1
+ """Middleware definitionsfor the mockstack app."""
2
+
3
+ import time
4
+
5
+ from fastapi import FastAPI, Request, Response
6
+ from opentelemetry import trace
7
+ from opentelemetry.propagate import extract
8
+
9
+ from mockstack.config import Settings
10
+ from mockstack.telemetry import extract_body, span_name_for
11
+
12
+
13
+ def middleware_provider(app: FastAPI, settings: Settings) -> None:
14
+ """Instrument the middlewares to the mockstack app."""
15
+
16
+ @app.middleware("http")
17
+ async def add_process_time_header(request: Request, call_next):
18
+ start_time = time.time()
19
+ response = await call_next(request)
20
+ process_time = time.time() - start_time
21
+ response.headers["X-Process-Time"] = str(process_time)
22
+ return response
23
+
24
+ @app.middleware("http")
25
+ async def instrument_opentelemetry(request: Request, call_next):
26
+ tracer = trace.get_tracer(__name__)
27
+ ctx = extract(request.headers)
28
+ with tracer.start_as_current_span(span_name_for(request), context=ctx) as span:
29
+ span.set_attribute("http.method", request.method)
30
+ span.set_attribute("http.url", str(request.url))
31
+
32
+ response = await call_next(request)
33
+
34
+ span.set_attribute("http.status_code", response.status_code)
35
+
36
+ # Nb. persisting response body can hamper performance,
37
+ # expose sensitive / PII data, and / or may not be needed.
38
+ # it is therefore an opt-in setting.
39
+ if settings.opentelemetry.capture_response_body:
40
+ body = await extract_body(response)
41
+
42
+ # for semantics of payload collection see:
43
+ # https://github.com/open-telemetry/oteps/pull/234
44
+ span.set_attribute("http.response.body", body)
45
+
46
+ # recreate response with the same body since when consuming it to log it above
47
+ # we effectively "deplete" the iterator.
48
+ response = Response(
49
+ content=body,
50
+ status_code=response.status_code,
51
+ headers=dict(response.headers),
52
+ media_type=response.media_type,
53
+ )
54
+
55
+ return response
@@ -0,0 +1,14 @@
1
+ """Routes for the mockstack app."""
2
+
3
+ from fastapi import FastAPI, Request
4
+
5
+ from mockstack.config import Settings
6
+
7
+
8
+ def catchall_router_provider(app: FastAPI, settings: Settings) -> None:
9
+ """Create the catch-all routes for the mockstack app."""
10
+
11
+ @app.route("/{full_path:path}", methods=["GET", "PATCH", "POST", "PUT", "DELETE"])
12
+ async def catch_all(request: Request):
13
+ """Catch all requests and delegate to the strategy."""
14
+ return await app.state.strategy.apply(request)
@@ -8,6 +8,7 @@ from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExport
8
8
  from opentelemetry.sdk.resources import Resource
9
9
  from opentelemetry.sdk.trace import TracerProvider
10
10
  from opentelemetry.sdk.trace.export import BatchSpanProcessor
11
+ from starlette.responses import StreamingResponse
11
12
 
12
13
  from mockstack.config import Settings
13
14
 
@@ -17,6 +18,24 @@ def span_name_for(request: Request) -> str:
17
18
  return f"{request.method.upper()} {request.url.path}"
18
19
 
19
20
 
21
+ async def extract_body(response: StreamingResponse) -> str:
22
+ """Extract the body of a response."""
23
+
24
+ async def read_response_body(response: StreamingResponse) -> bytes:
25
+ """Helper function to read response body asynchronously into memory."""
26
+ body = b""
27
+ async for chunk in response.body_iterator:
28
+ if isinstance(chunk, str):
29
+ body += chunk.encode()
30
+ else:
31
+ body += chunk
32
+ return body
33
+
34
+ body = await read_response_body(response)
35
+
36
+ return body.decode()
37
+
38
+
20
39
  def opentelemetry_provider(app: FastAPI, settings: Settings) -> None:
21
40
  """Initialize OpenTelemetry for the mockstack app."""
22
41
  if not settings.opentelemetry.enabled:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mockstack
3
- Version: 0.4.1
3
+ Version: 0.4.2
4
4
  Summary: An API mocking workhorse
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/adamhadani/mockstack
@@ -1,19 +0,0 @@
1
- """Middleware definitionsfor the mockstack app."""
2
-
3
- import time
4
-
5
- from fastapi import FastAPI, Request
6
-
7
- from mockstack.config import Settings
8
-
9
-
10
- def middleware_provider(app: FastAPI, settings: Settings) -> None:
11
- """Instrument the middlewares to the mockstack app."""
12
-
13
- @app.middleware("http")
14
- async def add_process_time_header(request: Request, call_next):
15
- start_time = time.time()
16
- response = await call_next(request)
17
- process_time = time.time() - start_time
18
- response.headers["X-Process-Time"] = str(process_time)
19
- return response
@@ -1,33 +0,0 @@
1
- """Routes for the mockstack app."""
2
-
3
- from fastapi import FastAPI, Request
4
- from opentelemetry import trace
5
- from opentelemetry.propagate import extract
6
-
7
- from mockstack.config import Settings
8
- from mockstack.telemetry import span_name_for
9
-
10
-
11
- def catchall_router_provider(app: FastAPI, settings: Settings) -> None:
12
- """Create the catch-all routes for the mockstack app."""
13
-
14
- @app.route("/{full_path:path}", methods=["GET", "PATCH", "POST", "PUT", "DELETE"])
15
- async def catch_all(request: Request):
16
- """Catch all requests and delegate to the strategy."""
17
- tracer = trace.get_tracer(__name__)
18
- ctx = extract(request.headers)
19
- with tracer.start_as_current_span(span_name_for(request), context=ctx) as span:
20
- span.set_attribute("http.method", request.method)
21
- span.set_attribute("http.url", str(request.url))
22
-
23
- response = await app.state.strategy.apply(request)
24
-
25
- span.set_attribute("http.status_code", response.status_code)
26
-
27
- # Nb. persisting response body can hamper performance,
28
- # expose sensitive / PII data, and / or may not be needed.
29
- # it is therefore an opt-in setting.
30
- if settings.opentelemetry.capture_response_body:
31
- span.set_attribute("response", response.body)
32
-
33
- return response
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes