mockstack 0.7.3__tar.gz → 0.9.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 (86) hide show
  1. {mockstack-0.7.3 → mockstack-0.9.0}/Dockerfile +1 -1
  2. {mockstack-0.7.3 → mockstack-0.9.0}/PKG-INFO +24 -15
  3. {mockstack-0.7.3 → mockstack-0.9.0}/README.md +19 -12
  4. {mockstack-0.7.3 → mockstack-0.9.0}/SECURITY.md +1 -1
  5. mockstack-0.9.0/docs/ollama.md +87 -0
  6. {mockstack-0.7.3 → mockstack-0.9.0}/examples/llm/mockstack-langchain-example.ipynb +73 -8
  7. mockstack-0.9.0/examples/llm/templates/ollama-openai-v1-chat-completions.j2 +35 -0
  8. {mockstack-0.7.3 → mockstack-0.9.0}/mkdocs.yml +12 -3
  9. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/config.py +2 -2
  10. mockstack-0.9.0/mockstack/exceptions.py +6 -0
  11. mockstack-0.9.0/mockstack/llm/ollama.py +50 -0
  12. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/strategies/filefixtures.py +22 -16
  13. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/strategies/proxyrules.py +12 -5
  14. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/templating.py +44 -1
  15. mockstack-0.9.0/mockstack/tests/llm/__init__.py +0 -0
  16. mockstack-0.9.0/mockstack/tests/llm/test_ollama.py +95 -0
  17. mockstack-0.9.0/mockstack/tests/routers/__init__.py +0 -0
  18. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/strategies/test_filefixtures.py +7 -26
  19. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/test_templating.py +29 -6
  20. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack.egg-info/PKG-INFO +24 -15
  21. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack.egg-info/SOURCES.txt +7 -0
  22. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack.egg-info/requires.txt +3 -0
  23. {mockstack-0.7.3 → mockstack-0.9.0}/pyproject.toml +7 -2
  24. {mockstack-0.7.3 → mockstack-0.9.0}/uv.lock +20 -0
  25. {mockstack-0.7.3 → mockstack-0.9.0}/.dockerignore +0 -0
  26. {mockstack-0.7.3 → mockstack-0.9.0}/.env.example +0 -0
  27. {mockstack-0.7.3 → mockstack-0.9.0}/.github/workflows/ci.yml +0 -0
  28. {mockstack-0.7.3 → mockstack-0.9.0}/.github/workflows/publish-docs.yml +0 -0
  29. {mockstack-0.7.3 → mockstack-0.9.0}/.github/workflows/publish-to-dockerhub.yml +0 -0
  30. {mockstack-0.7.3 → mockstack-0.9.0}/.github/workflows/publish-to-pypi.yml +0 -0
  31. {mockstack-0.7.3 → mockstack-0.9.0}/.gitignore +0 -0
  32. {mockstack-0.7.3 → mockstack-0.9.0}/.pre-commit-config.yaml +0 -0
  33. {mockstack-0.7.3 → mockstack-0.9.0}/CODE_OF_CONDUCT.md +0 -0
  34. {mockstack-0.7.3 → mockstack-0.9.0}/LICENSE +0 -0
  35. {mockstack-0.7.3 → mockstack-0.9.0}/docker-entrypoint.sh +0 -0
  36. {mockstack-0.7.3 → mockstack-0.9.0}/docs/assets/favicon.ico +0 -0
  37. {mockstack-0.7.3 → mockstack-0.9.0}/docs/assets/logo.png +0 -0
  38. {mockstack-0.7.3 → mockstack-0.9.0}/docs/assets/mockstack.png +0 -0
  39. {mockstack-0.7.3 → mockstack-0.9.0}/docs/configuration.md +0 -0
  40. {mockstack-0.7.3 → mockstack-0.9.0}/docs/strategies/base.md +0 -0
  41. {mockstack-0.7.3 → mockstack-0.9.0}/docs/strategies/filefixtures.md +0 -0
  42. {mockstack-0.7.3 → mockstack-0.9.0}/docs/strategies/proxyrules.md +0 -0
  43. {mockstack-0.7.3 → mockstack-0.9.0}/examples/filefixtures-with-templates/.env.example +0 -0
  44. {mockstack-0.7.3 → mockstack-0.9.0}/examples/filefixtures-with-templates/README.md +0 -0
  45. {mockstack-0.7.3 → mockstack-0.9.0}/examples/filefixtures-with-templates/templates/servicename-api-v1-items.j2 +0 -0
  46. {mockstack-0.7.3 → mockstack-0.9.0}/examples/filefixtures-with-templates/templates/servicename2-api-v2-user.533ec889-7c68-45c8-b21e-4a7e455d1234.j2 +0 -0
  47. {mockstack-0.7.3 → mockstack-0.9.0}/examples/llm/README.md +0 -0
  48. {mockstack-0.7.3 → mockstack-0.9.0}/examples/llm/templates/openai-v1-chat-completions.j2 +0 -0
  49. {mockstack-0.7.3 → mockstack-0.9.0}/examples/llm/templates/openai-v1-deployments-gpt-4o-chat-completions.j2 +0 -0
  50. {mockstack-0.7.3 → mockstack-0.9.0}/examples/proxyrules-with-rules-file/.env.example +0 -0
  51. {mockstack-0.7.3 → mockstack-0.9.0}/examples/proxyrules-with-rules-file/README.md +0 -0
  52. {mockstack-0.7.3 → mockstack-0.9.0}/examples/proxyrules-with-rules-file/rules.yml +0 -0
  53. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/__init__.py +0 -0
  54. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/constants.py +0 -0
  55. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/display.py +0 -0
  56. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/identifiers.py +0 -0
  57. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/intent.py +0 -0
  58. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/lifespan.py +0 -0
  59. {mockstack-0.7.3/mockstack/routers → mockstack-0.9.0/mockstack/llm}/__init__.py +0 -0
  60. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/main.py +0 -0
  61. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/middleware.py +0 -0
  62. {mockstack-0.7.3/mockstack/tests → mockstack-0.9.0/mockstack/routers}/__init__.py +0 -0
  63. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/routers/catchall.py +0 -0
  64. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/routers/homepage.py +0 -0
  65. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/strategies/__init__.py +0 -0
  66. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/strategies/base.py +0 -0
  67. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/strategies/create_mixin.py +0 -0
  68. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/strategies/factory.py +0 -0
  69. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/telemetry.py +0 -0
  70. {mockstack-0.7.3/mockstack/tests/fixtures/templates → mockstack-0.9.0/mockstack/tests}/__init__.py +0 -0
  71. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/conftest.py +0 -0
  72. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/fixtures/proxyrules.yml +0 -0
  73. {mockstack-0.7.3/mockstack/tests/routers → mockstack-0.9.0/mockstack/tests/fixtures/templates}/__init__.py +0 -0
  74. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/fixtures/templates/example-template.j2 +0 -0
  75. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/routers/test_catchall.py +0 -0
  76. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/routers/test_homepage.py +0 -0
  77. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/strategies/test_create_mixin.py +0 -0
  78. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/strategies/test_proxyrules.py +0 -0
  79. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/test_display.py +0 -0
  80. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/test_identifiers.py +0 -0
  81. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/test_middleware.py +0 -0
  82. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/test_telemetry.py +0 -0
  83. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack.egg-info/dependency_links.txt +0 -0
  84. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack.egg-info/entry_points.txt +0 -0
  85. {mockstack-0.7.3 → mockstack-0.9.0}/mockstack.egg-info/top_level.txt +0 -0
  86. {mockstack-0.7.3 → mockstack-0.9.0}/setup.cfg +0 -0
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Define build argument for version
4
4
  ARG PYTHON_IMAGE_VERSION=3.13.3-slim
5
- ARG MOCKSTACK_VERSION=0.1.0
5
+ ARG MOCKSTACK_VERSION=0.8.0
6
6
 
7
7
  # ---------------------------- Base Image --------------------------------
8
8
 
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mockstack
3
- Version: 0.7.3
3
+ Version: 0.9.0
4
4
  Summary: An API mocking workhorse
5
5
  Author-email: Adam Ever-Hadani <mockstack.contact@gmail.com>
6
6
  License-Expression: MIT
7
- Project-URL: Homepage, https://github.com/adamhadani/mockstack
8
- Project-URL: Issues, https://github.com/adamhadani/mockstack/issues
7
+ Project-URL: Homepage, https://github.com/promptromp/mockstack
8
+ Project-URL: Issues, https://github.com/promptromp/mockstack/issues
9
9
  Keywords: mocking,integration-testing,testing,microservices,api
10
10
  Classifier: Development Status :: 5 - Production/Stable
11
11
  Classifier: Programming Language :: Python
@@ -27,15 +27,17 @@ Requires-Dist: pyyaml>=6.0.2
27
27
  Requires-Dist: rich>=14.0.0
28
28
  Requires-Dist: typer>=0.15.2
29
29
  Requires-Dist: uvicorn>=0.34.2
30
+ Provides-Extra: llm
31
+ Requires-Dist: ollama>=0.4.8; extra == "llm"
30
32
  Dynamic: license-file
31
33
 
32
- ![mockstack logo](https://github.com/adamhadani/mockstack/raw/main/docs/assets/mockstack.png)
34
+ ![mockstack logo](https://github.com/promptromp/mockstack/raw/main/docs/assets/mockstack.png)
33
35
 
34
36
  --------------------------------------------------------------------------------
35
37
 
36
38
 
37
- [![CI](https://github.com/adamhadani/mockstack/actions/workflows/ci.yml/badge.svg)](https://github.com/adamhadani/mockstack/actions/workflows/ci.yml)
38
- [![GitHub License](https://img.shields.io/github/license/adamhadani/mockstack)](https://github.com/adamhadani/mockstack/blob/main/LICENSE)
39
+ [![CI](https://github.com/promptromp/mockstack/actions/workflows/ci.yml/badge.svg)](https://github.com/promptromp/mockstack/actions/workflows/ci.yml)
40
+ [![GitHub License](https://img.shields.io/github/license/promptromp/mockstack)](https://github.com/promptromp/mockstack/blob/main/LICENSE)
39
41
  [![PyPI - Version](https://img.shields.io/pypi/v/mockstack)](https://pypi.org/project/mockstack/)
40
42
  [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/mockstack)](https://pypi.org/project/mockstack/)
41
43
 
@@ -50,7 +52,7 @@ Use mockstack for:
50
52
 
51
53
  * **Integration Testing** :ok_hand:. Creating a consistent environment for running integration tests on a single component, using fixture responses.
52
54
 
53
- * **LLM-powered Workflows** :alien:. Speeding up development of LLM-based workflows and tools for use with frameworks such as [LangChain](https://python.langchain.com/docs/introduction/), [LangGraph](https://www.langchain.com/langgraph) and others. When developing LLM-driven execution graphs, you often want to have a consistent response from a tool while you're tuning prompts or debugging other aspects of a particular trace. **mockstack** can give you a solid foundation for this.
55
+ * **LLM-powered Workflows** :alien:. Speeding up development and reducing per-token costs of LLM-based workflows and tools for use with frameworks such as [LangChain](https://python.langchain.com/docs/introduction/), [LangGraph](https://www.langchain.com/langgraph) and others. When developing LLM-driven execution graphs. Optional [Ollama](https://ollama.com/) integration allows for realistic mocking of 3rd party LLMs without changing a line of code in your project. In addition, when you want to have a consistent response from a tool while you're tuning prompts or debugging other aspects of a particular trace you can create various fixture responses with varying levels of dynamic content that is template-driven. **mockstack** can give you a solid foundation for this.
54
56
 
55
57
  * **Chaos Engineering** :boom:. mockstack can simulate various real-world runtime error scenarios such as timeouts, http error codes, and invalid response payloads. This can be a great way to do some upfront [Chaos Monkey](https://github.com/Netflix/chaosmonkey) type of testing on software components.
56
58
 
@@ -75,21 +77,28 @@ or install into a persistent environment and add it to the PATH with:
75
77
 
76
78
  ## Usage
77
79
 
78
- See the [examples](https://github.com/adamhadani/mockstack/blob/main/examples/) directory for complete examples with documentation.
80
+ See the [examples](https://github.com/promptromp/mockstack/blob/main/examples/) directory for complete examples with documentation.
79
81
 
80
- Available configuration options are [here](https://github.com/adamhadani/mockstack/blob/main/mockstack/config.py).
82
+ Available configuration options are [here](https://github.com/promptromp/mockstack/blob/main/mockstack/config.py).
81
83
 
82
- Setting individual options can be done either through an `.env` file, individual environment variables, or command-line arguments. For example:
84
+ Setting individual options can be done either through an `.env` file, individual environment variables, or command-line arguments.
85
+
86
+
87
+ Minimal example to get you started:
83
88
 
84
89
  ```shell
85
- export MOCKSTACK__STRATEGY=filefixtures
90
+ mkdir -p ~/mockstack-templates
91
+ echo '{"message": "Hello from mockstack!"}' > ~/mockstack-templates/myservice-api-myresource.j2
92
+
86
93
  export MOCKSTACK__TEMPLATES_DIR=~/mockstack-templates/
87
- export MOCKSTACK__OPENTELEMETRY__ENABLED=true
88
- export MOCKSTACK__OPENTELEMETRY__CAPTURE_RESPONSE_BODY=true
89
94
  uvx mockstack
90
95
  ```
91
96
 
92
- 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.
97
+ You can then hit `http://localhost:8000/myservice/api/myresource/23faa8cf-5daa-4bcb-8c92-27018b712aa9` (or any other UUID).
98
+
99
+ This is of course just the tip of the iceberg.
100
+
101
+ See also the included [.env.example](https://github.com/promptromp/mockstack/blob/main/.env.example) for more settings you are likely to find useful. You can copy that file to `.env` and fill in configuration as needed based on the given examples.
93
102
 
94
103
  Out of the box, you get the following behavior when using the default `filefixtures` strategy:
95
104
 
@@ -118,7 +127,7 @@ Linting, formatting, static type checks etc. are all managed via [pre-commit](ht
118
127
 
119
128
  If you are contributing to development, you will want to clone this project, and can then install it locally with:
120
129
 
121
- gh repo clone adamhadani/mockstack
130
+ gh repo clone promptromp/mockstack
122
131
  cd mockstack/
123
132
  uv sync
124
133
  uv pip install -e .
@@ -1,10 +1,10 @@
1
- ![mockstack logo](https://github.com/adamhadani/mockstack/raw/main/docs/assets/mockstack.png)
1
+ ![mockstack logo](https://github.com/promptromp/mockstack/raw/main/docs/assets/mockstack.png)
2
2
 
3
3
  --------------------------------------------------------------------------------
4
4
 
5
5
 
6
- [![CI](https://github.com/adamhadani/mockstack/actions/workflows/ci.yml/badge.svg)](https://github.com/adamhadani/mockstack/actions/workflows/ci.yml)
7
- [![GitHub License](https://img.shields.io/github/license/adamhadani/mockstack)](https://github.com/adamhadani/mockstack/blob/main/LICENSE)
6
+ [![CI](https://github.com/promptromp/mockstack/actions/workflows/ci.yml/badge.svg)](https://github.com/promptromp/mockstack/actions/workflows/ci.yml)
7
+ [![GitHub License](https://img.shields.io/github/license/promptromp/mockstack)](https://github.com/promptromp/mockstack/blob/main/LICENSE)
8
8
  [![PyPI - Version](https://img.shields.io/pypi/v/mockstack)](https://pypi.org/project/mockstack/)
9
9
  [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/mockstack)](https://pypi.org/project/mockstack/)
10
10
 
@@ -19,7 +19,7 @@ Use mockstack for:
19
19
 
20
20
  * **Integration Testing** :ok_hand:. Creating a consistent environment for running integration tests on a single component, using fixture responses.
21
21
 
22
- * **LLM-powered Workflows** :alien:. Speeding up development of LLM-based workflows and tools for use with frameworks such as [LangChain](https://python.langchain.com/docs/introduction/), [LangGraph](https://www.langchain.com/langgraph) and others. When developing LLM-driven execution graphs, you often want to have a consistent response from a tool while you're tuning prompts or debugging other aspects of a particular trace. **mockstack** can give you a solid foundation for this.
22
+ * **LLM-powered Workflows** :alien:. Speeding up development and reducing per-token costs of LLM-based workflows and tools for use with frameworks such as [LangChain](https://python.langchain.com/docs/introduction/), [LangGraph](https://www.langchain.com/langgraph) and others. When developing LLM-driven execution graphs. Optional [Ollama](https://ollama.com/) integration allows for realistic mocking of 3rd party LLMs without changing a line of code in your project. In addition, when you want to have a consistent response from a tool while you're tuning prompts or debugging other aspects of a particular trace you can create various fixture responses with varying levels of dynamic content that is template-driven. **mockstack** can give you a solid foundation for this.
23
23
 
24
24
  * **Chaos Engineering** :boom:. mockstack can simulate various real-world runtime error scenarios such as timeouts, http error codes, and invalid response payloads. This can be a great way to do some upfront [Chaos Monkey](https://github.com/Netflix/chaosmonkey) type of testing on software components.
25
25
 
@@ -44,21 +44,28 @@ or install into a persistent environment and add it to the PATH with:
44
44
 
45
45
  ## Usage
46
46
 
47
- See the [examples](https://github.com/adamhadani/mockstack/blob/main/examples/) directory for complete examples with documentation.
47
+ See the [examples](https://github.com/promptromp/mockstack/blob/main/examples/) directory for complete examples with documentation.
48
48
 
49
- Available configuration options are [here](https://github.com/adamhadani/mockstack/blob/main/mockstack/config.py).
49
+ Available configuration options are [here](https://github.com/promptromp/mockstack/blob/main/mockstack/config.py).
50
50
 
51
- Setting individual options can be done either through an `.env` file, individual environment variables, or command-line arguments. For example:
51
+ Setting individual options can be done either through an `.env` file, individual environment variables, or command-line arguments.
52
+
53
+
54
+ Minimal example to get you started:
52
55
 
53
56
  ```shell
54
- export MOCKSTACK__STRATEGY=filefixtures
57
+ mkdir -p ~/mockstack-templates
58
+ echo '{"message": "Hello from mockstack!"}' > ~/mockstack-templates/myservice-api-myresource.j2
59
+
55
60
  export MOCKSTACK__TEMPLATES_DIR=~/mockstack-templates/
56
- export MOCKSTACK__OPENTELEMETRY__ENABLED=true
57
- export MOCKSTACK__OPENTELEMETRY__CAPTURE_RESPONSE_BODY=true
58
61
  uvx mockstack
59
62
  ```
60
63
 
61
- 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.
64
+ You can then hit `http://localhost:8000/myservice/api/myresource/23faa8cf-5daa-4bcb-8c92-27018b712aa9` (or any other UUID).
65
+
66
+ This is of course just the tip of the iceberg.
67
+
68
+ See also the included [.env.example](https://github.com/promptromp/mockstack/blob/main/.env.example) for more settings you are likely to find useful. You can copy that file to `.env` and fill in configuration as needed based on the given examples.
62
69
 
63
70
  Out of the box, you get the following behavior when using the default `filefixtures` strategy:
64
71
 
@@ -87,7 +94,7 @@ Linting, formatting, static type checks etc. are all managed via [pre-commit](ht
87
94
 
88
95
  If you are contributing to development, you will want to clone this project, and can then install it locally with:
89
96
 
90
- gh repo clone adamhadani/mockstack
97
+ gh repo clone promptromp/mockstack
91
98
  cd mockstack/
92
99
  uv sync
93
100
  uv pip install -e .
@@ -11,4 +11,4 @@ We do not currently run any bug bounty programs.
11
11
  ## Vulnerability disclosures
12
12
 
13
13
  Critical vulnerabilities will be disclosed via GitHub's
14
- [security advisory](https://github.com/adamhadani/mockstack/security) system.
14
+ [security advisory](https://github.com/promptromp/mockstack/security) system.
@@ -0,0 +1,87 @@
1
+ # Ollama Integration
2
+
3
+ Mockstack provides integration with [Ollama](https://ollama.com/), allowing you to use real LLM responses in your mock templates. This is particularly useful for development, debugging, and integration testing scenarios where you want to capture the non-deterministic nature of LLM responses.
4
+
5
+ ## Prerequisites
6
+
7
+ To use the Ollama integration, you'll need:
8
+
9
+ 1. Mockstack installed with the optional `llm` dependencies:
10
+ ```bash
11
+ uv pip install mockstack[llm]
12
+ ```
13
+
14
+ 2. [Ollama](https://ollama.com/) installed locally with at least one model (e.g., "llama3.2")
15
+
16
+ ## Basic Usage
17
+
18
+ The Ollama integration works by routing requests to a template file that uses the special `ollama` template function.
19
+
20
+ 1. Configure your LLM client to hit an endpoint that maps to a template filepath calling the `ollama` method, e.g.:
21
+ ```python
22
+ from langchain_openai import ChatOpenAI
23
+
24
+ llm = ChatOpenAI(
25
+ model="gpt-4o",
26
+ base_url="http://localhost:8000/ollama/openai/v1",
27
+ api_key="SOME_STRING_THAT_DOES_NOT_MATTER",
28
+ )
29
+ ```
30
+
31
+ 2. Make requests as you normally would:
32
+ ```python
33
+ messages = [
34
+ (
35
+ "system",
36
+ "You are a helpful assistant that translates English to French. Translate the user sentence.",
37
+ ),
38
+ ("human", "mockstack is pretty cool. But LLMs are way cooler"),
39
+ ]
40
+ ai_msg = llm.invoke(messages)
41
+ print(ai_msg.content)
42
+ ```
43
+
44
+ ## Integration with Templates
45
+
46
+ You can use Ollama responses within your Jinja templates via the provided `ollama` template function. This allows you to:
47
+
48
+ 1. Mix static and dynamic content
49
+ 2. Apply transformations to the LLM responses
50
+ 3. Create conditional logic based on the responses
51
+
52
+ Example template structure:
53
+
54
+ ```jinja
55
+ {
56
+ "id": "chatcmpl-123",
57
+ "object": "chat.completion",
58
+ "created": 1677652288,
59
+ "model": "gpt-4.1",
60
+ "choices": [{
61
+ "index": 0,
62
+ "message": {
63
+ "role": "assistant",
64
+ "content": "{{ ollama(request_json.messages, 'llama3.2') | json_escape }}"
65
+ },
66
+ "finish_reason": "stop"
67
+ }],
68
+ "usage": {
69
+ "prompt_tokens": 9,
70
+ "completion_tokens": 12,
71
+ "total_tokens": 21
72
+ }
73
+ }
74
+ ```
75
+
76
+ ## Best Practices
77
+
78
+ 1. **Caching**: Consider implementing caching for frequently used responses to improve performance
79
+ 2. **Model Selection**: Choose the appropriate model based on your testing needs
80
+ 3. **Error Handling**: Implement proper error handling in your templates
81
+ 4. **Performance**: Be mindful of response times when using real LLM responses
82
+
83
+ ## Limitations
84
+
85
+ - The integration requires a local Ollama instance
86
+ - Response times will be slower than static templates
87
+ - Model availability depends on your local setup
@@ -11,7 +11,7 @@
11
11
  },
12
12
  {
13
13
  "cell_type": "code",
14
- "execution_count": 26,
14
+ "execution_count": 7,
15
15
  "metadata": {},
16
16
  "outputs": [],
17
17
  "source": [
@@ -22,11 +22,31 @@
22
22
  "#!uv pip install langchain langchain-openai"
23
23
  ]
24
24
  },
25
+ {
26
+ "cell_type": "code",
27
+ "execution_count": null,
28
+ "metadata": {},
29
+ "outputs": [],
30
+ "source": [
31
+ "from ollama import chat\n",
32
+ "from ollama import ChatResponse\n",
33
+ "\n",
34
+ "response: ChatResponse = chat(model='llama3.2', messages=[\n",
35
+ " {\n",
36
+ " 'role': 'user',\n",
37
+ " 'content': 'Why is the sky blue?',\n",
38
+ " },\n",
39
+ "])\n",
40
+ "print(response['message']['content'])\n",
41
+ "# or access fields directly from the response object\n",
42
+ "print(response.message.content)"
43
+ ]
44
+ },
25
45
  {
26
46
  "cell_type": "markdown",
27
47
  "metadata": {},
28
48
  "source": [
29
- "## Example #1: Template-based mocking (`filefixtures` strategy)\n",
49
+ "## Example #1: Static template-based mocking (`filefixtures` strategy)\n",
30
50
  "\n",
31
51
  "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
52
  "\n",
@@ -42,7 +62,7 @@
42
62
  },
43
63
  {
44
64
  "cell_type": "code",
45
- "execution_count": 38,
65
+ "execution_count": 2,
46
66
  "metadata": {},
47
67
  "outputs": [],
48
68
  "source": [
@@ -86,7 +106,7 @@
86
106
  },
87
107
  {
88
108
  "cell_type": "code",
89
- "execution_count": 40,
109
+ "execution_count": 3,
90
110
  "metadata": {},
91
111
  "outputs": [],
92
112
  "source": [
@@ -120,17 +140,62 @@
120
140
  "cell_type": "markdown",
121
141
  "metadata": {},
122
142
  "source": [
123
- "## Example #2: `llm` strategy\n",
143
+ "## Example #2: Dynamic template-based mocking with ollama integration\n",
124
144
  "\n",
125
- "** COMING SOON **"
145
+ "In this example we use the [ollama-python](https://github.com/ollama/ollama-python) integration to return mock responses that actually come from a real LLM running on your host! This is a great way to go a step further in development, debugging, and integration testing scenarios where LLMs and their non-determinism are important to capture.\n",
146
+ "\n",
147
+ "For this example you will need to make sure you have the following:\n",
148
+ "\n",
149
+ "* mockstack installed with the optional `llm` dependencies:\n",
150
+ "\n",
151
+ " ```bash\n",
152
+ " uv pip install mockstack[llm]\n",
153
+ " ```\n",
154
+ "\n",
155
+ "* [ollama](https://ollama.com/) installed locally with the \"llama3.2\" model (which is typically the default model installed)"
126
156
  ]
127
157
  },
128
158
  {
129
159
  "cell_type": "code",
130
160
  "execution_count": null,
131
161
  "metadata": {},
132
- "outputs": [],
133
- "source": []
162
+ "outputs": [
163
+ {
164
+ "name": "stdout",
165
+ "output_type": "stream",
166
+ "text": [
167
+ "Le mockstack est très amusant, mais les LLMs sont beaucoup plus froids.\n",
168
+ "\n",
169
+ "(Note: I used \"froids\" instead of \"cool\" as it's a more common translation in French. If you want to keep the exact word order and meaning, I can suggest an alternative: Le mockstack est très amusant, mais les LLMs sont vraiment cool.)\n"
170
+ ]
171
+ }
172
+ ],
173
+ "source": [
174
+ "from langchain_openai import ChatOpenAI\n",
175
+ "\n",
176
+ "\n",
177
+ "llm = ChatOpenAI(\n",
178
+ " model=\"gpt-4o\",\n",
179
+ " temperature=0,\n",
180
+ " max_tokens=None,\n",
181
+ " timeout=None,\n",
182
+ " max_retries=2,\n",
183
+ " base_url=\"http://localhost:8000/ollama/openai/v1\",\n",
184
+ " api_key=\"SOME_STRING_THAT_DOES_NOT_MATTER\",\n",
185
+ ")\n",
186
+ "\n",
187
+ "messages = [\n",
188
+ " (\n",
189
+ " \"system\",\n",
190
+ " \"You are a helpful assistant that translates English to French. Translate the user sentence.\",\n",
191
+ " ),\n",
192
+ " (\"human\", \"mockstack is pretty cool. But LLMs are way cooler\"),\n",
193
+ "]\n",
194
+ "ai_msg = llm.invoke(messages)\n",
195
+ "\n",
196
+ "# Output would not be deterministic, but should be a valid French translation.\n",
197
+ "print(ai_msg.content)"
198
+ ]
134
199
  },
135
200
  {
136
201
  "cell_type": "markdown",
@@ -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": "{{ ollama(request_json.messages, 'llama3.2') | json_escape }}",
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
+ }
@@ -1,7 +1,7 @@
1
1
  site_name: mockstack
2
- site_url: https://adamhadani.github.io/mockstack
2
+ site_url: https://promptromp.github.io/mockstack
3
3
  repo_name: mockstack
4
- repo_url: https://github.com/adamhadani/mockstack
4
+ repo_url: https://github.com/promptromp/mockstack
5
5
 
6
6
  theme:
7
7
  name: material
@@ -49,10 +49,19 @@ markdown_extensions:
49
49
  - pymdownx.tabbed:
50
50
  alternate_style: true
51
51
 
52
+ nav:
53
+ - Home: README.md
54
+ - Configuration: configuration.md
55
+ - Strategies:
56
+ - BaseStrategy: strategies/base.md
57
+ - FileFixtures: strategies/filefixtures.md
58
+ - ProxyRules: strategies/proxyrules.md
59
+ - LLM Integrations:
60
+ - Ollama: ollama.md
52
61
 
53
62
  extra:
54
63
  social:
55
64
  - icon: simple/github
56
- link: https://github.com/adamhadani/mockstack
65
+ link: https://github.com/promptromp/mockstack
57
66
 
58
67
  copyright: Copyright &copy; 2025 Adam Ever-Hadani
@@ -20,13 +20,13 @@ from mockstack.constants import (
20
20
  class OpenTelemetrySettings(BaseSettings):
21
21
  """Settings for OpenTelemetry."""
22
22
 
23
- enabled: bool = False
23
+ enabled: CliImplicitFlag[bool] = False
24
24
 
25
25
  endpoint: str = "http://localhost:4317/"
26
26
 
27
27
  # whether to capture the response body.
28
28
  # this can be heavy, sensitive (PII) and/or not needed depending on the use case.
29
- capture_response_body: bool = False
29
+ capture_response_body: CliImplicitFlag[bool] = False
30
30
 
31
31
 
32
32
  class Settings(BaseSettings):
@@ -0,0 +1,6 @@
1
+ """Custom exceptions for mockstack."""
2
+
3
+
4
+ def raise_for_missing(message: str, *args, **kwargs):
5
+ """Raise an exception for a missing dependency."""
6
+ raise RuntimeError(message)
@@ -0,0 +1,50 @@
1
+ """Ollama integration"""
2
+
3
+ from typing import List, Dict
4
+
5
+ try:
6
+ from ollama import chat
7
+ from ollama import ChatResponse
8
+
9
+ IS_OLLAMA_AVAILABLE = True
10
+ except ImportError:
11
+ IS_OLLAMA_AVAILABLE = False
12
+
13
+
14
+ if IS_OLLAMA_AVAILABLE:
15
+
16
+ class OllamaLLM:
17
+ def __init__(self, model: str = "llama3.2"):
18
+ self.model = model
19
+
20
+ def __call__(
21
+ self,
22
+ messages: List[Dict[str, str]],
23
+ max_tokens: int = 4096,
24
+ temperature: float = 0.7,
25
+ ) -> str:
26
+ response: ChatResponse = chat(
27
+ model=self.model,
28
+ messages=messages,
29
+ options={"num_ctx": max_tokens, "temperature": temperature},
30
+ )
31
+
32
+ return content(response)
33
+
34
+ def content(response: ChatResponse) -> str:
35
+ """Extract the message content from the LLM response."""
36
+ return response["message"]["content"]
37
+
38
+ def ollama(
39
+ messages: List[Dict[str, str]],
40
+ model: str = "llama3.2",
41
+ *args,
42
+ **kwargs,
43
+ ) -> str:
44
+ """Fluent interface for Ollama to be used in templates."""
45
+
46
+ return OllamaLLM(model)(
47
+ messages,
48
+ *args,
49
+ **kwargs,
50
+ )
@@ -2,21 +2,24 @@
2
2
 
3
3
  import logging
4
4
  import os
5
+ from functools import cached_property
5
6
  from pathlib import Path
6
7
 
7
8
  from fastapi import HTTPException, Request, Response, status
8
- from jinja2 import Environment, FileSystemLoader
9
+ from fastapi.responses import JSONResponse
10
+ from jinja2 import Environment
9
11
 
10
12
  from mockstack.config import Settings
11
13
  from mockstack.intent import (
12
14
  looks_like_a_command,
13
15
  looks_like_a_search,
16
+ wants_json,
14
17
  )
15
18
  from mockstack.strategies.base import BaseStrategy
16
19
  from mockstack.strategies.create_mixin import CreateMixin
17
20
  from mockstack.templating import (
18
21
  iter_possible_template_arguments,
19
- missing_template_detail,
22
+ templates_env_provider,
20
23
  )
21
24
 
22
25
 
@@ -37,8 +40,6 @@ class FileFixturesStrategy(BaseStrategy, CreateMixin):
37
40
  self.created_resource_metadata = settings.created_resource_metadata
38
41
  self.missing_resource_fields = settings.missing_resource_fields
39
42
 
40
- self.env = Environment(loader=FileSystemLoader(self.templates_dir))
41
-
42
43
  def __str__(self) -> str:
43
44
  return (
44
45
  f"[medium_purple]filefixtures[/medium_purple]\n "
@@ -46,6 +47,11 @@ class FileFixturesStrategy(BaseStrategy, CreateMixin):
46
47
  f"enable_templates_for_post: [medium_purple]{self.enable_templates_for_post}[/medium_purple]. "
47
48
  )
48
49
 
50
+ @cached_property
51
+ def env(self) -> Environment:
52
+ """Jinja2 environment for the filefixtures strategy."""
53
+ return templates_env_provider(self.templates_dir)
54
+
49
55
  async def apply(self, request: Request) -> Response:
50
56
  match request.method:
51
57
  case "GET":
@@ -74,9 +80,10 @@ class FileFixturesStrategy(BaseStrategy, CreateMixin):
74
80
  We also allow a configuration to specify a default intent.
75
81
 
76
82
  """
83
+ request_json = (await request.json()) if wants_json(request) else None
77
84
  if self.enable_templates_for_post:
78
85
  try:
79
- return self._response_from_template(request)
86
+ return self._response_from_template(request, request_json=request_json)
80
87
  except HTTPException as e:
81
88
  if e.status_code == status.HTTP_404_NOT_FOUND:
82
89
  # If the template is not found, we try to create the resource with logic below.
@@ -86,12 +93,12 @@ class FileFixturesStrategy(BaseStrategy, CreateMixin):
86
93
 
87
94
  if looks_like_a_search(request):
88
95
  # Searching for resources with a complex query that cannot be expressed in a URI.
89
- return self._response_from_template(request)
96
+ return self._response_from_template(request, request_json=request_json)
90
97
  elif looks_like_a_command(request):
91
98
  # Executing a 'command' of some sort, like a workflow or a batch job.
92
99
  # We return a 201 CREATED status code with response from template.
93
100
  return self._response_from_template(
94
- request, status_code=status.HTTP_201_CREATED
101
+ request, request_json=request_json, status_code=status.HTTP_201_CREATED
95
102
  )
96
103
  else:
97
104
  # simulate resource creation:
@@ -133,9 +140,15 @@ class FileFixturesStrategy(BaseStrategy, CreateMixin):
133
140
  return Response(status_code=status.HTTP_204_NO_CONTENT)
134
141
 
135
142
  def _response_from_template(
136
- self, request: Request, status_code: int = status.HTTP_200_OK
143
+ self,
144
+ request: Request,
145
+ *,
146
+ request_json: dict | None = None,
147
+ status_code: int = status.HTTP_200_OK,
137
148
  ) -> Response:
138
- for template_args in iter_possible_template_arguments(request):
149
+ for template_args in iter_possible_template_arguments(
150
+ request, request_json=request_json
151
+ ):
139
152
  filename = self.templates_dir / template_args["name"]
140
153
  self.logger.debug("Looking for template filename: %s", filename)
141
154
  if not os.path.exists(filename):
@@ -152,17 +165,10 @@ class FileFixturesStrategy(BaseStrategy, CreateMixin):
152
165
  )
153
166
 
154
167
  # if we get here, we have no template to render.
155
- raise HTTPException(
156
- status_code=status.HTTP_404_NOT_FOUND,
157
- detail=missing_template_detail(request, templates_dir=self.templates_dir),
158
- )
159
- """
160
- # TODO: return custom fields from settings
161
168
  return JSONResponse(
162
169
  content=self.missing_resource_fields,
163
170
  status_code=status.HTTP_404_NOT_FOUND,
164
171
  )
165
- """
166
172
 
167
173
  def update_opentelemetry(self, request: Request, template_args: dict) -> None:
168
174
  """Update the opentelemetry span with the file fixtures details."""