mockworld 0.2.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.
- mockworld-0.2.0/.claude/settings.local.json +7 -0
- mockworld-0.2.0/.gitignore +101 -0
- mockworld-0.2.0/LICENSE +21 -0
- mockworld-0.2.0/PKG-INFO +320 -0
- mockworld-0.2.0/README.md +287 -0
- mockworld-0.2.0/example.py +89 -0
- mockworld-0.2.0/mockworld/__init__.py +20 -0
- mockworld-0.2.0/mockworld/_version.py +34 -0
- mockworld-0.2.0/mockworld/config.py +147 -0
- mockworld-0.2.0/mockworld/interceptor.py +206 -0
- mockworld-0.2.0/mockworld/mappings/mokra.yml +19 -0
- mockworld-0.2.0/mockworld/service_mapper.py +202 -0
- mockworld-0.2.0/mockworld/world.py +671 -0
- mockworld-0.2.0/pyproject.toml +103 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
*.manifest
|
|
31
|
+
*.spec
|
|
32
|
+
|
|
33
|
+
# Installer logs
|
|
34
|
+
pip-log.txt
|
|
35
|
+
pip-delete-this-directory.txt
|
|
36
|
+
|
|
37
|
+
# Unit test / coverage reports
|
|
38
|
+
htmlcov/
|
|
39
|
+
.tox/
|
|
40
|
+
.nox/
|
|
41
|
+
.coverage
|
|
42
|
+
.coverage.*
|
|
43
|
+
.cache
|
|
44
|
+
nosetests.xml
|
|
45
|
+
coverage.xml
|
|
46
|
+
*.cover
|
|
47
|
+
*.py,cover
|
|
48
|
+
.hypothesis/
|
|
49
|
+
.pytest_cache/
|
|
50
|
+
|
|
51
|
+
# Translations
|
|
52
|
+
*.mo
|
|
53
|
+
*.pot
|
|
54
|
+
|
|
55
|
+
# Environments
|
|
56
|
+
.env
|
|
57
|
+
.venv
|
|
58
|
+
env/
|
|
59
|
+
venv/
|
|
60
|
+
ENV/
|
|
61
|
+
env.bak/
|
|
62
|
+
venv.bak/
|
|
63
|
+
|
|
64
|
+
# Spyder project settings
|
|
65
|
+
.spyderproject
|
|
66
|
+
.spyproject
|
|
67
|
+
|
|
68
|
+
# Rope project settings
|
|
69
|
+
.ropeproject
|
|
70
|
+
|
|
71
|
+
# mkdocs documentation
|
|
72
|
+
/site
|
|
73
|
+
|
|
74
|
+
# mypy
|
|
75
|
+
.mypy_cache/
|
|
76
|
+
.dmypy.json
|
|
77
|
+
dmypy.json
|
|
78
|
+
|
|
79
|
+
# Pyre type checker
|
|
80
|
+
.pyre/
|
|
81
|
+
|
|
82
|
+
# pytype static type analyzer
|
|
83
|
+
.pytype/
|
|
84
|
+
|
|
85
|
+
# Cython debug symbols
|
|
86
|
+
cython_debug/
|
|
87
|
+
|
|
88
|
+
# IDE
|
|
89
|
+
.idea/
|
|
90
|
+
.vscode/
|
|
91
|
+
*.swp
|
|
92
|
+
*.swo
|
|
93
|
+
*~
|
|
94
|
+
|
|
95
|
+
# OS
|
|
96
|
+
.DS_Store
|
|
97
|
+
Thumbs.db
|
|
98
|
+
|
|
99
|
+
# Project specific
|
|
100
|
+
*.egg-info/
|
|
101
|
+
mockworld/_version.py
|
mockworld-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Handled Engineering
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
mockworld-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mockworld
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: World tests for AI agents - intercept and mock HTTP requests across services
|
|
5
|
+
Project-URL: Homepage, https://github.com/handled-engineering/mockworld-python
|
|
6
|
+
Project-URL: Documentation, https://github.com/handled-engineering/mockworld-python#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/handled-engineering/mockworld-python
|
|
8
|
+
Project-URL: Issues, https://github.com/handled-engineering/mockworld-python/issues
|
|
9
|
+
Author-email: Handled Engineering <engineering@handled.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: agent,ai,http,interceptor,mocking,testing
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Topic :: Software Development :: Testing
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Requires-Dist: requests>=2.25
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: httpx; extra == 'dev'
|
|
27
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
30
|
+
Requires-Dist: responses; extra == 'dev'
|
|
31
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# mockworld-python
|
|
35
|
+
|
|
36
|
+
> World tests for AI agents - intercept and mock HTTP requests across services
|
|
37
|
+
|
|
38
|
+
[](https://badge.fury.io/py/mockworld)
|
|
39
|
+
[](https://opensource.org/licenses/MIT)
|
|
40
|
+
|
|
41
|
+
mockworld intercepts HTTP requests from your AI agent and redirects them to mock endpoints. Configure which services to intercept, and mockworld handles the rest.
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install mockworld
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import mockworld
|
|
53
|
+
|
|
54
|
+
# Configure connection to MockServer
|
|
55
|
+
mockworld.configure(
|
|
56
|
+
base_url="https://api.mokra.ai/api",
|
|
57
|
+
api_key="your-api-key"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Create a world with your services
|
|
61
|
+
world = mockworld.mockworld("my test", "stripe", "shopify")
|
|
62
|
+
|
|
63
|
+
# Run your agent
|
|
64
|
+
def agent_code(services):
|
|
65
|
+
import requests
|
|
66
|
+
# Your agent code here - HTTP calls are automatically intercepted
|
|
67
|
+
requests.post(
|
|
68
|
+
"https://api.stripe.com/v1/charges",
|
|
69
|
+
json={"amount": 1000, "currency": "usd"}
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
world.run(agent_code)
|
|
73
|
+
|
|
74
|
+
# See what happened
|
|
75
|
+
world.observe()
|
|
76
|
+
|
|
77
|
+
# Assert outcomes
|
|
78
|
+
world.assert_(
|
|
79
|
+
"a charge was created",
|
|
80
|
+
"no errors occurred"
|
|
81
|
+
)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## How It Works
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
88
|
+
│ Your Agent Code │
|
|
89
|
+
│ ┌─────────────────────────────────────────────────────┐ │
|
|
90
|
+
│ │ world.run(lambda services: │ │
|
|
91
|
+
│ │ requests.post("https://api.stripe...│ │
|
|
92
|
+
│ │ ) │ │
|
|
93
|
+
│ └─────────────────────────────────────────────────────┘ │
|
|
94
|
+
└─────────────────────────────────────────────────────────────┘
|
|
95
|
+
│
|
|
96
|
+
▼
|
|
97
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
98
|
+
│ mockworld Interceptor │
|
|
99
|
+
│ │
|
|
100
|
+
│ https://api.stripe.com/v1/charges │
|
|
101
|
+
│ ↓ │
|
|
102
|
+
│ https://api.mokra.ai/api/mock/stripe/v1/charges │
|
|
103
|
+
│ │
|
|
104
|
+
└─────────────────────────────────────────────────────────────┘
|
|
105
|
+
│
|
|
106
|
+
▼
|
|
107
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
108
|
+
│ Mock Server (api.mokra.ai) │
|
|
109
|
+
│ │
|
|
110
|
+
│ Records observations, returns mock responses, │
|
|
111
|
+
│ evaluates assertions, manages state │
|
|
112
|
+
└─────────────────────────────────────────────────────────────┘
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Configuration
|
|
116
|
+
|
|
117
|
+
### Direct Configuration
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
mockworld.configure(
|
|
121
|
+
base_url="https://api.mokra.ai/api",
|
|
122
|
+
api_key="your-api-key"
|
|
123
|
+
)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Environment Variables
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
export MOCKWORLD_BASE_URL="https://api.mokra.ai/api"
|
|
130
|
+
export MOCKWORLD_API_KEY="your-api-key"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Auth Styles
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
# Default: X-API-Key header (for mokra.ai)
|
|
137
|
+
mockworld.configure(
|
|
138
|
+
api_key="your-api-key"
|
|
139
|
+
)
|
|
140
|
+
# Sends: X-API-Key: your-api-key
|
|
141
|
+
|
|
142
|
+
# Bearer token auth
|
|
143
|
+
mockworld.configure(
|
|
144
|
+
api_key="your-token",
|
|
145
|
+
auth_style="bearer"
|
|
146
|
+
)
|
|
147
|
+
# Sends: Authorization: Bearer your-token
|
|
148
|
+
|
|
149
|
+
# Custom header
|
|
150
|
+
mockworld.configure(
|
|
151
|
+
api_key="your-key",
|
|
152
|
+
auth_style="custom",
|
|
153
|
+
auth_header="X-Custom-Auth"
|
|
154
|
+
)
|
|
155
|
+
# Sends: X-Custom-Auth: your-key
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## API Reference
|
|
159
|
+
|
|
160
|
+
### `mockworld.mockworld(name, *services)`
|
|
161
|
+
|
|
162
|
+
Create a new world with specified services.
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
# String shorthand
|
|
166
|
+
world = mockworld.mockworld("my test", "stripe", "shopify", "loop")
|
|
167
|
+
|
|
168
|
+
# Attach existing mock server
|
|
169
|
+
world = mockworld.mockworld("my test",
|
|
170
|
+
"stripe",
|
|
171
|
+
{"service_id": "shopify", "server_id": "existing-ms-id"}
|
|
172
|
+
)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### `world.run(func)` / `with world.run():`
|
|
176
|
+
|
|
177
|
+
Run your agent. All HTTP calls made during execution are intercepted and recorded by MockServer.
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
# Function style
|
|
181
|
+
def agent_code(services):
|
|
182
|
+
# Your agent code
|
|
183
|
+
requests.post("https://api.stripe.com/v1/charges", ...)
|
|
184
|
+
|
|
185
|
+
world.run(agent_code)
|
|
186
|
+
|
|
187
|
+
# Context manager style (recommended for pytest)
|
|
188
|
+
with world.run():
|
|
189
|
+
requests.post("https://api.stripe.com/v1/charges", ...)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### `world.assert_(*assertions)`
|
|
193
|
+
|
|
194
|
+
Assert conditions using natural language.
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
results = world.assert_(
|
|
198
|
+
"customer charged once",
|
|
199
|
+
"no refund issued"
|
|
200
|
+
)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### `world.observe()`
|
|
204
|
+
|
|
205
|
+
Display observations recorded by MockServer.
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
world.observe()
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### `world.seed(func)`
|
|
212
|
+
|
|
213
|
+
Pre-populate state before running the agent.
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
def setup(s):
|
|
217
|
+
s.shopify.orders.create({"id": "order-123", "total": 99.99})
|
|
218
|
+
|
|
219
|
+
world.seed(setup)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### `world.state(mock_server_id=None)`
|
|
223
|
+
|
|
224
|
+
Get current state of a mock server.
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
state = world.state()
|
|
228
|
+
state.total # => 5
|
|
229
|
+
state.resource_types # => ["products", "orders"]
|
|
230
|
+
state["products"].count # => 3
|
|
231
|
+
state["products"].records # => [StateRecord, ...]
|
|
232
|
+
state["products"].find_by_id("prod-123")
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Service Mappings
|
|
236
|
+
|
|
237
|
+
mockworld uses YAML files to map hosts to service slugs. The default mappings for mokra.ai are included.
|
|
238
|
+
|
|
239
|
+
### Custom Mappings
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
from mockworld import ServiceMapper
|
|
243
|
+
|
|
244
|
+
# Add a single mapping
|
|
245
|
+
ServiceMapper.add_mapping("api.myservice.com", "myservice")
|
|
246
|
+
|
|
247
|
+
# Load mappings from a YAML file
|
|
248
|
+
ServiceMapper.load_mappings("path/to/custom.yml")
|
|
249
|
+
|
|
250
|
+
# Load and replace all mappings
|
|
251
|
+
ServiceMapper.load_mappings("path/to/custom.yml", replace=True)
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Custom YAML Format
|
|
255
|
+
|
|
256
|
+
```yaml
|
|
257
|
+
# custom_mappings.yml
|
|
258
|
+
api.myservice.com: myservice
|
|
259
|
+
api.another.com: another
|
|
260
|
+
*.internal.mycompany.com: internal
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Supporting Other Mock Servers
|
|
264
|
+
|
|
265
|
+
Create a YAML file in the mappings directory named after your provider:
|
|
266
|
+
|
|
267
|
+
```yaml
|
|
268
|
+
# my_provider.yml
|
|
269
|
+
api.stripe.com: stripe
|
|
270
|
+
api.custom.com: custom
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Then load it:
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
from mockworld import ServiceMapper
|
|
277
|
+
|
|
278
|
+
ServiceMapper.load_provider("my_provider")
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Pytest Integration
|
|
282
|
+
|
|
283
|
+
mockworld works seamlessly with pytest:
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
# conftest.py
|
|
287
|
+
import pytest
|
|
288
|
+
import mockworld
|
|
289
|
+
import os
|
|
290
|
+
|
|
291
|
+
@pytest.fixture(autouse=True)
|
|
292
|
+
def configure_mockworld():
|
|
293
|
+
mockworld.configure(api_key=os.environ["MOCKWORLD_API_KEY"])
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
# test_refund.py
|
|
298
|
+
from mockworld import mockworld
|
|
299
|
+
|
|
300
|
+
def test_refund_service():
|
|
301
|
+
world = mockworld(name="Refund test", services_list=["stripe"])
|
|
302
|
+
|
|
303
|
+
with world.run():
|
|
304
|
+
# Your code that calls Stripe API
|
|
305
|
+
RefundService.process(payment_id="pi_123", amount=5000)
|
|
306
|
+
|
|
307
|
+
world.assert_("a refund was created")
|
|
308
|
+
world.assert_("refund amount is $50")
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Development
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
pip install -e ".[dev]"
|
|
315
|
+
pytest
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## License
|
|
319
|
+
|
|
320
|
+
MIT
|