simudyne-pulse 0.7.0.dev17__tar.gz → 0.7.0.dev18__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 (25) hide show
  1. {simudyne_pulse-0.7.0.dev17/src/simudyne_pulse.egg-info → simudyne_pulse-0.7.0.dev18}/PKG-INFO +1 -1
  2. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev18}/pyproject.toml +1 -1
  3. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev18}/src/simudyne/resources/validation.py +52 -5
  4. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev18/src/simudyne_pulse.egg-info}/PKG-INFO +1 -1
  5. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev18}/tests/test_validation.py +22 -1
  6. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev18}/LICENSE +0 -0
  7. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev18}/README.md +0 -0
  8. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev18}/setup.cfg +0 -0
  9. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev18}/src/simudyne/__init__.py +0 -0
  10. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev18}/src/simudyne/client.py +0 -0
  11. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev18}/src/simudyne/exceptions.py +0 -0
  12. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev18}/src/simudyne/resources/__init__.py +0 -0
  13. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev18}/src/simudyne/resources/api_keys.py +0 -0
  14. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev18}/src/simudyne/resources/data.py +0 -0
  15. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev18}/src/simudyne/resources/fix.py +0 -0
  16. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev18}/src/simudyne/resources/fm.py +0 -0
  17. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev18}/src/simudyne/resources/profile.py +0 -0
  18. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev18}/src/simudyne/resources/simulation.py +0 -0
  19. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev18}/src/simudyne/resources/simulator_gym.py +0 -0
  20. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev18}/src/simudyne_pulse.egg-info/SOURCES.txt +0 -0
  21. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev18}/src/simudyne_pulse.egg-info/dependency_links.txt +0 -0
  22. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev18}/src/simudyne_pulse.egg-info/requires.txt +0 -0
  23. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev18}/src/simudyne_pulse.egg-info/top_level.txt +0 -0
  24. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev18}/tests/test_new_resources.py +0 -0
  25. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev18}/tests/test_simulator_gym.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simudyne-pulse
3
- Version: 0.7.0.dev17
3
+ Version: 0.7.0.dev18
4
4
  Summary: Python SDK for the Simudyne Pulse synthetic market data API
5
5
  Author-email: Simudyne <support@simudyne.com>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "simudyne-pulse"
7
- version = "0.7.0-dev.17"
7
+ version = "0.7.0-dev.18"
8
8
  description = "Python SDK for the Simudyne Pulse synthetic market data API"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -561,19 +561,57 @@ class ValidationResource:
561
561
  job["plot_paths"] = _save_plots(job, plot_dir)
562
562
  return job
563
563
 
564
- def list_jobs(self, limit: int = 50) -> dict:
564
+ def get_job_status(self, job_id: str) -> dict:
565
+ """Where a validation job has got to, without its result.
566
+
567
+ The full result is large and tier-filtered, so a poll loop should ask
568
+ this instead of :meth:`get_job` and fetch the result once, when
569
+ ``is_complete`` turns true.
570
+
571
+ Parameters
572
+ ----------
573
+ job_id : str
574
+ A validation job handle from :meth:`run`.
575
+
576
+ Returns
577
+ -------
578
+ dict
579
+ ``{job_id, status, message, is_complete, created_at, updated_at}``.
580
+ ``status`` is one of pending / running / completed / failed, and
581
+ only the last two are terminal — which is what ``is_complete``
582
+ says, so a caller need not keep that list itself.
583
+
584
+ Raises
585
+ ------
586
+ PulseAPIError
587
+ If the job does not exist or belongs to another key — status 404.
588
+
589
+ Examples
590
+ --------
591
+ >>> import time
592
+ >>> while not client.validation.get_job_status(job_id)["is_complete"]:
593
+ ... time.sleep(30)
594
+ >>> result = client.validation.get_job(job_id, plot_dir="figures/")
595
+ """
596
+ return self._client._request("GET", f"{JOBS_PATH}/{job_id}/status")
597
+
598
+ def list_jobs(self, limit: int = 50, offset: int = 0) -> dict:
565
599
  """List your validation jobs, newest first.
566
600
 
567
601
  Parameters
568
602
  ----------
569
603
  limit : int, default 50
570
- Maximum number of jobs to return.
604
+ Maximum number of jobs to return. Capped at 200 by the API.
605
+ offset : int, default 0
606
+ Jobs to skip before this page, for paging through more than
607
+ ``limit`` of them.
571
608
 
572
609
  Returns
573
610
  -------
574
611
  dict
575
- Contains ``jobs``, a list of job summaries newest first, and
576
- ``total``, the count before ``limit`` was applied.
612
+ ``{total, limit, offset, jobs}``. ``jobs`` is the page, newest
613
+ first; ``total`` is your full job count rather than the page size,
614
+ so a caller can tell whether more pages exist.
577
615
 
578
616
  Raises
579
617
  ------
@@ -591,5 +629,14 @@ class ValidationResource:
591
629
  >>> done = [j for j in listing["jobs"] if j["status"] == "completed"]
592
630
  >>> newest = done[0]
593
631
  >>> result = client.validation.get_job(newest["job_id"])
632
+
633
+ Page through the rest:
634
+
635
+ >>> seen = len(listing["jobs"])
636
+ >>> while seen < listing["total"]:
637
+ ... page = client.validation.list_jobs(limit=10, offset=seen)
638
+ ... seen += len(page["jobs"])
594
639
  """
595
- return self._client._request("GET", JOBS_PATH, params={"limit": limit})
640
+ return self._client._request(
641
+ "GET", JOBS_PATH, params={"limit": limit, "offset": offset}
642
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simudyne-pulse
3
- Version: 0.7.0.dev17
3
+ Version: 0.7.0.dev18
4
4
  Summary: Python SDK for the Simudyne Pulse synthetic market data API
5
5
  Author-email: Simudyne <support@simudyne.com>
6
6
  License-Expression: MIT
@@ -202,7 +202,7 @@ class TestOneEntryPoint:
202
202
  res.get_job("v1")
203
203
  res.list_jobs(limit=5)
204
204
  assert client.calls[0][:2] == ("GET", "/validation/jobs/v1")
205
- assert client.calls[1][2]["params"] == {"limit": 5}
205
+ assert client.calls[1][2]["params"] == {"limit": 5, "offset": 0}
206
206
 
207
207
 
208
208
  class TestSimulatedFrameShapes:
@@ -406,3 +406,24 @@ class TestBothSourcesAtOnce:
406
406
  sim_ids=["s"] * 13,
407
407
  **IDENTITY,
408
408
  )
409
+
410
+
411
+ class TestPagingAndStatus:
412
+ """Matching pulse-api-pod 1.69.0: a paged list and a lightweight poll."""
413
+
414
+ def test_list_jobs_pages(self):
415
+ client = FakeClient([{"total": 60, "limit": 10, "offset": 10, "jobs": []}])
416
+ ValidationResource(client).list_jobs(limit=10, offset=10)
417
+ assert client.calls[0][2]["params"] == {"limit": 10, "offset": 10}
418
+
419
+ def test_list_jobs_starts_at_the_beginning(self):
420
+ client = FakeClient([{"total": 0, "limit": 50, "offset": 0, "jobs": []}])
421
+ ValidationResource(client).list_jobs()
422
+ assert client.calls[0][2]["params"] == {"limit": 50, "offset": 0}
423
+
424
+ def test_status_asks_the_cheap_endpoint(self):
425
+ """Not get_job: the full result is large and tier-filtered."""
426
+ client = FakeClient([{"job_id": "v1", "status": "running", "is_complete": False}])
427
+ out = ValidationResource(client).get_job_status("v1")
428
+ assert client.calls[0][1] == "/validation/jobs/v1/status"
429
+ assert out["is_complete"] is False