mockstack 0.4.0__tar.gz → 0.4.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.
- {mockstack-0.4.0/mockstack.egg-info → mockstack-0.4.1}/PKG-INFO +5 -2
- {mockstack-0.4.0 → mockstack-0.4.1}/README.md +3 -1
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/config.py +2 -4
- mockstack-0.4.1/mockstack/display.py +27 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/intent.py +15 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/strategies/filefixtures.py +3 -6
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/strategies/proxyrules.py +14 -13
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/test_display.py +1 -14
- {mockstack-0.4.0 → mockstack-0.4.1/mockstack.egg-info}/PKG-INFO +5 -2
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack.egg-info/requires.txt +1 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/pyproject.toml +1 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/uv.lock +592 -590
- mockstack-0.4.0/mockstack/display.py +0 -38
- {mockstack-0.4.0 → mockstack-0.4.1}/.env.example +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/.github/workflows/ci.yml +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/.github/workflows/publish-docs.yml +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/.github/workflows/publish-to-pypi.yml +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/.gitignore +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/.pre-commit-config.yaml +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/CODE_OF_CONDUCT.md +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/LICENSE +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/SECURITY.md +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/docs/assets/favicon.ico +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/docs/assets/logo.png +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/docs/assets/mockstack.png +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mkdocs.yml +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/__init__.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/constants.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/identifiers.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/lifespan.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/main.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/middleware.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/routers/__init__.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/routers/catchall.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/routers/homepage.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/strategies/__init__.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/strategies/base.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/strategies/create_mixin.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/strategies/factory.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/telemetry.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/templating.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/__init__.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/conftest.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/fixtures/proxyrules.yml +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/fixtures/templates/__init__.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/fixtures/templates/example-template.j2 +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/routers/__init__.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/routers/test_catchall.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/routers/test_homepage.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/strategies/test_filefixtures.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/strategies/test_proxyrules.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/test_identifiers.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/test_middleware.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/test_templating.py +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack.egg-info/SOURCES.txt +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack.egg-info/dependency_links.txt +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack.egg-info/entry_points.txt +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/mockstack.egg-info/top_level.txt +0 -0
- {mockstack-0.4.0 → mockstack-0.4.1}/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.1
|
|
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
|
+
)
|
|
@@ -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
|
|
|
@@ -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.1
|
|
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.
|