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.
Files changed (59) hide show
  1. {mockstack-0.4.0/mockstack.egg-info → mockstack-0.4.1}/PKG-INFO +5 -2
  2. {mockstack-0.4.0 → mockstack-0.4.1}/README.md +3 -1
  3. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/config.py +2 -4
  4. mockstack-0.4.1/mockstack/display.py +27 -0
  5. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/intent.py +15 -0
  6. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/strategies/filefixtures.py +3 -6
  7. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/strategies/proxyrules.py +14 -13
  8. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/test_display.py +1 -14
  9. {mockstack-0.4.0 → mockstack-0.4.1/mockstack.egg-info}/PKG-INFO +5 -2
  10. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack.egg-info/requires.txt +1 -0
  11. {mockstack-0.4.0 → mockstack-0.4.1}/pyproject.toml +1 -0
  12. {mockstack-0.4.0 → mockstack-0.4.1}/uv.lock +592 -590
  13. mockstack-0.4.0/mockstack/display.py +0 -38
  14. {mockstack-0.4.0 → mockstack-0.4.1}/.env.example +0 -0
  15. {mockstack-0.4.0 → mockstack-0.4.1}/.github/workflows/ci.yml +0 -0
  16. {mockstack-0.4.0 → mockstack-0.4.1}/.github/workflows/publish-docs.yml +0 -0
  17. {mockstack-0.4.0 → mockstack-0.4.1}/.github/workflows/publish-to-pypi.yml +0 -0
  18. {mockstack-0.4.0 → mockstack-0.4.1}/.gitignore +0 -0
  19. {mockstack-0.4.0 → mockstack-0.4.1}/.pre-commit-config.yaml +0 -0
  20. {mockstack-0.4.0 → mockstack-0.4.1}/CODE_OF_CONDUCT.md +0 -0
  21. {mockstack-0.4.0 → mockstack-0.4.1}/LICENSE +0 -0
  22. {mockstack-0.4.0 → mockstack-0.4.1}/SECURITY.md +0 -0
  23. {mockstack-0.4.0 → mockstack-0.4.1}/docs/assets/favicon.ico +0 -0
  24. {mockstack-0.4.0 → mockstack-0.4.1}/docs/assets/logo.png +0 -0
  25. {mockstack-0.4.0 → mockstack-0.4.1}/docs/assets/mockstack.png +0 -0
  26. {mockstack-0.4.0 → mockstack-0.4.1}/mkdocs.yml +0 -0
  27. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/__init__.py +0 -0
  28. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/constants.py +0 -0
  29. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/identifiers.py +0 -0
  30. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/lifespan.py +0 -0
  31. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/main.py +0 -0
  32. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/middleware.py +0 -0
  33. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/routers/__init__.py +0 -0
  34. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/routers/catchall.py +0 -0
  35. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/routers/homepage.py +0 -0
  36. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/strategies/__init__.py +0 -0
  37. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/strategies/base.py +0 -0
  38. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/strategies/create_mixin.py +0 -0
  39. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/strategies/factory.py +0 -0
  40. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/telemetry.py +0 -0
  41. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/templating.py +0 -0
  42. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/__init__.py +0 -0
  43. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/conftest.py +0 -0
  44. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/fixtures/proxyrules.yml +0 -0
  45. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/fixtures/templates/__init__.py +0 -0
  46. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/fixtures/templates/example-template.j2 +0 -0
  47. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/routers/__init__.py +0 -0
  48. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/routers/test_catchall.py +0 -0
  49. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/routers/test_homepage.py +0 -0
  50. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/strategies/test_filefixtures.py +0 -0
  51. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/strategies/test_proxyrules.py +0 -0
  52. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/test_identifiers.py +0 -0
  53. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/test_middleware.py +0 -0
  54. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack/tests/test_templating.py +0 -0
  55. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack.egg-info/SOURCES.txt +0 -0
  56. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack.egg-info/dependency_links.txt +0 -0
  57. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack.egg-info/entry_points.txt +0 -0
  58. {mockstack-0.4.0 → mockstack-0.4.1}/mockstack.egg-info/top_level.txt +0 -0
  59. {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.0
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 --debug
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 --debug
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.StreamHandler",
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": "DEBUG",
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"{HIGHLIGHT}[filefixtures]{ENDC} "
47
- f"templates_dir: {HIGHLIGHT}{self.templates_dir}{ENDC}. "
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.display import ANSIColors
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"{HIGHLIGHT}[proxyrules]{ENDC} "
77
- f"rules_filename: {HIGHLIGHT}{self.rules_filename}{ENDC}. "
78
- f"redirect_via: {HIGHLIGHT}{self.proxyrules_redirect_via}{ENDC}. "
79
- f"simulate_create_on_missing: {HIGHLIGHT}{self.proxyrules_simulate_create_on_missing}{ENDC}"
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
- if self.proxyrules_simulate_create_on_missing:
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=self.reverse_proxy_headers(request.headers, url=url),
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 ANSIColors, announce
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.0
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 --debug
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.
@@ -7,5 +7,6 @@ opentelemetry-instrumentation-fastapi>=0.53b1
7
7
  pydantic>=2.11.3
8
8
  pydantic-settings>=2.9.1
9
9
  pyyaml>=6.0.2
10
+ rich>=14.0.0
10
11
  typer>=0.15.2
11
12
  uvicorn>=0.34.2
@@ -20,6 +20,7 @@ dependencies = [
20
20
  "pydantic>=2.11.3",
21
21
  "pydantic-settings>=2.9.1",
22
22
  "pyyaml>=6.0.2",
23
+ "rich>=14.0.0",
23
24
  "typer>=0.15.2",
24
25
  "uvicorn>=0.34.2",
25
26
  ]