simulo 0.31.0__tar.gz → 0.31.2__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 (61) hide show
  1. {simulo-0.31.0/src/simulo.egg-info → simulo-0.31.2}/PKG-INFO +1 -1
  2. {simulo-0.31.0 → simulo-0.31.2}/pyproject.toml +1 -1
  3. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/builtin_aliases.py +8 -15
  4. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/cli.py +78 -27
  5. {simulo-0.31.0 → simulo-0.31.2/src/simulo.egg-info}/PKG-INFO +1 -1
  6. {simulo-0.31.0 → simulo-0.31.2}/MANIFEST.in +0 -0
  7. {simulo-0.31.0 → simulo-0.31.2}/PYPI.md +0 -0
  8. {simulo-0.31.0 → simulo-0.31.2}/setup.cfg +0 -0
  9. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/__init__.py +0 -0
  10. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/__init__.py +0 -0
  11. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/_arguments.py +0 -0
  12. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/_mounts.py +0 -0
  13. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/_runner.py +0 -0
  14. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/_secure_downloads.py +0 -0
  15. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/app.py +0 -0
  16. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/asset.py +0 -0
  17. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/asset_api.py +0 -0
  18. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/asset_package.py +0 -0
  19. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/asset_pins.py +0 -0
  20. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/bundle.py +0 -0
  21. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/cancel_api.py +0 -0
  22. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/config.py +0 -0
  23. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/credentials.py +0 -0
  24. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/discovery.py +0 -0
  25. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/export_api.py +0 -0
  26. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/facades.py +0 -0
  27. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/http.py +0 -0
  28. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/identity_api.py +0 -0
  29. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/install_samples.py +0 -0
  30. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/jobs_api.py +0 -0
  31. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/learning.py +0 -0
  32. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/login.py +0 -0
  33. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/mode.py +0 -0
  34. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/onnx_bundle.py +0 -0
  35. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/outputs.py +0 -0
  36. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/packaging.py +0 -0
  37. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/policies_api.py +0 -0
  38. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/preflight_api.py +0 -0
  39. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/preflight_render.py +0 -0
  40. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/preview_report.py +0 -0
  41. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/registry.py +0 -0
  42. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/runtime.py +0 -0
  43. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/runtime_display.py +0 -0
  44. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/stub.py +0 -0
  45. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/submit_api.py +0 -0
  46. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/templates/__init__.py +0 -0
  47. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/templates/training/play.py.tmpl +0 -0
  48. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/templates/training/preview.py.tmpl +0 -0
  49. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/templates/training/simuloignore.tmpl +0 -0
  50. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/templates/training/task.py.tmpl +0 -0
  51. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/templates/training/train.py.tmpl +0 -0
  52. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/view_fragment.py +0 -0
  53. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/view_session_api.py +0 -0
  54. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/_client/volume.py +0 -0
  55. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/callbacks.py +0 -0
  56. {simulo-0.31.0 → simulo-0.31.2}/src/simulo/py.typed +0 -0
  57. {simulo-0.31.0 → simulo-0.31.2}/src/simulo.egg-info/SOURCES.txt +0 -0
  58. {simulo-0.31.0 → simulo-0.31.2}/src/simulo.egg-info/dependency_links.txt +0 -0
  59. {simulo-0.31.0 → simulo-0.31.2}/src/simulo.egg-info/entry_points.txt +0 -0
  60. {simulo-0.31.0 → simulo-0.31.2}/src/simulo.egg-info/requires.txt +0 -0
  61. {simulo-0.31.0 → simulo-0.31.2}/src/simulo.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simulo
3
- Version: 0.31.0
3
+ Version: 0.31.2
4
4
  Summary: Python SDK and CLI for cloud-native robotics simulation, reinforcement learning, and robot policy training on managed GPUs.
5
5
  Author-email: Simulo Team <team@simulo.ai>
6
6
  License: BSD-3-Clause
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "simulo"
7
- version = "0.31.0"
7
+ version = "0.31.2"
8
8
  description = "Python SDK and CLI for cloud-native robotics simulation, reinforcement learning, and robot policy training on managed GPUs."
9
9
  readme = "PYPI.md"
10
10
  requires-python = ">=3.11"
@@ -9,21 +9,12 @@ equivalent has been seeded — pointing the user at the ``simulo/robot/...`` idi
9
9
  Two pieces:
10
10
 
11
11
  * :data:`BUILTIN_ASSET_ALIASES` — the ``<builtin-name> -> <catalog-ref>`` table.
12
- **PR-13 populated this table for real**, from a live GPU seeding run
13
- (``scripts/seed-global-catalog.py``, run against a real control plane + a
14
- real worker on the local 3090): every entry below is a builtin whose USD
15
- closure localized AND whose real cloud validation (settle + actuation
16
- probes) actually PASSED. A ``BUILTIN_ROBOT_CONFIGS`` name that isn't here
17
- (``cartpole``, ``franka``, ``humanoid``, ``unitree_go2``, ``ur10`` as of
18
- PR-13) genuinely has NOT been seeded — it failed real validation or could
19
- not be localized into a self-contained package — so the scan below finds
20
- nothing for it and NO notice is printed; that ``builtin://`` ref still
21
- resolves through the legacy path. Do not add entries here by hand: a name
22
- belongs in this table only once ``scripts/seed-global-catalog.py`` has
23
- actually published + validated its ``simulo/robot/<name>`` catalog entry
24
- (the script's own ``update_alias_table`` appends here, additively, keyed
25
- on the SAME name it published under — see that script for the current
26
- seed report), or the notice would send users to a ref that 404s.
12
+ Every entry was published by the catalog seeding script and passed real
13
+ validation. A builtin that is not here has not, so the scan below prints no
14
+ notice for it and its ``builtin://`` reference still resolves the old way.
15
+ Add entries only after a real publish and validation (the seeding script
16
+ appends them itself); otherwise the notice would send users to a catalog
17
+ reference that does not exist.
27
18
 
28
19
  * :func:`scan_source_for_builtin_aliases` — a textual scan of the packaged source
29
20
  files at submit, returning every ``(builtin_name, catalog_ref)`` pair whose
@@ -45,7 +36,9 @@ from typing import Dict, List, Tuple
45
36
  BUILTIN_ASSET_ALIASES: Dict[str, str] = {
46
37
  "anymal": "simulo/robot/anymal-c",
47
38
  "anymal_c": "simulo/robot/anymal-c",
39
+ "cartpole": "simulo/robot/cartpole",
48
40
  "h1": "simulo/robot/unitree-h1",
41
+ "humanoid": "simulo/robot/humanoid",
49
42
  "jetbot": "simulo/robot/jetbot",
50
43
  "pick_and_place": "simulo/robot/pick-and-place",
51
44
  "unitree_h1": "simulo/robot/unitree-h1",
@@ -168,7 +168,7 @@ from simulo._client.asset_package import (
168
168
  slugify_name,
169
169
  validate_asset_name,
170
170
  )
171
- from simulo._client.asset_pins import collect_asset_refs
171
+ from simulo._client.asset_pins import collect_asset_refs, resolve_and_pin_assets
172
172
  from simulo._client.builtin_aliases import format_builtin_deprecation_notice, scan_source_for_builtin_aliases
173
173
  from simulo._client.bundle import build_manifest
174
174
  from simulo._client.cancel_api import CancelApiClient, CancelApiHTTPError
@@ -673,6 +673,14 @@ def _resolve_client_context() -> tuple[str, Optional[str]]:
673
673
  return base_url, token
674
674
 
675
675
 
676
+ def _client_with_fresh_token(client: JobsApiClient) -> JobsApiClient:
677
+ """Return a client with current stored credentials, preserving its target."""
678
+ creds = credentials.try_get_valid_credentials(base_url=client.base_url)
679
+ if creds is None:
680
+ return client
681
+ return JobsApiClient(client.base_url, token=str(creds["access_token"]))
682
+
683
+
676
684
  def _is_uuid_text(value: Any) -> bool:
677
685
  try:
678
686
  UUID(str(value))
@@ -1470,10 +1478,16 @@ def _follow_and_report(handle: JobHandle, app_path: str) -> int:
1470
1478
  job_ref = str(getattr(handle, "public_id", None) or handle.job_id)
1471
1479
  exit_code = _drain_logs(client, job_ref, follow=True, scope=JOB_SCOPE_MINE)
1472
1480
  if handle.job_type is JobType.PREVIEW:
1481
+ client = _client_with_fresh_token(client)
1473
1482
  return _report_preview(client, job_ref, display_id, app_path, fallback_exit_code=exit_code)
1474
1483
  if exit_code == 0:
1475
1484
  if handle.job_type is JobType.TRAIN:
1476
- _print_training_next_steps(handle, app_path)
1485
+ client = _client_with_fresh_token(client)
1486
+ _print_training_next_steps(
1487
+ PoliciesApiClient(client.base_url, token=client._token),
1488
+ handle,
1489
+ app_path,
1490
+ )
1477
1491
  elif handle.job_type is JobType.PLAY:
1478
1492
  print(f"Next: simulo recordings {display_id}")
1479
1493
  return exit_code
@@ -1566,12 +1580,11 @@ def _checkpoint_iteration(handle: JobHandle, checkpoint_ref: str) -> Optional[in
1566
1580
  return None
1567
1581
 
1568
1582
 
1569
- def _print_training_next_steps(handle: JobHandle, app_path: str) -> None:
1583
+ def _print_training_next_steps(client: PoliciesApiClient, handle: JobHandle, app_path: str) -> None:
1570
1584
  """Print the sealed policy's checkpoints and the three canonical next steps."""
1571
- assert handle.base_url is not None
1572
1585
  policy_id = str(policy_id_for_job(_handle_display_id(handle)))
1573
1586
  try:
1574
- record = PoliciesApiClient(handle.base_url, token=handle.token).get_policy(policy_id)
1587
+ record = client.get_policy(policy_id)
1575
1588
  except http.HttpError:
1576
1589
  return
1577
1590
  rendered: list[str] = []
@@ -1693,8 +1706,8 @@ def _render_preflight_outcome(app_file: Path, outcome: PreflightOutcome) -> int:
1693
1706
 
1694
1707
 
1695
1708
  def _cmd_prepare(args: argparse.Namespace) -> int:
1696
- """``simulo prepare <app>`` — build the manifest and ask the platform
1697
- whether this app would submit cleanly, before uploading a single byte.
1709
+ """``simulo prepare <app>`` — build the manifest, resolve its assets when
1710
+ authenticated, and ask whether the app would submit cleanly before upload.
1698
1711
 
1699
1712
  Never uploads a package, never creates a job — safe to run as often as
1700
1713
  you like before ``simulo run``. It DOES import your app file to discover
@@ -1710,22 +1723,46 @@ def _cmd_prepare(args: argparse.Namespace) -> int:
1710
1723
  if not app_file.is_file():
1711
1724
  raise SystemExit(f"No such app file: {app_file}")
1712
1725
  project_root = app_file.parent
1713
- app = discover(app_file)
1714
- if not isinstance(app, App):
1715
- raise TypeError(f"Discovered object is not a simulo.App: {app!r}")
1716
- _validate_app_job_file(app, app_file)
1717
- # Builds the manifest only — no source tree copy, no tar, no upload. The
1718
- # #285 platform-pinned-package denylist (``build_manifest``'s own
1719
- # ``SystemExit``) still applies here exactly as it does at `simulo run`;
1720
- # it is a DIFFERENT, client-side-only mechanism from the preflight HTTP
1721
- # call below and must never be confused with a preflight finding (see
1722
- # the preflight API/rendering separation verified by the CLI tests).
1723
- manifest = build_manifest(app, app_file, project_root)
1726
+ with capture_constructed_assets() as captured_assets:
1727
+ app = discover(app_file)
1728
+ if not isinstance(app, App):
1729
+ raise TypeError(f"Discovered object is not a simulo.App: {app!r}")
1730
+ _validate_app_job_file(app, app_file)
1731
+ # Builds the manifest only — no source tree copy, no tar, no upload. The
1732
+ # #285 platform-pinned-package denylist (``build_manifest``'s own
1733
+ # ``SystemExit``) still applies here exactly as it does at `simulo run`;
1734
+ # it is a DIFFERENT, client-side-only mechanism from the preflight HTTP
1735
+ # call below and must never be confused with a preflight finding (see
1736
+ # the preflight API/rendering separation verified by the CLI tests).
1737
+ manifest = build_manifest(app, app_file, project_root, extra_assets=captured_assets)
1724
1738
  manifest_data = json.loads(manifest.to_json())
1725
1739
  asset_refs = collect_asset_refs(manifest_data)
1726
- base_url, token = _resolve_client_context()
1740
+ try:
1741
+ base_url, token = _resolve_client_context()
1742
+ except credentials.TokenRefreshError as exc:
1743
+ if not _caused_by(exc, http.HttpUnavailable):
1744
+ raise
1745
+ # Asset resolution and preflight are optional checks. Preserve a
1746
+ # rejected refresh as a hard authentication error, while allowing a
1747
+ # network-only refresh failure to use the saved target without sending
1748
+ # the expired bearer.
1749
+ stale_creds = credentials.load_credentials()
1750
+ base_url, token = config.resolve_base_url(stale_creds), None
1751
+ resolved_assets: Optional[list[dict[str, str]]] = [] if not asset_refs else None
1752
+ if asset_refs:
1753
+ if token is None:
1754
+ print("Asset references were not checked.", file=sys.stderr)
1755
+ else:
1756
+ try:
1757
+ resolved_assets = resolve_and_pin_assets(SubmitApiClient(base_url, token=token), asset_refs)
1758
+ except http.HttpUnavailable:
1759
+ print("Asset references were not checked.", file=sys.stderr)
1760
+ except http.HttpHTTPError as exc:
1761
+ if exc.status != 404 and not 500 <= exc.status <= 599:
1762
+ raise
1763
+ print("Asset references were not checked.", file=sys.stderr)
1727
1764
  client = PreflightApiClient(base_url, token=token)
1728
- outcome = client.evaluate(manifest, assets=[] if not asset_refs else None)
1765
+ outcome = client.evaluate(manifest, assets=resolved_assets)
1729
1766
  return _render_preflight_outcome(app_file, outcome)
1730
1767
 
1731
1768
 
@@ -2386,6 +2423,7 @@ def _wait_for_job(client: JobsApiClient, job_id: str, *, scope: str = JOB_SCOPE_
2386
2423
  """Poll until ``job_id`` exists — lets a user arm the tail BEFORE the job is created."""
2387
2424
  waiting_announced = False
2388
2425
  while True:
2426
+ client = _client_with_fresh_token(client)
2389
2427
  try:
2390
2428
  return client.get_job(job_id, scope=scope)
2391
2429
  except JobsApiHTTPError as exc:
@@ -2414,6 +2452,7 @@ def _wait_for_latest_job(client: JobsApiClient) -> dict[str, Any]:
2414
2452
  """
2415
2453
  waiting_announced = False
2416
2454
  while True:
2455
+ client = _client_with_fresh_token(client)
2417
2456
  try:
2418
2457
  latest = client.get_latest_job()
2419
2458
  except JobsApiHTTPError as exc:
@@ -2608,6 +2647,8 @@ def _drain_logs(
2608
2647
  post-``simulo run`` default follow — one follow loop, one
2609
2648
  exit-code-mirrors-outcome rule.
2610
2649
  """
2650
+ if follow:
2651
+ client = _client_with_fresh_token(client)
2611
2652
  offset = 0
2612
2653
  if tail_lines is not None:
2613
2654
  try:
@@ -2633,6 +2674,8 @@ def _drain_logs(
2633
2674
  # Not honored (old server): the loop below simply continues the full
2634
2675
  # history from where the server's offset-0 answer left off.
2635
2676
  while True:
2677
+ if follow:
2678
+ client = _client_with_fresh_token(client)
2636
2679
  try:
2637
2680
  chunk, offset, status, _ = client.get_log_chunk(job_id, offset, scope=scope)
2638
2681
  except JobsApiHTTPError as exc:
@@ -2770,9 +2813,9 @@ def _cmd_result(args: argparse.Namespace) -> int:
2770
2813
  announced on stderr.
2771
2814
  A job whose run announced typed outputs gets a second
2772
2815
  stderr line, ``⚠ 1 anomaly (non_finite_reward)`` (:func:`_outputs_flag_summary`)
2773
- — the result JSON itself is untouched (``stats["anomaly_captures"]`` stays
2774
- byte-identical; a byte stream is an output, a JSON scalar is a metric,
2775
- surfaced together but never merged). Both lines print to
2816
+ while built-in training results omit anomaly-capture paths. Those paths
2817
+ are exposed through the recordings and outputs commands instead. Both
2818
+ lines print to
2776
2819
  stderr so stdout stays pure result JSON for pipelines. Best-effort: for
2777
2820
  an explicit JOB_ID this needs one extra ``get_job`` call, and a failure
2778
2821
  there (e.g. an older control plane) skips both lines rather than failing a
@@ -2806,6 +2849,13 @@ def _cmd_result(args: argparse.Namespace) -> int:
2806
2849
  if exc.status == 404:
2807
2850
  display_ref = _human_job_ref(job_id)
2808
2851
  hint_ref = display_ref if display_ref.startswith("job_") else "<JOB_ID>"
2852
+ if (job_record or {}).get("job_type") == "export":
2853
+ print(
2854
+ f"{_human_job_subject(job_id)} produces a bundle rather than a result. "
2855
+ f"List its outputs with `simulo outputs {hint_ref}`.",
2856
+ file=sys.stderr,
2857
+ )
2858
+ return 1
2809
2859
  print(
2810
2860
  f"No result for {display_ref}: {_safe_cli_error_text(exc.message)}\n"
2811
2861
  "A result exists once the job has finished and returned one — check `simulo jobs` "
@@ -8085,10 +8135,11 @@ def _build_parser() -> argparse.ArgumentParser:
8085
8135
  prepare = sub.add_parser(
8086
8136
  "prepare",
8087
8137
  help=(
8088
- "Check a Simulo app against the platform BEFORE submitting it — builds the manifest "
8089
- "and asks the platform whether it would work (pip version conflicts, invalid job "
8090
- "timeouts, and more). Imports your app file, same as `simulo run`. Never uploads "
8091
- "anything and never creates a job."
8138
+ "Check a Simulo app against the platform BEFORE submitting it. Builds the manifest "
8139
+ "and, when signed in, resolves its catalog asset references before asking whether it "
8140
+ "would work (pip version conflicts, invalid job timeouts, and more). Signed-out or "
8141
+ "unreachable asset checks are reported and skipped. Imports your app file, same as "
8142
+ "`simulo run`. Never uploads anything and never creates a job."
8092
8143
  ),
8093
8144
  )
8094
8145
  prepare.add_argument("app_file", help="Path to the Python file declaring the simulo.App.")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simulo
3
- Version: 0.31.0
3
+ Version: 0.31.2
4
4
  Summary: Python SDK and CLI for cloud-native robotics simulation, reinforcement learning, and robot policy training on managed GPUs.
5
5
  Author-email: Simulo Team <team@simulo.ai>
6
6
  License: BSD-3-Clause
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes