mockstack 0.5.1__tar.gz → 0.6.0__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 (76) hide show
  1. {mockstack-0.5.1 → mockstack-0.6.0}/.env.example +4 -0
  2. {mockstack-0.5.1 → mockstack-0.6.0}/.pre-commit-config.yaml +1 -1
  3. {mockstack-0.5.1 → mockstack-0.6.0}/PKG-INFO +1 -1
  4. mockstack-0.6.0/docs/configuration.md +103 -0
  5. mockstack-0.6.0/docs/strategies/base.md +55 -0
  6. mockstack-0.6.0/docs/strategies/filefixtures.md +109 -0
  7. mockstack-0.6.0/docs/strategies/proxyrules.md +123 -0
  8. {mockstack-0.5.1 → mockstack-0.6.0}/examples/filefixtures-with-templates/.env.example +4 -0
  9. mockstack-0.6.0/examples/filefixtures-with-templates/README.md +17 -0
  10. mockstack-0.6.0/examples/llm/README.md +11 -0
  11. mockstack-0.6.0/examples/llm/mockstack-langchain-example.ipynb +128 -0
  12. mockstack-0.6.0/examples/llm/templates/openai-v1-chat-completions.j2 +35 -0
  13. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/config.py +7 -0
  14. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/display.py +2 -2
  15. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/strategies/create_mixin.py +14 -0
  16. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/strategies/filefixtures.py +15 -2
  17. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/strategies/proxyrules.py +5 -4
  18. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/tests/conftest.py +15 -0
  19. mockstack-0.6.0/mockstack/tests/strategies/test_create_mixin.py +200 -0
  20. mockstack-0.6.0/mockstack/tests/strategies/test_filefixtures.py +294 -0
  21. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/tests/strategies/test_proxyrules.py +175 -1
  22. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/tests/test_display.py +1 -1
  23. mockstack-0.6.0/mockstack/tests/test_telemetry.py +218 -0
  24. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack.egg-info/PKG-INFO +1 -1
  25. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack.egg-info/SOURCES.txt +9 -0
  26. mockstack-0.5.1/examples/filefixtures-with-templates/README.md +0 -10
  27. mockstack-0.5.1/mockstack/tests/strategies/test_filefixtures.py +0 -100
  28. {mockstack-0.5.1 → mockstack-0.6.0}/.github/workflows/ci.yml +0 -0
  29. {mockstack-0.5.1 → mockstack-0.6.0}/.github/workflows/publish-docs.yml +0 -0
  30. {mockstack-0.5.1 → mockstack-0.6.0}/.github/workflows/publish-to-pypi.yml +0 -0
  31. {mockstack-0.5.1 → mockstack-0.6.0}/.gitignore +0 -0
  32. {mockstack-0.5.1 → mockstack-0.6.0}/CODE_OF_CONDUCT.md +0 -0
  33. {mockstack-0.5.1 → mockstack-0.6.0}/LICENSE +0 -0
  34. {mockstack-0.5.1 → mockstack-0.6.0}/README.md +0 -0
  35. {mockstack-0.5.1 → mockstack-0.6.0}/SECURITY.md +0 -0
  36. {mockstack-0.5.1 → mockstack-0.6.0}/docs/assets/favicon.ico +0 -0
  37. {mockstack-0.5.1 → mockstack-0.6.0}/docs/assets/logo.png +0 -0
  38. {mockstack-0.5.1 → mockstack-0.6.0}/docs/assets/mockstack.png +0 -0
  39. {mockstack-0.5.1 → mockstack-0.6.0}/examples/filefixtures-with-templates/templates/servicename-api-v1-items.j2 +0 -0
  40. {mockstack-0.5.1 → mockstack-0.6.0}/examples/filefixtures-with-templates/templates/servicename2-api-v2-user.533ec889-7c68-45c8-b21e-4a7e455d1234.j2 +0 -0
  41. {mockstack-0.5.1 → mockstack-0.6.0}/examples/proxyrules-with-rules-file/.env.example +0 -0
  42. {mockstack-0.5.1 → mockstack-0.6.0}/examples/proxyrules-with-rules-file/README.md +0 -0
  43. {mockstack-0.5.1 → mockstack-0.6.0}/examples/proxyrules-with-rules-file/rules.yml +0 -0
  44. {mockstack-0.5.1 → mockstack-0.6.0}/mkdocs.yml +0 -0
  45. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/__init__.py +0 -0
  46. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/constants.py +0 -0
  47. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/identifiers.py +0 -0
  48. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/intent.py +0 -0
  49. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/lifespan.py +0 -0
  50. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/main.py +0 -0
  51. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/middleware.py +0 -0
  52. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/routers/__init__.py +0 -0
  53. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/routers/catchall.py +0 -0
  54. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/routers/homepage.py +0 -0
  55. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/strategies/__init__.py +0 -0
  56. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/strategies/base.py +0 -0
  57. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/strategies/factory.py +0 -0
  58. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/telemetry.py +0 -0
  59. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/templating.py +0 -0
  60. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/tests/__init__.py +0 -0
  61. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/tests/fixtures/proxyrules.yml +0 -0
  62. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/tests/fixtures/templates/__init__.py +0 -0
  63. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/tests/fixtures/templates/example-template.j2 +0 -0
  64. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/tests/routers/__init__.py +0 -0
  65. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/tests/routers/test_catchall.py +0 -0
  66. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/tests/routers/test_homepage.py +0 -0
  67. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/tests/test_identifiers.py +0 -0
  68. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/tests/test_middleware.py +0 -0
  69. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack/tests/test_templating.py +0 -0
  70. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack.egg-info/dependency_links.txt +0 -0
  71. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack.egg-info/entry_points.txt +0 -0
  72. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack.egg-info/requires.txt +0 -0
  73. {mockstack-0.5.1 → mockstack-0.6.0}/mockstack.egg-info/top_level.txt +0 -0
  74. {mockstack-0.5.1 → mockstack-0.6.0}/pyproject.toml +0 -0
  75. {mockstack-0.5.1 → mockstack-0.6.0}/setup.cfg +0 -0
  76. {mockstack-0.5.1 → mockstack-0.6.0}/uv.lock +0 -0
@@ -9,6 +9,10 @@ MOCKSTACK__STRATEGY = filefixtures
9
9
  # and other hybrid strategies utilizing templates.
10
10
  MOCKSTACK__TEMPLATES_DIR = "/some/path/templates/"
11
11
 
12
+ # When using filefixtures strategy, controls whether to enable
13
+ # using templates for POST requests or go directly to simulate a create.
14
+ FILEFIXTURES_ENABLE_TEMPLATES_FOR_POST = false
15
+
12
16
  # rules table for the proxyrules strategy.
13
17
  # see .mockstack/tests/fixtures/ for examples.
14
18
  MOCKSTACK__PROXYRULES_RULES_FILENAME="/some/path/proxyrules.yml"
@@ -39,7 +39,7 @@ repos:
39
39
  hooks:
40
40
  - id: pytest
41
41
  name: pytest [with coverage, without slow]
42
- entry: bash -ec "uv run python -m pytest --cov=mockstack mockstack/tests --cov-fail-under='75' -m 'not slow'"
42
+ entry: bash -ec "uv run python -m pytest --cov=mockstack mockstack/tests --cov-fail-under='85' -m 'not slow'"
43
43
  language: system
44
44
  types: [python]
45
45
  pass_filenames: false
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mockstack
3
- Version: 0.5.1
3
+ Version: 0.6.0
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,103 @@
1
+ # Configuration
2
+
3
+ mockstack can be configured through multiple methods, in order of priority:
4
+
5
+ - Command-line arguments
6
+ - Environment variables
7
+ - `.env` file
8
+
9
+
10
+ All configuration options are prefixed with `MOCKSTACK__` when using environment variables or the `.env` file.
11
+
12
+ ## General Settings
13
+
14
+ | Option | Type | Default | Description |
15
+ |--------|------|---------|-------------|
16
+ | `debug` | boolean | `false` | Whether to run in debug mode |
17
+ | `host` | string | `0.0.0.0` | Host to run the server on |
18
+ | `port` | integer | `8000` | Port to run the server on |
19
+ | `strategy` | string | `filefixtures` | Strategy to use for handling requests. Options: `filefixtures`, `proxyrules` |
20
+
21
+ ## OpenTelemetry Settings
22
+
23
+ | Option | Type | Default | Description |
24
+ |--------|------|---------|-------------|
25
+ | `opentelemetry.enabled` | boolean | `false` | Whether to enable OpenTelemetry integration |
26
+ | `opentelemetry.endpoint` | string | `http://localhost:4317/` | OpenTelemetry endpoint |
27
+ | `opentelemetry.capture_response_body` | boolean | `false` | Whether to capture response body in traces |
28
+
29
+ ## Strategy-Specific Settings
30
+
31
+ ### FileFixtures Strategy
32
+
33
+ | Option | Type | Default | Description |
34
+ |--------|------|---------|-------------|
35
+ | `templates_dir` | string | - | Base directory for templates used by the strategy |
36
+ | `filefixtures_enable_templates_for_post` | boolean | `false` | Whether to enable template-based responses for POST requests |
37
+
38
+ ### ProxyRules Strategy
39
+
40
+ | Option | Type | Default | Description |
41
+ |--------|------|---------|-------------|
42
+ | `proxyrules_rules_filename` | string | - | Rules filename for proxyrules strategy |
43
+ | `proxyrules_redirect_via` | string | `reverse_proxy` | Controls behavior of proxying. Options: `reverse_proxy`, `http_307_temporary`, `http_301_permanent` |
44
+ | `proxyrules_reverse_proxy_timeout` | float | `10.0` | Default timeout for reverse proxy requests in seconds |
45
+ | `proxyrules_simulate_create_on_missing` | boolean | `false` | Whether to simulate creation of resources when a POST request is made to a resource that doesn't match any rules |
46
+
47
+ ## Resource Creation Settings
48
+
49
+ | Option | Type | Default | Description |
50
+ |--------|------|---------|-------------|
51
+ | `created_resource_metadata` | object | See below | Metadata fields to inject into created resources |
52
+ | `missing_resource_fields` | object | See below | Fields to inject into missing resources response JSON |
53
+
54
+ ### Default created_resource_metadata
55
+ ```json
56
+ {
57
+ "id": "{{ uuid4() }}",
58
+ "createdAt": "{{ utcnow().isoformat() }}",
59
+ "updatedAt": "{{ utcnow().isoformat() }}",
60
+ "createdBy": "{{ request.headers.get('X-User-Id', uuid4()) }}",
61
+ "status": {
62
+ "code": "OK",
63
+ "error_code": null
64
+ }
65
+ }
66
+ ```
67
+
68
+ ### Default missing_resource_fields
69
+ ```json
70
+ {
71
+ "code": 404,
72
+ "message": "mockstack: resource not found",
73
+ "retryable": false
74
+ }
75
+ ```
76
+
77
+ ## Logging Configuration
78
+
79
+ The logging configuration follows the Python logging configuration schema. By default, it includes:
80
+
81
+ - Rich console handler
82
+ - Uvicorn formatter
83
+ - Separate loggers for different components
84
+ - Debug level logging for strategy-specific loggers
85
+
86
+ ## Example Configuration
87
+
88
+ Here's an example `.env` file:
89
+
90
+ ```env
91
+ MOCKSTACK__STRATEGY=filefixtures
92
+ MOCKSTACK__TEMPLATES_DIR=~/mockstack-templates/
93
+ MOCKSTACK__OPENTELEMETRY__ENABLED=true
94
+ MOCKSTACK__OPENTELEMETRY__CAPTURE_RESPONSE_BODY=true
95
+ ```
96
+
97
+ ## Command Line Usage
98
+
99
+ You can also set configuration options via command line arguments:
100
+
101
+ ```bash
102
+ uvx mockstack --strategy filefixtures --templates-dir ~/mockstack-templates/
103
+ ```
@@ -0,0 +1,55 @@
1
+ # Base Strategy
2
+
3
+ The `BaseStrategy` class serves as the foundation for all mockstack strategies. It defines the core interface that all strategies must implement.
4
+
5
+ ## Interface
6
+
7
+ ### Constructor
8
+
9
+ ```python
10
+ def __init__(self, settings: Settings, *args, **kwargs)
11
+ ```
12
+
13
+ Initializes the strategy with the provided settings.
14
+
15
+ ### Methods
16
+
17
+ #### `apply`
18
+
19
+ ```python
20
+ async def apply(self, request: Request) -> Response
21
+ ```
22
+
23
+ This is the main method that all strategies must implement. It takes a FastAPI `Request` object and returns a FastAPI `Response` object. This is where the strategy's core logic for handling requests is implemented.
24
+
25
+ #### `update_opentelemetry`
26
+
27
+ ```python
28
+ def update_opentelemetry(self, request: Request, *args, **kwargs) -> None
29
+ ```
30
+
31
+ This method allows strategies to add strategy-specific attributes to the OpenTelemetry span. The span is available on `request.state.span`. When OpenTelemetry is not enabled, this span will exist but will not be reported.
32
+
33
+ ## Creating Custom Strategies
34
+
35
+ To create a custom strategy, you should:
36
+
37
+ 1. Inherit from `BaseStrategy`
38
+ 2. Implement the `apply` method
39
+ 3. Optionally override `update_opentelemetry` to add strategy-specific telemetry
40
+
41
+ Example:
42
+
43
+ ```python
44
+ from mockstack.strategies.base import BaseStrategy
45
+ from fastapi import Request, Response
46
+
47
+ class CustomStrategy(BaseStrategy):
48
+ async def apply(self, request: Request) -> Response:
49
+ # Implement your custom logic here
50
+ pass
51
+
52
+ def update_opentelemetry(self, request: Request, *args, **kwargs) -> None:
53
+ # Add custom telemetry attributes
54
+ request.state.span.set_attribute("custom.attribute", "value")
55
+ ```
@@ -0,0 +1,109 @@
1
+ # FileFixtures Strategy
2
+
3
+ The `FileFixturesStrategy` is a template-based strategy that uses Jinja2 templates to generate responses. It's particularly useful for creating consistent mock responses for your API endpoints.
4
+
5
+ ## Overview
6
+
7
+ This strategy:
8
+
9
+ - Uses Jinja2 templates stored in a specified directory
10
+ - Intelligently matches requests to templates based on the request path
11
+ - Supports all HTTP methods (GET, POST, PUT, PATCH, DELETE)
12
+ - Simulates resource creation for POST requests which are not handled by proxying rules (behavior controlled via configuration)
13
+ - Provides OpenTelemetry integration for observability
14
+
15
+ ## Template Resolution
16
+
17
+ For a given request path like `/api/v1/projects/1234`, the strategy will look for templates in the following order:
18
+
19
+ 1. `api-v1-projects.1234.j2` (specific to the resource ID)
20
+ 2. `api-v1-projects.j2` (generic for the resource type)
21
+ 3. `index.j2` (fallback template)
22
+
23
+ ## HTTP Method Handling
24
+
25
+ ### GET Requests
26
+ - Attempts to find and render a matching template
27
+ - Returns 404 if no matching template is found
28
+
29
+ ### POST Requests
30
+ The strategy intelligently handles POST requests based on the request context:
31
+
32
+ 1. **Search Requests**: If the request looks like a search (based on URL and body), returns a template response
33
+ 2. **Command Requests**: If the request looks like a command, returns a 201 CREATED status with template response
34
+ 3. **Resource Creation**: Otherwise, simulates resource creation with injected metadata
35
+
36
+ ### DELETE/PUT/PATCH Requests
37
+ - Returns 204 NO CONTENT by default
38
+ - These are no-op operations in the default implementation
39
+
40
+ ## Template Context
41
+
42
+ Templates have access to the following context variables:
43
+
44
+ - `request`: The FastAPI Request object
45
+ - `request.body`: The parsed request body
46
+ - `request.headers`: The request headers
47
+ - `request.query_params`: The query parameters
48
+ - `request.path_params`: The path parameters
49
+
50
+ ## Resource Creation
51
+
52
+ When simulating resource creation (POST requests), the strategy injects the following metadata fields by default:
53
+
54
+ ```json
55
+ {
56
+ "id": "{{ uuid4() }}",
57
+ "createdAt": "{{ utcnow().isoformat() }}",
58
+ "updatedAt": "{{ utcnow().isoformat() }}",
59
+ "createdBy": "{{ request.headers.get('X-User-Id', uuid4()) }}",
60
+ "status": {
61
+ "code": "OK",
62
+ "error_code": null
63
+ }
64
+ }
65
+ ```
66
+
67
+ ## Configuration
68
+
69
+ The strategy requires the following configuration:
70
+
71
+ ```python
72
+ settings = Settings(
73
+ strategy="filefixtures",
74
+ templates_dir="/path/to/templates",
75
+ filefixtures_enable_templates_for_post=True # Optional: Enables template-based responses for POST requests
76
+ )
77
+ ```
78
+
79
+ ## Example Template
80
+
81
+ Here's an example template for a user resource:
82
+
83
+ ```jinja2
84
+ {
85
+ "id": "{{ uuid4() }}",
86
+ "name": "{{ request.body.name }}",
87
+ "email": "{{ request.body.email }}",
88
+ "createdAt": "{{ utcnow().isoformat() }}",
89
+ "updatedAt": "{{ utcnow().isoformat() }}"
90
+ }
91
+ ```
92
+
93
+ ## OpenTelemetry Integration
94
+
95
+ The strategy automatically adds the following OpenTelemetry attributes:
96
+
97
+ - `mockstack.filefixtures.template_name`: The name of the template being rendered
98
+
99
+ ## Error Handling
100
+
101
+ When no matching template is found, the strategy returns a 404 response with the following structure:
102
+
103
+ ```json
104
+ {
105
+ "code": 404,
106
+ "message": "mockstack: resource not found",
107
+ "retryable": false
108
+ }
109
+ ```
@@ -0,0 +1,123 @@
1
+ # ProxyRules Strategy
2
+
3
+ The `ProxyRulesStrategy` is a powerful strategy that allows you to define rules for redirecting or proxying requests to other services. It's particularly useful when you need to mix mock responses with real service calls.
4
+
5
+ ## Overview
6
+
7
+ This strategy:
8
+
9
+ - Uses a YAML configuration file to define routing rules
10
+ - Supports multiple redirection methods (HTTP redirects or reverse proxy)
11
+ - Can simulate resource creation for unmatched requests
12
+ - Provides OpenTelemetry integration for observability
13
+
14
+ ## Configuration
15
+
16
+ The strategy requires the following configuration:
17
+
18
+ ```python
19
+ settings = Settings(
20
+ strategy="proxyrules",
21
+ proxyrules_rules_filename="/path/to/rules.yaml",
22
+ proxyrules_redirect_via="REVERSE_PROXY", # or "HTTP_TEMPORARY_REDIRECT" or "HTTP_PERMANENT_REDIRECT"
23
+ proxyrules_reverse_proxy_timeout=10.0,
24
+ proxyrules_simulate_create_on_missing=False
25
+ )
26
+ ```
27
+
28
+ ## Rules Configuration
29
+
30
+ Rules are defined in a YAML file with the following structure:
31
+
32
+ ```yaml
33
+ rules:
34
+ - name: "user-service"
35
+ pattern: "^/api/v1/users/(.*)"
36
+ replacement: "http://user-service/api/v1/users/\1"
37
+ method: "GET" # optional, if not specified matches all methods
38
+ ```
39
+
40
+ ### Rule Properties
41
+
42
+ - `name`: Optional identifier for the rule (used in telemetry)
43
+ - `pattern`: Regular expression pattern to match against the request path
44
+ - `replacement`: URL template to redirect to (can use capture groups from pattern)
45
+ - `method`: Optional HTTP method to match (if not specified, matches all methods)
46
+
47
+ ## Redirection Methods
48
+
49
+ The strategy supports three redirection methods:
50
+
51
+ 1. **HTTP Temporary Redirect (307)**
52
+ - Client makes a new request to the target URL
53
+ - Preserves the original HTTP method
54
+
55
+ 2. **HTTP Permanent Redirect (301)**
56
+ - Client makes a new request to the target URL
57
+ - Browsers may cache the redirect
58
+
59
+ 3. **Reverse Proxy**
60
+ - Server forwards the request to the target service
61
+ - Client is unaware of the redirection
62
+ - Useful when you need to work with clients that do not handle HTTP redirects gracefully.
63
+
64
+ ## Resource Creation Simulation
65
+
66
+ When `proxyrules_simulate_create_on_missing` is enabled and a POST request doesn't match any rules, the strategy will simulate resource creation by:
67
+
68
+ 1. Injecting metadata fields into the response
69
+ 2. Returning a 201 CREATED status code
70
+ 3. Echoing back the request body with added metadata
71
+
72
+ The default metadata fields are controlled via the configuration file and at the time of writing are as follows:
73
+
74
+ ```json
75
+ {
76
+ "id": "{{ uuid4() }}",
77
+ "createdAt": "{{ utcnow().isoformat() }}",
78
+ "updatedAt": "{{ utcnow().isoformat() }}",
79
+ "createdBy": "{{ request.headers.get('X-User-Id', uuid4()) }}",
80
+ "status": {
81
+ "code": "OK",
82
+ "error_code": null
83
+ }
84
+ }
85
+ ```
86
+
87
+ ## OpenTelemetry Integration
88
+
89
+ The strategy automatically adds the following OpenTelemetry attributes:
90
+
91
+ - `mockstack.proxyrules.rule_name`: The name of the matched rule (if specified)
92
+ - `mockstack.proxyrules.rule_method`: The HTTP method the rule matches (if specified)
93
+ - `mockstack.proxyrules.rule_pattern`: The pattern used to match the request
94
+ - `mockstack.proxyrules.rule_replacement`: The replacement URL template
95
+ - `mockstack.proxyrules.rewritten_url`: The final URL after applying the rule
96
+
97
+ ## Example Rules
98
+
99
+ Here are some example rules:
100
+
101
+ ```yaml
102
+ rules:
103
+ # Redirect all GET requests to /api/v1/users/* to the user service
104
+ - name: "user-service-get"
105
+ pattern: "^/api/v1/users/(.*)"
106
+ replacement: "http://user-service/api/v1/users/\1"
107
+ method: "GET"
108
+
109
+ # Proxy all POST requests to /api/v1/orders to the order service
110
+ - name: "order-service-post"
111
+ pattern: "^/api/v1/orders"
112
+ replacement: "http://order-service/api/v1/orders"
113
+ method: "POST"
114
+
115
+ # Redirect all requests to /api/v1/products to the product service
116
+ - name: "product-service"
117
+ pattern: "^/api/v1/products/(.*)"
118
+ replacement: "http://product-service/api/v1/products/\1"
119
+ ```
120
+
121
+ ## Error Handling
122
+
123
+ When no matching rule is found and resource creation simulation is disabled, the strategy returns a 404 NOT FOUND response.
@@ -9,5 +9,9 @@ MOCKSTACK__STRATEGY = filefixtures
9
9
  # and other hybrid strategies utilizing templates.
10
10
  MOCKSTACK__TEMPLATES_DIR = "./templates/"
11
11
 
12
+ # When using filefixtures strategy, controls whether to enable
13
+ # using templates for POST requests or go directly to simulate a create.
14
+ FILEFIXTURES_ENABLE_TEMPLATES_FOR_POST = false
15
+
12
16
  # OpenTelemetry settings
13
17
  MOCKSTACK__OPENTELEMETRY__ENABLED = false
@@ -0,0 +1,17 @@
1
+ # filefixtures example
2
+
3
+ This folder shows an example setup for using mockstack with the `filefixtures` strategy which lets you route
4
+ requests to template files.
5
+
6
+ The folder is comprised of the following:
7
+
8
+ An `.env.example` file which you would rename `.env` and contains the relevant configuration options needed to run. When you have that file in the working directory when invoking mockstack it will automatically pick up the settings from this file.
9
+
10
+ A `templates/` dir, pointed to by a configurationkey in the env file, which contains our templates.
11
+ We have a few example templates showcasing some of the capabilities of template-based mocking:
12
+
13
+ * One template file shows how you can have Jinja2 templating conditionals in the template e.g. to handle different responses based on request query parameters.
14
+ * Another template shows the naming convention for templates that depend on an identifier embedded in the request URL, e.g. `GET
15
+ /someservice/api/v1/item/ae420979-33f3-4c99-bc42-9d7cdee5259e` would get routed to the template file `someservice-api-v1-item.ae420979-33f3-4c99-bc42-9d7cdee5259e.j2`.
16
+ * Multiple identifiers in the path are also supported and would appear in filenames separated by dots, according to the order in which they appear in the URL.
17
+ * A configuration value in controllable via `.env` / environment variables lets you also decide whether to allow using templates for POST requests or try to simulate a createion. When templates are allowed, mockstaack will first try to find a suitable template for the request based on the URL, and if it fails will fallback to the create simulation behavior.
@@ -0,0 +1,11 @@
1
+ # LLMs
2
+
3
+ **mockstack** can be a great tool for the development of LLM-based flows.
4
+
5
+ A lot of current applications involve a sort of DAG of API calls to LLM APIs and various "tool" APIs, sometimes serially, sometimes in parallel, and sometimes in a "iterative" fashion (e.g. see [Agents](https://langchain-ai.github.io/langgraph/agents/overview/) documentation on LangGraph website).
6
+
7
+ you can mock any of these parts out to accelerate development, debugging and integration testing of such workflows.
8
+
9
+ Mocking out the LLMs themselves can be a particularly effective method to make sure no costs are incurred in early stages of development and in debugging scenarios that don't critically rely on the semantic content of the responses.
10
+
11
+ This example shows a few possible scenarios involving mockstack and (mostly LangChain-based) LLM workflows.
@@ -0,0 +1,128 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "# LLMs + mockstack\n",
8
+ "\n",
9
+ "A few simple examples for using `mockstack` to mock various components in typical LLM-driven use cases."
10
+ ]
11
+ },
12
+ {
13
+ "cell_type": "code",
14
+ "execution_count": 26,
15
+ "metadata": {},
16
+ "outputs": [],
17
+ "source": [
18
+ "# Install langchain dependencies with the following or using `uv` depending on your venv setup:\n",
19
+ "\n",
20
+ "#!pip install -q langchain langchain-openai\n",
21
+ "# or:\n",
22
+ "#!uv pip install langchain langchain-openai"
23
+ ]
24
+ },
25
+ {
26
+ "cell_type": "markdown",
27
+ "metadata": {},
28
+ "source": [
29
+ "## Example #1: Template-based mocking (`filefixtures` strategy)\n",
30
+ "\n",
31
+ "Here we simply use the **filefixtures** strategy to route requesets coming in for a certain URL to a template file with the appropriate name.\n",
32
+ "\n",
33
+ "For the below example you'll want to make sure:\n",
34
+ "\n",
35
+ "- mockstack is running at http://localhost:8000 which are the default settings\n",
36
+ "- `MOCKSTACK__TEMPLATES_DIR` is pointing to a valid directory with a template file called `openai-v1-chat-completions.j2`. See the [included file](./templates/openai-v1-chat-completions.j2) with same name in the ./templates sub-directory of this example for a a template with a valid response based on [OpenAI API reference](https://platform.openai.com/docs/api-reference/chat/create).\n",
37
+ "- the `MOCKSTACK__FILEFIXTURES_ENABLE_TEMPLATES_FOR_POST` flag is set to true (which it should be by default)\n",
38
+ "\n",
39
+ "\n",
40
+ "If everything is setup correctly, you should get back the mocked response in the correct format from the template and the below assert should pass."
41
+ ]
42
+ },
43
+ {
44
+ "cell_type": "code",
45
+ "execution_count": 31,
46
+ "metadata": {},
47
+ "outputs": [],
48
+ "source": [
49
+ "from langchain_openai import ChatOpenAI\n",
50
+ "\n",
51
+ "\n",
52
+ "llm = ChatOpenAI(\n",
53
+ " model=\"gpt-4o\",\n",
54
+ " temperature=0,\n",
55
+ " max_tokens=None,\n",
56
+ " timeout=None,\n",
57
+ " max_retries=2,\n",
58
+ " base_url=\"http://localhost:8000/openai/v1\",\n",
59
+ " api_key=\"SOME_STRING_THAT_DOES_NOT_MATTER\",\n",
60
+ ")\n",
61
+ "\n",
62
+ "messages = [\n",
63
+ " (\n",
64
+ " \"system\",\n",
65
+ " \"You are a helpful assistant that translates English to French. Translate the user sentence.\",\n",
66
+ " ),\n",
67
+ " (\"human\", \"I love programming.\"),\n",
68
+ "]\n",
69
+ "ai_msg = llm.invoke(messages)\n",
70
+ "\n",
71
+ "assert ai_msg.content == \"Hello! How can I assist you today?\""
72
+ ]
73
+ },
74
+ {
75
+ "cell_type": "markdown",
76
+ "metadata": {},
77
+ "source": [
78
+ "## Example #2: `llm` strategy\n",
79
+ "\n",
80
+ "** COMING SOON **"
81
+ ]
82
+ },
83
+ {
84
+ "cell_type": "code",
85
+ "execution_count": null,
86
+ "metadata": {},
87
+ "outputs": [],
88
+ "source": []
89
+ },
90
+ {
91
+ "cell_type": "markdown",
92
+ "metadata": {},
93
+ "source": [
94
+ "## Example #3: mocking out a tool call\n",
95
+ "\n",
96
+ "** COMING SOON **"
97
+ ]
98
+ },
99
+ {
100
+ "cell_type": "code",
101
+ "execution_count": null,
102
+ "metadata": {},
103
+ "outputs": [],
104
+ "source": []
105
+ }
106
+ ],
107
+ "metadata": {
108
+ "kernelspec": {
109
+ "display_name": ".venv",
110
+ "language": "python",
111
+ "name": "python3"
112
+ },
113
+ "language_info": {
114
+ "codemirror_mode": {
115
+ "name": "ipython",
116
+ "version": 3
117
+ },
118
+ "file_extension": ".py",
119
+ "mimetype": "text/x-python",
120
+ "name": "python",
121
+ "nbconvert_exporter": "python",
122
+ "pygments_lexer": "ipython3",
123
+ "version": "3.13.3"
124
+ }
125
+ },
126
+ "nbformat": 4,
127
+ "nbformat_minor": 2
128
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "id": "chatcmpl-B9MBs8CjcvOU2jLn4n570S5qMJKcT",
3
+ "object": "chat.completion",
4
+ "created": 1741569952,
5
+ "model": "gpt-4.1-2025-04-14",
6
+ "choices": [
7
+ {
8
+ "index": 0,
9
+ "message": {
10
+ "role": "assistant",
11
+ "content": "Hello! How can I assist you today?",
12
+ "refusal": null,
13
+ "annotations": []
14
+ },
15
+ "logprobs": null,
16
+ "finish_reason": "stop"
17
+ }
18
+ ],
19
+ "usage": {
20
+ "prompt_tokens": 19,
21
+ "completion_tokens": 10,
22
+ "total_tokens": 29,
23
+ "prompt_tokens_details": {
24
+ "cached_tokens": 0,
25
+ "audio_tokens": 0
26
+ },
27
+ "completion_tokens_details": {
28
+ "reasoning_tokens": 0,
29
+ "audio_tokens": 0,
30
+ "accepted_prediction_tokens": 0,
31
+ "rejected_prediction_tokens": 0
32
+ }
33
+ },
34
+ "service_tier": "default"
35
+ }
@@ -61,6 +61,13 @@ class Settings(BaseSettings):
61
61
  # base directory for templates used by strategies
62
62
  templates_dir: DirectoryPath | None = None # type: ignore[assignment]
63
63
 
64
+ # whether to enable templates for POST requests.
65
+ # By default, templates are not used for POSTs, and instead we try to
66
+ # simulate a create (or search) operation. If turned on, we will first
67
+ # try to materialize a template for the response, and if that fails
68
+ # with a 404, we will then try to simulate creation of the resource.
69
+ filefixtures_enable_templates_for_post: CliImplicitFlag[bool] = True
70
+
64
71
  # rules filename for proxyrules strategy
65
72
  proxyrules_rules_filename: FilePath | None = None # type: ignore[assignment]
66
73
 
@@ -20,8 +20,8 @@ def announce(app: FastAPI, settings: Settings):
20
20
  )
21
21
  logger.info(str(app.state.strategy), extra=extra)
22
22
  logger.info(
23
- f"OpenTelemetry enabled: [medium_purple]{settings.opentelemetry.enabled}[/medium_purple], "
24
- f"endpoint: [medium_purple]{settings.opentelemetry.endpoint}[/medium_purple], "
23
+ f"[medium_purple]OpenTelemetry[/medium_purple] enabled: [medium_purple]{settings.opentelemetry.enabled}[/medium_purple],\n "
24
+ f"endpoint: [medium_purple]{settings.opentelemetry.endpoint}[/medium_purple],\n "
25
25
  f"capture_response_body: [medium_purple]{settings.opentelemetry.capture_response_body}[/medium_purple]",
26
26
  extra=extra,
27
27
  )