mockstack 0.3.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 (60) hide show
  1. {mockstack-0.3.0/mockstack.egg-info → mockstack-0.4.1}/PKG-INFO +12 -6
  2. {mockstack-0.3.0 → mockstack-0.4.1}/README.md +10 -5
  3. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/config.py +52 -17
  4. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/constants.py +4 -1
  5. mockstack-0.4.1/mockstack/display.py +27 -0
  6. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/intent.py +15 -0
  7. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/lifespan.py +6 -1
  8. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/main.py +16 -18
  9. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/strategies/filefixtures.py +3 -6
  10. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/strategies/proxyrules.py +14 -13
  11. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/tests/test_display.py +1 -14
  12. {mockstack-0.3.0 → mockstack-0.4.1/mockstack.egg-info}/PKG-INFO +12 -6
  13. mockstack-0.4.1/mockstack.egg-info/entry_points.txt +2 -0
  14. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack.egg-info/requires.txt +1 -0
  15. {mockstack-0.3.0 → mockstack-0.4.1}/pyproject.toml +2 -1
  16. {mockstack-0.3.0 → mockstack-0.4.1}/uv.lock +592 -590
  17. mockstack-0.3.0/mockstack/display.py +0 -37
  18. mockstack-0.3.0/mockstack.egg-info/entry_points.txt +0 -2
  19. {mockstack-0.3.0 → mockstack-0.4.1}/.env.example +0 -0
  20. {mockstack-0.3.0 → mockstack-0.4.1}/.github/workflows/ci.yml +0 -0
  21. {mockstack-0.3.0 → mockstack-0.4.1}/.github/workflows/publish-docs.yml +0 -0
  22. {mockstack-0.3.0 → mockstack-0.4.1}/.github/workflows/publish-to-pypi.yml +0 -0
  23. {mockstack-0.3.0 → mockstack-0.4.1}/.gitignore +0 -0
  24. {mockstack-0.3.0 → mockstack-0.4.1}/.pre-commit-config.yaml +0 -0
  25. {mockstack-0.3.0 → mockstack-0.4.1}/CODE_OF_CONDUCT.md +0 -0
  26. {mockstack-0.3.0 → mockstack-0.4.1}/LICENSE +0 -0
  27. {mockstack-0.3.0 → mockstack-0.4.1}/SECURITY.md +0 -0
  28. {mockstack-0.3.0 → mockstack-0.4.1}/docs/assets/favicon.ico +0 -0
  29. {mockstack-0.3.0 → mockstack-0.4.1}/docs/assets/logo.png +0 -0
  30. {mockstack-0.3.0 → mockstack-0.4.1}/docs/assets/mockstack.png +0 -0
  31. {mockstack-0.3.0 → mockstack-0.4.1}/mkdocs.yml +0 -0
  32. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/__init__.py +0 -0
  33. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/identifiers.py +0 -0
  34. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/middleware.py +0 -0
  35. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/routers/__init__.py +0 -0
  36. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/routers/catchall.py +0 -0
  37. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/routers/homepage.py +0 -0
  38. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/strategies/__init__.py +0 -0
  39. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/strategies/base.py +0 -0
  40. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/strategies/create_mixin.py +0 -0
  41. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/strategies/factory.py +0 -0
  42. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/telemetry.py +0 -0
  43. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/templating.py +0 -0
  44. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/tests/__init__.py +0 -0
  45. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/tests/conftest.py +0 -0
  46. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/tests/fixtures/proxyrules.yml +0 -0
  47. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/tests/fixtures/templates/__init__.py +0 -0
  48. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/tests/fixtures/templates/example-template.j2 +0 -0
  49. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/tests/routers/__init__.py +0 -0
  50. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/tests/routers/test_catchall.py +0 -0
  51. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/tests/routers/test_homepage.py +0 -0
  52. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/tests/strategies/test_filefixtures.py +0 -0
  53. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/tests/strategies/test_proxyrules.py +0 -0
  54. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/tests/test_identifiers.py +0 -0
  55. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/tests/test_middleware.py +0 -0
  56. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack/tests/test_templating.py +0 -0
  57. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack.egg-info/SOURCES.txt +0 -0
  58. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack.egg-info/dependency_links.txt +0 -0
  59. {mockstack-0.3.0 → mockstack-0.4.1}/mockstack.egg-info/top_level.txt +0 -0
  60. {mockstack-0.3.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.3.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
@@ -47,7 +48,7 @@ Highlights include:
47
48
 
48
49
  Install using [uv](https://docs.astral.sh/uv/). This package conforms the concept of a [tool](https://docs.astral.sh/uv/concepts/tools/) and hence can simply install / run with `uvx`:
49
50
 
50
- uvx mockstack
51
+ uvx mockstack --help
51
52
 
52
53
  or install into a persistent environment and add it to the PATH with:
53
54
 
@@ -56,15 +57,20 @@ or install into a persistent environment and add it to the PATH with:
56
57
 
57
58
  ## Usage
58
59
 
59
- Available configuration options are [here](https://github.com/adamhadani/mockstack/blob/main/mockstack/config.py). Setting individual options can be done with env. variables as in the following example:
60
+ Available configuration options are [here](https://github.com/adamhadani/mockstack/blob/main/mockstack/config.py).
61
+
62
+ Setting individual options can be done either through an `.env` file, individual environment variables, or command-line arguments. For example:
60
63
 
61
64
  ```shell
65
+ export MOCKSTACK__STRATEGY=filefixtures
62
66
  export MOCKSTACK__TEMPLATES_DIR=~/mockstack-templates/
63
67
  export MOCKSTACK__OPENTELEMETRY__ENABLED=true
64
68
  export MOCKSTACK__OPENTELEMETRY__CAPTURE_RESPONSE_BODY=true
65
- uv run
69
+ uvx mockstack
66
70
  ```
67
71
 
72
+ See also the included [.env.example](https://github.com/adamhadani/mockstack/blob/main/.env.example) for more examples. You can copy that file to `.env` and fill in configuration as needed based on the given examples.
73
+
68
74
  Out of the box, you get the following behavior when using the default `filefixtures` strategy:
69
75
 
70
76
  - The HTTP request `GET /someservice/api/v1/user/c27f5b2b-6e81-420d-a4e4-6426e1c32db8` will try to find `<templates_dir>/someservice-api-v1-user.c27f5b2b-6e81-420d-a4e4-6426e1c32db8.j2`,
@@ -97,8 +103,8 @@ If you are contributing to development, you will want to clone this project, and
97
103
  uv sync
98
104
  uv pip install -e .
99
105
 
100
- Copy the included [.env.example](https://github.com/adamhadani/mockstack/blob/main/.env.example) file to `.env` and fill in configuration as needed based on the given examples.
101
-
102
106
  Run in development mode (for live-reload of changes when developing):
103
107
 
104
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.
@@ -23,7 +23,7 @@ Highlights include:
23
23
 
24
24
  Install using [uv](https://docs.astral.sh/uv/). This package conforms the concept of a [tool](https://docs.astral.sh/uv/concepts/tools/) and hence can simply install / run with `uvx`:
25
25
 
26
- uvx mockstack
26
+ uvx mockstack --help
27
27
 
28
28
  or install into a persistent environment and add it to the PATH with:
29
29
 
@@ -32,15 +32,20 @@ or install into a persistent environment and add it to the PATH with:
32
32
 
33
33
  ## Usage
34
34
 
35
- Available configuration options are [here](https://github.com/adamhadani/mockstack/blob/main/mockstack/config.py). Setting individual options can be done with env. variables as in the following example:
35
+ Available configuration options are [here](https://github.com/adamhadani/mockstack/blob/main/mockstack/config.py).
36
+
37
+ Setting individual options can be done either through an `.env` file, individual environment variables, or command-line arguments. For example:
36
38
 
37
39
  ```shell
40
+ export MOCKSTACK__STRATEGY=filefixtures
38
41
  export MOCKSTACK__TEMPLATES_DIR=~/mockstack-templates/
39
42
  export MOCKSTACK__OPENTELEMETRY__ENABLED=true
40
43
  export MOCKSTACK__OPENTELEMETRY__CAPTURE_RESPONSE_BODY=true
41
- uv run
44
+ uvx mockstack
42
45
  ```
43
46
 
47
+ See also the included [.env.example](https://github.com/adamhadani/mockstack/blob/main/.env.example) for more examples. You can copy that file to `.env` and fill in configuration as needed based on the given examples.
48
+
44
49
  Out of the box, you get the following behavior when using the default `filefixtures` strategy:
45
50
 
46
51
  - The HTTP request `GET /someservice/api/v1/user/c27f5b2b-6e81-420d-a4e4-6426e1c32db8` will try to find `<templates_dir>/someservice-api-v1-user.c27f5b2b-6e81-420d-a4e4-6426e1c32db8.j2`,
@@ -73,8 +78,8 @@ If you are contributing to development, you will want to clone this project, and
73
78
  uv sync
74
79
  uv pip install -e .
75
80
 
76
- Copy the included [.env.example](https://github.com/adamhadani/mockstack/blob/main/.env.example) file to `.env` and fill in configuration as needed based on the given examples.
77
-
78
81
  Run in development mode (for live-reload of changes when developing):
79
82
 
80
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.
@@ -2,9 +2,19 @@ from functools import lru_cache
2
2
  from typing import Any, Literal, Self
3
3
 
4
4
  from pydantic import DirectoryPath, FilePath, model_validator
5
- from pydantic_settings import BaseSettings, SettingsConfigDict
5
+ from pydantic_settings import (
6
+ BaseSettings,
7
+ CliImplicitFlag,
8
+ CliSuppress,
9
+ SettingsConfigDict,
10
+ )
6
11
 
7
- from mockstack.constants import ProxyRulesRedirectVia
12
+ from mockstack.constants import (
13
+ ENV_FILE,
14
+ ENV_NESTED_DELIMITER,
15
+ ENV_PREFIX,
16
+ ProxyRulesRedirectVia,
17
+ )
8
18
 
9
19
 
10
20
  class OpenTelemetrySettings(BaseSettings):
@@ -28,11 +38,20 @@ class Settings(BaseSettings):
28
38
  """
29
39
 
30
40
  model_config = SettingsConfigDict(
31
- env_prefix="mockstack__",
32
- env_file=".env",
33
- env_nested_delimiter="__",
41
+ env_prefix=ENV_PREFIX,
42
+ env_file=ENV_FILE,
43
+ env_nested_delimiter=ENV_NESTED_DELIMITER,
34
44
  )
35
45
 
46
+ # whether to run in debug mode
47
+ debug: CliImplicitFlag[bool] = False
48
+
49
+ # host to run the server on
50
+ host: str = "0.0.0.0"
51
+
52
+ # port to run the server on
53
+ port: int = 8000
54
+
36
55
  # OpenTelemetry configuration
37
56
  opentelemetry: OpenTelemetrySettings = OpenTelemetrySettings()
38
57
 
@@ -51,11 +70,11 @@ class Settings(BaseSettings):
51
70
 
52
71
  # controls behavior of proxying. Whether to simulate creation of resources
53
72
  # when a POST request is made to a resource that doesn't match any rules..
54
- proxyrules_simulate_create_on_missing: bool = False
73
+ proxyrules_simulate_create_on_missing: CliImplicitFlag[bool] = False
55
74
 
56
75
  # metadata fields to inject into created resources.
57
76
  # A few template fields are available. See documentation for more details.
58
- created_resource_metadata: dict[str, Any] = {
77
+ created_resource_metadata: CliSuppress[dict[str, Any]] = {
59
78
  "id": "{{ uuid4() }}",
60
79
  "createdAt": "{{ utcnow().isoformat() }}",
61
80
  "updatedAt": "{{ utcnow().isoformat() }}",
@@ -65,7 +84,7 @@ class Settings(BaseSettings):
65
84
 
66
85
  # fields to inject into missing resources response json.
67
86
  # some services may require such additional fields to be present in the response.
68
- missing_resource_fields: dict[str, Any] = dict(
87
+ missing_resource_fields: CliSuppress[dict[str, Any]] = dict(
69
88
  code=404,
70
89
  message="mockstack: resource not found",
71
90
  retryable=False,
@@ -73,7 +92,7 @@ class Settings(BaseSettings):
73
92
 
74
93
  # logging configuration. schema is based on the logging configuration schema:
75
94
  # https://docs.python.org/3/library/logging.config.html#logging-config-dictschema
76
- logging: dict[str, Any] = {
95
+ logging: CliSuppress[dict[str, Any]] = {
77
96
  "version": 1,
78
97
  "disable_existing_loggers": False,
79
98
  "formatters": {
@@ -84,32 +103,30 @@ class Settings(BaseSettings):
84
103
  },
85
104
  "handlers": {
86
105
  "console": {
87
- "class": "logging.StreamHandler",
88
- "level": "DEBUG",
89
- "formatter": "standard",
90
- "stream": "ext://sys.stdout",
106
+ "class": "rich.logging.RichHandler",
107
+ "level": "INFO",
91
108
  },
92
109
  },
93
110
  "loggers": {
94
111
  "uvicorn": {
95
112
  "handlers": ["console"],
96
- "level": "INFO",
113
+ "level": "DEBUG",
97
114
  "propagate": False,
98
115
  },
99
116
  "FileFixturesStrategy": {
100
117
  "handlers": ["console"],
101
- "level": "INFO",
118
+ "level": "DEBUG",
102
119
  "propagate": False,
103
120
  },
104
121
  "ProxyRulesStrategy": {
105
122
  "handlers": ["console"],
106
- "level": "INFO",
123
+ "level": "DEBUG",
107
124
  "propagate": False,
108
125
  },
109
126
  },
110
127
  "root": {
111
128
  "handlers": ["console"],
112
- "level": "INFO",
129
+ "level": "NOTSET",
113
130
  "propagate": False,
114
131
  },
115
132
  }
@@ -135,6 +152,24 @@ class Settings(BaseSettings):
135
152
  return self
136
153
 
137
154
 
155
+ # Nb. We separate the Cli-specific parameters since currently breaks pytest
156
+ # when running via pre-commit hooks. Can remove once fixed by pytest / pre-commit.
157
+
158
+
159
+ class CliSettings(Settings):
160
+ """Settings for mockstack CLI."""
161
+
162
+ model_config = SettingsConfigDict(
163
+ env_prefix=ENV_PREFIX,
164
+ env_file=ENV_FILE,
165
+ env_nested_delimiter=ENV_NESTED_DELIMITER,
166
+ cli_parse_args=True,
167
+ cli_kebab_case=True,
168
+ cli_hide_none_type=True,
169
+ cli_avoid_json=True,
170
+ )
171
+
172
+
138
173
  @lru_cache
139
174
  def settings_provider() -> Settings:
140
175
  """Provide the settings for the application."""
@@ -2,7 +2,10 @@
2
2
 
3
3
  from enum import StrEnum
4
4
 
5
- # proxyrules strategy constants
5
+
6
+ ENV_PREFIX = "mockstack__"
7
+ ENV_FILE = ".env"
8
+ ENV_NESTED_DELIMITER = "__"
6
9
 
7
10
 
8
11
  class ProxyRulesRedirectVia(StrEnum):
@@ -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
+ )
@@ -1,7 +1,7 @@
1
1
  """FastAPI application lifecycle management."""
2
2
 
3
3
  from contextlib import asynccontextmanager
4
- from logging import config
4
+ from logging import DEBUG, config
5
5
  from typing import Callable
6
6
 
7
7
  from fastapi import FastAPI
@@ -21,7 +21,12 @@ def lifespan_provider(
21
21
 
22
22
  This is the context manager that FastAPI will use to manage the lifecycle of the application.
23
23
  """
24
+ if settings.debug:
25
+ # Enable verbose debug logging if debug mode is set.
26
+ settings.logging["handlers"]["console"]["level"] = DEBUG
27
+
24
28
  config.dictConfig(settings.logging)
29
+
25
30
  announce(app, settings)
26
31
 
27
32
  yield
@@ -1,9 +1,9 @@
1
1
  """Application entrypoints."""
2
2
 
3
3
  from fastapi import FastAPI
4
- from typer import Typer
4
+ from pydantic_settings import CliApp, CliSettingsSource
5
5
 
6
- from mockstack.config import settings_provider
6
+ from mockstack.config import CliSettings, Settings, settings_provider
7
7
  from mockstack.lifespan import lifespan_provider
8
8
  from mockstack.middleware import middleware_provider
9
9
  from mockstack.routers.catchall import catchall_router_provider
@@ -11,12 +11,10 @@ from mockstack.routers.homepage import homepage_router_provider
11
11
  from mockstack.strategies.factory import strategy_provider
12
12
  from mockstack.telemetry import opentelemetry_provider
13
13
 
14
- cli = Typer()
15
14
 
16
-
17
- def create_app() -> FastAPI:
18
- """Create the FastAPI app."""
19
- settings = settings_provider()
15
+ def create_app(settings: Settings | None = None) -> FastAPI:
16
+ """Create the fastapi app and bootstrap all dependencies."""
17
+ settings = settings or settings_provider()
20
18
 
21
19
  app = FastAPI(lifespan=lifespan_provider(settings))
22
20
 
@@ -30,23 +28,23 @@ def create_app() -> FastAPI:
30
28
  return app
31
29
 
32
30
 
33
- @cli.command()
34
- def run(host: str = "0.0.0.0", port: int = 8000):
35
- """mockstack run CLI entrypoint."""
31
+ def run():
32
+ """run the mockstack server."""
33
+ import argparse
36
34
  import uvicorn
37
35
 
38
- app = create_app()
39
- uvicorn.run(app, host=host, port=port)
36
+ parser = argparse.ArgumentParser()
37
+ cli_settings = CliSettingsSource(CliSettings, root_parser=parser)
38
+ settings = CliApp.run(CliSettings, cli_settings_source=cli_settings)
39
+
40
+ app = create_app(settings=settings)
41
+
42
+ uvicorn.run(app, host=settings.host, port=settings.port)
40
43
 
41
44
 
42
- @cli.command()
43
45
  def version():
44
- """mockstack version CLI entrypoint."""
46
+ """display mockstack version."""
45
47
  from importlib.metadata import version
46
48
 
47
49
  pkg_version = version("mockstack")
48
50
  print(f"mockstack {pkg_version}")
49
-
50
-
51
- if __name__ == "__main__":
52
- cli()
@@ -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.3.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
@@ -47,7 +48,7 @@ Highlights include:
47
48
 
48
49
  Install using [uv](https://docs.astral.sh/uv/). This package conforms the concept of a [tool](https://docs.astral.sh/uv/concepts/tools/) and hence can simply install / run with `uvx`:
49
50
 
50
- uvx mockstack
51
+ uvx mockstack --help
51
52
 
52
53
  or install into a persistent environment and add it to the PATH with:
53
54
 
@@ -56,15 +57,20 @@ or install into a persistent environment and add it to the PATH with:
56
57
 
57
58
  ## Usage
58
59
 
59
- Available configuration options are [here](https://github.com/adamhadani/mockstack/blob/main/mockstack/config.py). Setting individual options can be done with env. variables as in the following example:
60
+ Available configuration options are [here](https://github.com/adamhadani/mockstack/blob/main/mockstack/config.py).
61
+
62
+ Setting individual options can be done either through an `.env` file, individual environment variables, or command-line arguments. For example:
60
63
 
61
64
  ```shell
65
+ export MOCKSTACK__STRATEGY=filefixtures
62
66
  export MOCKSTACK__TEMPLATES_DIR=~/mockstack-templates/
63
67
  export MOCKSTACK__OPENTELEMETRY__ENABLED=true
64
68
  export MOCKSTACK__OPENTELEMETRY__CAPTURE_RESPONSE_BODY=true
65
- uv run
69
+ uvx mockstack
66
70
  ```
67
71
 
72
+ See also the included [.env.example](https://github.com/adamhadani/mockstack/blob/main/.env.example) for more examples. You can copy that file to `.env` and fill in configuration as needed based on the given examples.
73
+
68
74
  Out of the box, you get the following behavior when using the default `filefixtures` strategy:
69
75
 
70
76
  - The HTTP request `GET /someservice/api/v1/user/c27f5b2b-6e81-420d-a4e4-6426e1c32db8` will try to find `<templates_dir>/someservice-api-v1-user.c27f5b2b-6e81-420d-a4e4-6426e1c32db8.j2`,
@@ -97,8 +103,8 @@ If you are contributing to development, you will want to clone this project, and
97
103
  uv sync
98
104
  uv pip install -e .
99
105
 
100
- Copy the included [.env.example](https://github.com/adamhadani/mockstack/blob/main/.env.example) file to `.env` and fill in configuration as needed based on the given examples.
101
-
102
106
  Run in development mode (for live-reload of changes when developing):
103
107
 
104
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.
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ mockstack = mockstack.main:run
@@ -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
  ]
@@ -30,7 +31,7 @@ Homepage = "https://github.com/adamhadani/mockstack"
30
31
  Issues = "https://github.com/adamhadani/mockstack/issues"
31
32
 
32
33
  [project.scripts]
33
- mockstack = "mockstack.main:cli"
34
+ mockstack = "mockstack.main:run"
34
35
 
35
36
  [dependency-groups]
36
37
  dev = [