mockstack 0.4.0__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.
- {mockstack-0.4.0/mockstack.egg-info → mockstack-0.4.2}/PKG-INFO +5 -2
- {mockstack-0.4.0 → mockstack-0.4.2}/README.md +3 -1
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/config.py +2 -4
- mockstack-0.4.2/mockstack/display.py +27 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/intent.py +15 -0
- mockstack-0.4.2/mockstack/middleware.py +55 -0
- mockstack-0.4.2/mockstack/routers/catchall.py +14 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/strategies/filefixtures.py +3 -6
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/strategies/proxyrules.py +14 -13
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/telemetry.py +19 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/tests/test_display.py +1 -14
- {mockstack-0.4.0 → mockstack-0.4.2/mockstack.egg-info}/PKG-INFO +5 -2
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack.egg-info/requires.txt +1 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/pyproject.toml +1 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/uv.lock +592 -590
- mockstack-0.4.0/mockstack/display.py +0 -38
- mockstack-0.4.0/mockstack/middleware.py +0 -19
- mockstack-0.4.0/mockstack/routers/catchall.py +0 -33
- {mockstack-0.4.0 → mockstack-0.4.2}/.env.example +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/.github/workflows/ci.yml +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/.github/workflows/publish-docs.yml +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/.github/workflows/publish-to-pypi.yml +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/.gitignore +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/.pre-commit-config.yaml +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/CODE_OF_CONDUCT.md +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/LICENSE +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/SECURITY.md +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/docs/assets/favicon.ico +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/docs/assets/logo.png +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/docs/assets/mockstack.png +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mkdocs.yml +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/__init__.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/constants.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/identifiers.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/lifespan.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/main.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/routers/__init__.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/routers/homepage.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/strategies/__init__.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/strategies/base.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/strategies/create_mixin.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/strategies/factory.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/templating.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/tests/__init__.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/tests/conftest.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/tests/fixtures/proxyrules.yml +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/tests/fixtures/templates/__init__.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/tests/fixtures/templates/example-template.j2 +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/tests/routers/__init__.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/tests/routers/test_catchall.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/tests/routers/test_homepage.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/tests/strategies/test_filefixtures.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/tests/strategies/test_proxyrules.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/tests/test_identifiers.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/tests/test_middleware.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack/tests/test_templating.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack.egg-info/SOURCES.txt +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack.egg-info/dependency_links.txt +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack.egg-info/entry_points.txt +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/mockstack.egg-info/top_level.txt +0 -0
- {mockstack-0.4.0 → mockstack-0.4.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mockstack
|
|
3
|
-
Version: 0.4.
|
|
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
|
|
@@ -18,6 +18,7 @@ Requires-Dist: opentelemetry-instrumentation-fastapi>=0.53b1
|
|
|
18
18
|
Requires-Dist: pydantic>=2.11.3
|
|
19
19
|
Requires-Dist: pydantic-settings>=2.9.1
|
|
20
20
|
Requires-Dist: pyyaml>=6.0.2
|
|
21
|
+
Requires-Dist: rich>=14.0.0
|
|
21
22
|
Requires-Dist: typer>=0.15.2
|
|
22
23
|
Requires-Dist: uvicorn>=0.34.2
|
|
23
24
|
Dynamic: license-file
|
|
@@ -104,4 +105,6 @@ If you are contributing to development, you will want to clone this project, and
|
|
|
104
105
|
|
|
105
106
|
Run in development mode (for live-reload of changes when developing):
|
|
106
107
|
|
|
107
|
-
uv run -- mockstack --
|
|
108
|
+
uv run uvicorn --factory mockstack.main:create_app --reload
|
|
109
|
+
|
|
110
|
+
Note that when you run using the uvicorn CLI, you will need to set any configuration via `.env` file or environment variables.
|
|
@@ -80,4 +80,6 @@ If you are contributing to development, you will want to clone this project, and
|
|
|
80
80
|
|
|
81
81
|
Run in development mode (for live-reload of changes when developing):
|
|
82
82
|
|
|
83
|
-
uv run -- mockstack --
|
|
83
|
+
uv run uvicorn --factory mockstack.main:create_app --reload
|
|
84
|
+
|
|
85
|
+
Note that when you run using the uvicorn CLI, you will need to set any configuration via `.env` file or environment variables.
|
|
@@ -103,10 +103,8 @@ class Settings(BaseSettings):
|
|
|
103
103
|
},
|
|
104
104
|
"handlers": {
|
|
105
105
|
"console": {
|
|
106
|
-
"class": "logging.
|
|
106
|
+
"class": "rich.logging.RichHandler",
|
|
107
107
|
"level": "INFO",
|
|
108
|
-
"formatter": "standard",
|
|
109
|
-
"stream": "ext://sys.stdout",
|
|
110
108
|
},
|
|
111
109
|
},
|
|
112
110
|
"loggers": {
|
|
@@ -128,7 +126,7 @@ class Settings(BaseSettings):
|
|
|
128
126
|
},
|
|
129
127
|
"root": {
|
|
130
128
|
"handlers": ["console"],
|
|
131
|
-
"level": "
|
|
129
|
+
"level": "NOTSET",
|
|
132
130
|
"propagate": False,
|
|
133
131
|
},
|
|
134
132
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Display and logging functionality."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from fastapi import FastAPI
|
|
6
|
+
|
|
7
|
+
from mockstack.config import Settings
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def announce(app: FastAPI, settings: Settings):
|
|
11
|
+
"""Log the startup message with the active settings."""
|
|
12
|
+
logger = logging.getLogger("uvicorn")
|
|
13
|
+
extra = {"markup": True}
|
|
14
|
+
|
|
15
|
+
logger.info(
|
|
16
|
+
f"[bold medium_purple]mockstack[/bold medium_purple] ready to roll. "
|
|
17
|
+
f"debug: [medium_purple]{settings.debug}[/medium_purple]. "
|
|
18
|
+
f"strategy: [medium_purple]{settings.strategy}[/medium_purple]. ",
|
|
19
|
+
extra=extra,
|
|
20
|
+
)
|
|
21
|
+
logger.info(str(app.state.strategy), extra=extra)
|
|
22
|
+
logger.info(
|
|
23
|
+
f"OpenTelemetry enabled: [medium_purple]{settings.opentelemetry.enabled}[/medium_purple], "
|
|
24
|
+
f"endpoint: [medium_purple]{settings.opentelemetry.endpoint}[/medium_purple], "
|
|
25
|
+
f"capture_response_body: [medium_purple]{settings.opentelemetry.capture_response_body}[/medium_purple]",
|
|
26
|
+
extra=extra,
|
|
27
|
+
)
|
|
@@ -51,3 +51,18 @@ def looks_like_a_command(request: Request) -> bool:
|
|
|
51
51
|
request.url.path.endswith("/execute"),
|
|
52
52
|
)
|
|
53
53
|
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def looks_like_a_create(request: Request) -> bool:
|
|
57
|
+
"""Check if the request looks like a create.
|
|
58
|
+
|
|
59
|
+
This is a heuristic to try and identify cases where a POST
|
|
60
|
+
request is used for creating a new resource.
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
return any(
|
|
64
|
+
(
|
|
65
|
+
request.method == "POST",
|
|
66
|
+
request.url.path.endswith("/create"),
|
|
67
|
+
),
|
|
68
|
+
)
|
|
@@ -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,7 +8,6 @@ from fastapi import HTTPException, Request, Response, status
|
|
|
8
8
|
from jinja2 import Environment, FileSystemLoader
|
|
9
9
|
|
|
10
10
|
from mockstack.config import Settings
|
|
11
|
-
from mockstack.display import ANSIColors
|
|
12
11
|
from mockstack.intent import (
|
|
13
12
|
looks_like_a_command,
|
|
14
13
|
looks_like_a_search,
|
|
@@ -39,12 +38,9 @@ class FileFixturesStrategy(BaseStrategy, CreateMixin):
|
|
|
39
38
|
self.env = Environment(loader=FileSystemLoader(self.templates_dir))
|
|
40
39
|
|
|
41
40
|
def __str__(self) -> str:
|
|
42
|
-
HIGHLIGHT = ANSIColors.HEADER
|
|
43
|
-
ENDC = ANSIColors.ENDC
|
|
44
|
-
|
|
45
41
|
return (
|
|
46
|
-
f"
|
|
47
|
-
f"templates_dir: {
|
|
42
|
+
f"[medium_purple]filefixtures[/medium_purple] "
|
|
43
|
+
f"templates_dir: [medium_purple]{self.templates_dir}[/medium_purple]. "
|
|
48
44
|
)
|
|
49
45
|
|
|
50
46
|
async def apply(self, request: Request) -> Response:
|
|
@@ -85,6 +81,7 @@ class FileFixturesStrategy(BaseStrategy, CreateMixin):
|
|
|
85
81
|
request, status_code=status.HTTP_201_CREATED
|
|
86
82
|
)
|
|
87
83
|
else:
|
|
84
|
+
# simulate resource creation:
|
|
88
85
|
return await self._create(
|
|
89
86
|
request,
|
|
90
87
|
env=self.env,
|
|
@@ -15,7 +15,7 @@ from starlette.datastructures import Headers
|
|
|
15
15
|
|
|
16
16
|
from mockstack.config import Settings
|
|
17
17
|
from mockstack.constants import ProxyRulesRedirectVia
|
|
18
|
-
from mockstack.
|
|
18
|
+
from mockstack.intent import looks_like_a_create
|
|
19
19
|
from mockstack.strategies.base import BaseStrategy
|
|
20
20
|
from mockstack.strategies.create_mixin import CreateMixin
|
|
21
21
|
|
|
@@ -69,14 +69,11 @@ class ProxyRulesStrategy(BaseStrategy, CreateMixin):
|
|
|
69
69
|
self.env = Environment()
|
|
70
70
|
|
|
71
71
|
def __str__(self) -> str:
|
|
72
|
-
HIGHLIGHT = ANSIColors.HEADER
|
|
73
|
-
ENDC = ANSIColors.ENDC
|
|
74
|
-
|
|
75
72
|
return (
|
|
76
|
-
f"
|
|
77
|
-
f"rules_filename: {
|
|
78
|
-
f"redirect_via: {
|
|
79
|
-
f"simulate_create_on_missing: {
|
|
73
|
+
f"[medium_purple]proxyrules[/medium_purple] "
|
|
74
|
+
f"rules_filename: [medium_purple]{self.rules_filename}[/medium_purple]. "
|
|
75
|
+
f"redirect_via: [medium_purple]{self.proxyrules_redirect_via}[/medium_purple]. "
|
|
76
|
+
f"simulate_create_on_missing: [medium_purple]{self.proxyrules_simulate_create_on_missing}[/medium_purple]"
|
|
80
77
|
)
|
|
81
78
|
|
|
82
79
|
@cached_property
|
|
@@ -103,9 +100,12 @@ class ProxyRulesStrategy(BaseStrategy, CreateMixin):
|
|
|
103
100
|
self.logger.warning(
|
|
104
101
|
f"No rule found for request: {request.method} {request.url.path}"
|
|
105
102
|
)
|
|
106
|
-
|
|
103
|
+
|
|
104
|
+
if self.proxyrules_simulate_create_on_missing and looks_like_a_create(
|
|
105
|
+
request
|
|
106
|
+
):
|
|
107
107
|
self.logger.info(
|
|
108
|
-
"Simulating resource creation for missing rule for {request.method} {request.url.path}"
|
|
108
|
+
f"Simulating resource creation for missing rule for {request.method} {request.url.path}"
|
|
109
109
|
)
|
|
110
110
|
return await self._create(
|
|
111
111
|
request,
|
|
@@ -113,6 +113,7 @@ class ProxyRulesStrategy(BaseStrategy, CreateMixin):
|
|
|
113
113
|
created_resource_metadata=self.created_resource_metadata,
|
|
114
114
|
)
|
|
115
115
|
else:
|
|
116
|
+
# no rule found and we are not simulating resource creation
|
|
116
117
|
return Response(status_code=status.HTTP_404_NOT_FOUND)
|
|
117
118
|
|
|
118
119
|
url = rule.apply(request)
|
|
@@ -141,13 +142,13 @@ class ProxyRulesStrategy(BaseStrategy, CreateMixin):
|
|
|
141
142
|
async def reverse_proxy(self, request: Request, url: str) -> Response:
|
|
142
143
|
"""Reverse proxy the request to the target URL."""
|
|
143
144
|
async with httpx.AsyncClient() as client:
|
|
144
|
-
method = request.method
|
|
145
145
|
request_content = await request.body()
|
|
146
|
+
request_headers = self.reverse_proxy_headers(request.headers, url=url)
|
|
146
147
|
req = client.build_request(
|
|
147
|
-
method,
|
|
148
|
+
request.method,
|
|
148
149
|
url,
|
|
149
150
|
content=request_content,
|
|
150
|
-
headers=
|
|
151
|
+
headers=request_headers,
|
|
151
152
|
params=request.url.query,
|
|
152
153
|
)
|
|
153
154
|
|
|
@@ -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:
|
|
@@ -2,20 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from unittest.mock import patch
|
|
4
4
|
|
|
5
|
-
from mockstack.display import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def test_ansicolors_constants():
|
|
9
|
-
"""Test that ANSIColors constants are defined correctly."""
|
|
10
|
-
assert ANSIColors.HEADER == "\033[95m"
|
|
11
|
-
assert ANSIColors.OKBLUE == "\033[94m"
|
|
12
|
-
assert ANSIColors.OKCYAN == "\033[96m"
|
|
13
|
-
assert ANSIColors.OKGREEN == "\033[92m"
|
|
14
|
-
assert ANSIColors.WARNING == "\033[93m"
|
|
15
|
-
assert ANSIColors.FAIL == "\033[91m"
|
|
16
|
-
assert ANSIColors.ENDC == "\033[0m"
|
|
17
|
-
assert ANSIColors.BOLD == "\033[1m"
|
|
18
|
-
assert ANSIColors.UNDERLINE == "\033[4m"
|
|
5
|
+
from mockstack.display import announce
|
|
19
6
|
|
|
20
7
|
|
|
21
8
|
def test_announce(app, settings):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mockstack
|
|
3
|
-
Version: 0.4.
|
|
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
|
|
@@ -18,6 +18,7 @@ Requires-Dist: opentelemetry-instrumentation-fastapi>=0.53b1
|
|
|
18
18
|
Requires-Dist: pydantic>=2.11.3
|
|
19
19
|
Requires-Dist: pydantic-settings>=2.9.1
|
|
20
20
|
Requires-Dist: pyyaml>=6.0.2
|
|
21
|
+
Requires-Dist: rich>=14.0.0
|
|
21
22
|
Requires-Dist: typer>=0.15.2
|
|
22
23
|
Requires-Dist: uvicorn>=0.34.2
|
|
23
24
|
Dynamic: license-file
|
|
@@ -104,4 +105,6 @@ If you are contributing to development, you will want to clone this project, and
|
|
|
104
105
|
|
|
105
106
|
Run in development mode (for live-reload of changes when developing):
|
|
106
107
|
|
|
107
|
-
uv run -- mockstack --
|
|
108
|
+
uv run uvicorn --factory mockstack.main:create_app --reload
|
|
109
|
+
|
|
110
|
+
Note that when you run using the uvicorn CLI, you will need to set any configuration via `.env` file or environment variables.
|