mockserver-client 7.6.0__tar.gz → 8.0.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 (40) hide show
  1. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/PKG-INFO +2 -2
  2. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/README.md +1 -1
  3. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/mockserver/async_client.py +107 -5
  4. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/mockserver/client.py +37 -1
  5. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/mockserver/launcher.py +1 -1
  6. mockserver_client-8.0.0/mockserver/py.typed +0 -0
  7. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/mockserver_client.egg-info/PKG-INFO +2 -2
  8. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/mockserver_client.egg-info/SOURCES.txt +1 -0
  9. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/pyproject.toml +7 -1
  10. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/tests/test_client.py +58 -2
  11. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/mockserver/__init__.py +0 -0
  12. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/mockserver/a2a.py +0 -0
  13. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/mockserver/exceptions.py +0 -0
  14. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/mockserver/fluent.py +0 -0
  15. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/mockserver/llm.py +0 -0
  16. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/mockserver/mcp.py +0 -0
  17. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/mockserver/models.py +0 -0
  18. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/mockserver/pytest_plugin.py +0 -0
  19. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/mockserver/websocket_client.py +0 -0
  20. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/mockserver_client.egg-info/dependency_links.txt +0 -0
  21. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/mockserver_client.egg-info/entry_points.txt +0 -0
  22. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/mockserver_client.egg-info/requires.txt +0 -0
  23. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/mockserver_client.egg-info/top_level.txt +0 -0
  24. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/setup.cfg +0 -0
  25. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/tests/test_a2a.py +0 -0
  26. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/tests/test_async_client.py +0 -0
  27. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/tests/test_auth_tls.py +0 -0
  28. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/tests/test_breakpoint.py +0 -0
  29. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/tests/test_control_plane.py +0 -0
  30. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/tests/test_exceptions.py +0 -0
  31. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/tests/test_fluent.py +0 -0
  32. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/tests/test_init.py +0 -0
  33. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/tests/test_integration.py +0 -0
  34. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/tests/test_launcher.py +0 -0
  35. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/tests/test_llm.py +0 -0
  36. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/tests/test_mcp.py +0 -0
  37. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/tests/test_models.py +0 -0
  38. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/tests/test_pytest_plugin.py +0 -0
  39. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/tests/test_roundtrip_fidelity.py +0 -0
  40. {mockserver_client-7.6.0 → mockserver_client-8.0.0}/tests/test_websocket_client.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mockserver-client
3
- Version: 7.6.0
3
+ Version: 8.0.0
4
4
  Summary: Official Python client for MockServer — create expectations, verify requests, and register dynamic callbacks via WebSocket
5
5
  Author-email: James Bloom <jamesdbloom@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -486,7 +486,7 @@ launcher_path = ensure_binary() # returns Path to the launcher executable
486
486
  ```python
487
487
  from mockserver.launcher import start
488
488
 
489
- server = start(port=1080, version="7.6.0")
489
+ server = start(port=1080, version="8.0.0")
490
490
  # ...
491
491
  server.stop()
492
492
  ```
@@ -456,7 +456,7 @@ launcher_path = ensure_binary() # returns Path to the launcher executable
456
456
  ```python
457
457
  from mockserver.launcher import start
458
458
 
459
- server = start(port=1080, version="7.6.0")
459
+ server = start(port=1080, version="8.0.0")
460
460
  # ...
461
461
  server.stop()
462
462
  ```
@@ -757,18 +757,21 @@ class AsyncMockServerClient:
757
757
  Returns the verdict dict with a ``result`` of ``PASS`` or ``INCONCLUSIVE``
758
758
  (HTTP 200). A ``FAIL`` verdict (HTTP 406) raises
759
759
  :class:`MockServerVerificationError` so a CI or chaos gate can assert on it
760
- directly. SLO tracking is off by default — the server returns 400 (raised as
761
- :class:`MockServerError`) until started with ``sloTrackingEnabled=true``.
760
+ directly. SLO tracking is off by default — the server returns 403 (raised as
761
+ :class:`MockServerError`) until started with ``sloTrackingEnabled=true``;
762
+ malformed criteria are a separate 400.
762
763
  """
763
764
  body = json.dumps(criteria)
764
765
  status, response_body = await self._request("PUT", "/mockserver/verifySLO", body)
765
766
  if status == 406:
766
767
  raise MockServerVerificationError(response_body or "SLO verdict: FAIL")
767
- if status == 400:
768
+ if status == 403:
768
769
  raise MockServerError(
769
- "Invalid SLO criteria (or SLO tracking disabled — set "
770
- f"sloTrackingEnabled=true): {response_body}"
770
+ "SLO tracking is disabled — start MockServer with "
771
+ f"sloTrackingEnabled=true: {response_body}"
771
772
  )
773
+ if status == 400:
774
+ raise MockServerError(f"Invalid SLO criteria: {response_body}")
772
775
  if status >= 400:
773
776
  raise MockServerError(
774
777
  f"Failed to verify SLO (status={status}): {response_body}"
@@ -1215,6 +1218,105 @@ class AsyncMockServerClient:
1215
1218
  return [Expectation.from_dict(e) for e in parsed]
1216
1219
  return []
1217
1220
 
1221
+ async def _retrieve_by_id(
1222
+ self, expectation_id: str, query_params: dict[str, str]
1223
+ ) -> tuple[int, str]:
1224
+ """Send a retrieve request filtered by expectation id.
1225
+
1226
+ An expectation id may be sent in place of a request matcher. For
1227
+ ``ACTIVE_EXPECTATIONS`` the server returns only the expectation with that
1228
+ id; for every other type it returns the entries matching that
1229
+ expectation's request, exactly as verify by expectation id matches them.
1230
+ An unknown id is rejected with a 400.
1231
+ """
1232
+ return await self._request(
1233
+ "PUT",
1234
+ "/mockserver/retrieve",
1235
+ json.dumps({"id": expectation_id}),
1236
+ query_params,
1237
+ )
1238
+
1239
+ async def retrieve_recorded_requests_by_id(
1240
+ self, expectation_id: str
1241
+ ) -> list[HttpRequest]:
1242
+ status, response_body = await self._retrieve_by_id(
1243
+ expectation_id, {"type": "REQUESTS", "format": "JSON"}
1244
+ )
1245
+ if status >= 400:
1246
+ raise MockServerError(
1247
+ f"Failed to retrieve recorded requests by id (status={status}): {response_body}"
1248
+ )
1249
+ if response_body:
1250
+ parsed = json.loads(response_body)
1251
+ if isinstance(parsed, list):
1252
+ return [HttpRequest.from_dict(r) for r in parsed]
1253
+ return []
1254
+
1255
+ async def retrieve_active_expectations_by_id(
1256
+ self, expectation_id: str
1257
+ ) -> list[Expectation]:
1258
+ status, response_body = await self._retrieve_by_id(
1259
+ expectation_id, {"type": "ACTIVE_EXPECTATIONS", "format": "JSON"}
1260
+ )
1261
+ if status >= 400:
1262
+ raise MockServerError(
1263
+ f"Failed to retrieve active expectations by id (status={status}): {response_body}"
1264
+ )
1265
+ if response_body:
1266
+ parsed = json.loads(response_body)
1267
+ if isinstance(parsed, list):
1268
+ return [Expectation.from_dict(e) for e in parsed]
1269
+ return []
1270
+
1271
+ async def retrieve_recorded_expectations_by_id(
1272
+ self, expectation_id: str
1273
+ ) -> list[Expectation]:
1274
+ status, response_body = await self._retrieve_by_id(
1275
+ expectation_id, {"type": "RECORDED_EXPECTATIONS", "format": "JSON"}
1276
+ )
1277
+ if status >= 400:
1278
+ raise MockServerError(
1279
+ f"Failed to retrieve recorded expectations by id (status={status}): {response_body}"
1280
+ )
1281
+ if response_body:
1282
+ parsed = json.loads(response_body)
1283
+ if isinstance(parsed, list):
1284
+ return [Expectation.from_dict(e) for e in parsed]
1285
+ return []
1286
+
1287
+ async def retrieve_recorded_requests_and_responses_by_id(
1288
+ self, expectation_id: str
1289
+ ) -> list[HttpRequestAndHttpResponse]:
1290
+ status, response_body = await self._retrieve_by_id(
1291
+ expectation_id, {"type": "REQUEST_RESPONSES", "format": "JSON"}
1292
+ )
1293
+ if status >= 400:
1294
+ raise MockServerError(
1295
+ f"Failed to retrieve request/responses by id (status={status}): {response_body}"
1296
+ )
1297
+ if response_body:
1298
+ parsed = json.loads(response_body)
1299
+ if isinstance(parsed, list):
1300
+ return [HttpRequestAndHttpResponse.from_dict(rr) for rr in parsed]
1301
+ return []
1302
+
1303
+ async def retrieve_log_messages_by_id(self, expectation_id: str) -> list[str]:
1304
+ status, response_body = await self._retrieve_by_id(
1305
+ expectation_id, {"type": "LOGS"}
1306
+ )
1307
+ if status >= 400:
1308
+ raise MockServerError(
1309
+ f"Failed to retrieve log messages by id (status={status}): {response_body}"
1310
+ )
1311
+ if response_body:
1312
+ try:
1313
+ parsed = json.loads(response_body)
1314
+ if isinstance(parsed, list):
1315
+ return parsed
1316
+ except json.JSONDecodeError:
1317
+ return response_body.split("------------------------------------\n")
1318
+ return []
1319
+
1218
1320
  async def retrieve_expectations_as_code(
1219
1321
  self, fmt: str = "java", request: HttpRequest | None = None
1220
1322
  ) -> str:
@@ -378,7 +378,8 @@ class MockServerClient:
378
378
 
379
379
  Returns the verdict dict (``result`` PASS or INCONCLUSIVE). A FAIL verdict
380
380
  (HTTP 406) raises :class:`MockServerVerificationError`. SLO tracking is off
381
- by default (HTTP 400 until ``sloTrackingEnabled=true``).
381
+ by default (HTTP 403 until ``sloTrackingEnabled=true``); malformed criteria
382
+ are a separate HTTP 400.
382
383
  """
383
384
  return self._run(self._async_client.verify_slo(criteria))
384
385
 
@@ -552,6 +553,41 @@ class MockServerClient:
552
553
  ) -> list[Expectation]:
553
554
  return self._run(self._async_client.retrieve_recorded_expectations(request))
554
555
 
556
+ def retrieve_recorded_requests_by_id(
557
+ self, expectation_id: str
558
+ ) -> list[HttpRequest]:
559
+ return self._run(
560
+ self._async_client.retrieve_recorded_requests_by_id(expectation_id)
561
+ )
562
+
563
+ def retrieve_active_expectations_by_id(
564
+ self, expectation_id: str
565
+ ) -> list[Expectation]:
566
+ return self._run(
567
+ self._async_client.retrieve_active_expectations_by_id(expectation_id)
568
+ )
569
+
570
+ def retrieve_recorded_expectations_by_id(
571
+ self, expectation_id: str
572
+ ) -> list[Expectation]:
573
+ return self._run(
574
+ self._async_client.retrieve_recorded_expectations_by_id(expectation_id)
575
+ )
576
+
577
+ def retrieve_recorded_requests_and_responses_by_id(
578
+ self, expectation_id: str
579
+ ) -> list[HttpRequestAndHttpResponse]:
580
+ return self._run(
581
+ self._async_client.retrieve_recorded_requests_and_responses_by_id(
582
+ expectation_id
583
+ )
584
+ )
585
+
586
+ def retrieve_log_messages_by_id(self, expectation_id: str) -> list[str]:
587
+ return self._run(
588
+ self._async_client.retrieve_log_messages_by_id(expectation_id)
589
+ )
590
+
555
591
  def retrieve_expectations_as_code(
556
592
  self, fmt: str = "java", request: HttpRequest | None = None
557
593
  ) -> str:
@@ -45,7 +45,7 @@ logger = logging.getLogger(__name__)
45
45
 
46
46
  # The version shipped with this client. ``ensure_binary()`` defaults to this
47
47
  # when no explicit version is passed.
48
- _CLIENT_VERSION = "7.6.0"
48
+ _CLIENT_VERSION = "8.0.0"
49
49
 
50
50
  _REPO = "mock-server/mockserver-monorepo"
51
51
 
File without changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mockserver-client
3
- Version: 7.6.0
3
+ Version: 8.0.0
4
4
  Summary: Official Python client for MockServer — create expectations, verify requests, and register dynamic callbacks via WebSocket
5
5
  Author-email: James Bloom <jamesdbloom@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -486,7 +486,7 @@ launcher_path = ensure_binary() # returns Path to the launcher executable
486
486
  ```python
487
487
  from mockserver.launcher import start
488
488
 
489
- server = start(port=1080, version="7.6.0")
489
+ server = start(port=1080, version="8.0.0")
490
490
  # ...
491
491
  server.stop()
492
492
  ```
@@ -10,6 +10,7 @@ mockserver/launcher.py
10
10
  mockserver/llm.py
11
11
  mockserver/mcp.py
12
12
  mockserver/models.py
13
+ mockserver/py.typed
13
14
  mockserver/pytest_plugin.py
14
15
  mockserver/websocket_client.py
15
16
  mockserver_client.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mockserver-client"
3
- version = "7.6.0"
3
+ version = "8.0.0"
4
4
  description = "Official Python client for MockServer — create expectations, verify requests, and register dynamic callbacks via WebSocket"
5
5
  authors = [
6
6
  {name = "James Bloom", email = "jamesdbloom@gmail.com"},
@@ -47,6 +47,12 @@ Changelog = "https://www.mock-server.com/mock_server/changelog.html"
47
47
  requires = ["setuptools"]
48
48
  build-backend = "setuptools.build_meta"
49
49
 
50
+ # PEP 561: ship the inline annotations. Without this marker mypy ignores the types entirely
51
+ # ("module is installed, but missing library stubs or py.typed marker"), and without the
52
+ # package-data entry setuptools would not put the marker in the wheel or sdist at all.
53
+ [tool.setuptools.package-data]
54
+ "mockserver" = ["py.typed"]
55
+
50
56
  [tool.pytest.ini_options]
51
57
  testpaths = ["tests"]
52
58
  asyncio_mode = "auto"
@@ -245,6 +245,54 @@ class TestSyncRetrieve:
245
245
  result = client.retrieve_recorded_expectations()
246
246
  assert len(result) == 1
247
247
 
248
+ def test_retrieve_recorded_requests_by_id(self, sync_mock_server):
249
+ SyncMockHandler.response_body = json.dumps([
250
+ {"method": "GET", "path": "/recorded"},
251
+ ])
252
+ with MockServerClient("127.0.0.1", sync_mock_server) as client:
253
+ result = client.retrieve_recorded_requests_by_id("exp-abc")
254
+ assert len(result) == 1
255
+ assert result[0].path == "/recorded"
256
+ assert json.loads(SyncMockHandler.last_request_body)["id"] == "exp-abc"
257
+ assert "type=REQUESTS" in SyncMockHandler.last_path
258
+
259
+ def test_retrieve_active_expectations_by_id(self, sync_mock_server):
260
+ SyncMockHandler.response_body = json.dumps([
261
+ {"id": "exp-abc", "httpRequest": {"path": "/active"}},
262
+ ])
263
+ with MockServerClient("127.0.0.1", sync_mock_server) as client:
264
+ result = client.retrieve_active_expectations_by_id("exp-abc")
265
+ assert len(result) == 1
266
+ assert result[0].id == "exp-abc"
267
+ assert json.loads(SyncMockHandler.last_request_body)["id"] == "exp-abc"
268
+ assert "type=ACTIVE_EXPECTATIONS" in SyncMockHandler.last_path
269
+
270
+ def test_retrieve_recorded_expectations_by_id(self, sync_mock_server):
271
+ SyncMockHandler.response_body = json.dumps([{"id": "re1"}])
272
+ with MockServerClient("127.0.0.1", sync_mock_server) as client:
273
+ result = client.retrieve_recorded_expectations_by_id("exp-abc")
274
+ assert len(result) == 1
275
+ assert json.loads(SyncMockHandler.last_request_body)["id"] == "exp-abc"
276
+ assert "type=RECORDED_EXPECTATIONS" in SyncMockHandler.last_path
277
+
278
+ def test_retrieve_recorded_requests_and_responses_by_id(self, sync_mock_server):
279
+ SyncMockHandler.response_body = json.dumps([
280
+ {"httpRequest": {"path": "/recorded"}, "httpResponse": {"statusCode": 200}},
281
+ ])
282
+ with MockServerClient("127.0.0.1", sync_mock_server) as client:
283
+ result = client.retrieve_recorded_requests_and_responses_by_id("exp-abc")
284
+ assert len(result) == 1
285
+ assert json.loads(SyncMockHandler.last_request_body)["id"] == "exp-abc"
286
+ assert "type=REQUEST_RESPONSES" in SyncMockHandler.last_path
287
+
288
+ def test_retrieve_log_messages_by_id(self, sync_mock_server):
289
+ SyncMockHandler.response_body = json.dumps(["log one"])
290
+ with MockServerClient("127.0.0.1", sync_mock_server) as client:
291
+ result = client.retrieve_log_messages_by_id("exp-abc")
292
+ assert result == ["log one"]
293
+ assert json.loads(SyncMockHandler.last_request_body)["id"] == "exp-abc"
294
+ assert "type=LOGS" in SyncMockHandler.last_path
295
+
248
296
  def test_retrieve_expectations_as_code(self, sync_mock_server):
249
297
  SyncMockHandler.response_body = 'when(request().withPath("/code"));'
250
298
  with MockServerClient("127.0.0.1", sync_mock_server) as client:
@@ -555,10 +603,18 @@ class TestSyncVerifySlo:
555
603
  client.verify_slo({"name": "x"})
556
604
 
557
605
  def test_verify_slo_disabled_raises_error(self, sync_mock_server):
606
+ # 403 is "SLO tracking is off"; it used to share 400 with malformed criteria
607
+ SyncMockHandler.response_status = 403
608
+ SyncMockHandler.response_body = "SLO tracking not enabled"
609
+ with MockServerClient("127.0.0.1", sync_mock_server) as client:
610
+ with pytest.raises(MockServerError, match="sloTrackingEnabled=true"):
611
+ client.verify_slo({"name": "x"})
612
+
613
+ def test_verify_slo_malformed_criteria_raises_invalid_error(self, sync_mock_server):
558
614
  SyncMockHandler.response_status = 400
559
- SyncMockHandler.response_body = "SLO tracking disabled"
615
+ SyncMockHandler.response_body = "objectives must be an array"
560
616
  with MockServerClient("127.0.0.1", sync_mock_server) as client:
561
- with pytest.raises(MockServerError, match="SLO tracking disabled"):
617
+ with pytest.raises(MockServerError, match="Invalid SLO criteria"):
562
618
  client.verify_slo({"name": "x"})
563
619
 
564
620