simulo 0.28.2__tar.gz → 0.28.3__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.28.2/src/simulo.egg-info → simulo-0.28.3}/PKG-INFO +1 -1
  2. {simulo-0.28.2 → simulo-0.28.3}/pyproject.toml +1 -1
  3. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/asset_package.py +6 -0
  4. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/cli.py +84 -5
  5. {simulo-0.28.2 → simulo-0.28.3/src/simulo.egg-info}/PKG-INFO +1 -1
  6. {simulo-0.28.2 → simulo-0.28.3}/MANIFEST.in +0 -0
  7. {simulo-0.28.2 → simulo-0.28.3}/PYPI.md +0 -0
  8. {simulo-0.28.2 → simulo-0.28.3}/setup.cfg +0 -0
  9. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/__init__.py +0 -0
  10. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/__init__.py +0 -0
  11. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/_entrypoint.py +0 -0
  12. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/_mounts.py +0 -0
  13. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/_runner.py +0 -0
  14. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/_secure_downloads.py +0 -0
  15. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/app.py +0 -0
  16. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/asset.py +0 -0
  17. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/asset_api.py +0 -0
  18. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/asset_pins.py +0 -0
  19. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/builtin_aliases.py +0 -0
  20. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/bundle.py +0 -0
  21. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/cancel_api.py +0 -0
  22. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/config.py +0 -0
  23. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/credentials.py +0 -0
  24. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/discovery.py +0 -0
  25. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/export_api.py +0 -0
  26. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/export_bundle.py +0 -0
  27. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/facades.py +0 -0
  28. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/http.py +0 -0
  29. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/identity_api.py +0 -0
  30. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/install_samples.py +0 -0
  31. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/jobs_api.py +0 -0
  32. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/learning.py +0 -0
  33. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/login.py +0 -0
  34. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/mode.py +0 -0
  35. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/outputs.py +0 -0
  36. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/packaging.py +0 -0
  37. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/preflight_api.py +0 -0
  38. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/preflight_render.py +0 -0
  39. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/registry.py +0 -0
  40. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/runtime.py +0 -0
  41. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/runtime_display.py +0 -0
  42. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/seed_ref.py +0 -0
  43. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/stub.py +0 -0
  44. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/submit_api.py +0 -0
  45. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/templates/__init__.py +0 -0
  46. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/templates/inference/app.py.tmpl +0 -0
  47. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/templates/inference/simuloignore.tmpl +0 -0
  48. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/templates/scenario/app.py.tmpl +0 -0
  49. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/templates/scenario/simuloignore.tmpl +0 -0
  50. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/templates/training/app.py.tmpl +0 -0
  51. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/templates/training/simuloignore.tmpl +0 -0
  52. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/view_fragment.py +0 -0
  53. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/view_session_api.py +0 -0
  54. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/_client/volume.py +0 -0
  55. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/callbacks.py +0 -0
  56. {simulo-0.28.2 → simulo-0.28.3}/src/simulo/py.typed +0 -0
  57. {simulo-0.28.2 → simulo-0.28.3}/src/simulo.egg-info/SOURCES.txt +0 -0
  58. {simulo-0.28.2 → simulo-0.28.3}/src/simulo.egg-info/dependency_links.txt +0 -0
  59. {simulo-0.28.2 → simulo-0.28.3}/src/simulo.egg-info/entry_points.txt +0 -0
  60. {simulo-0.28.2 → simulo-0.28.3}/src/simulo.egg-info/requires.txt +0 -0
  61. {simulo-0.28.2 → simulo-0.28.3}/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.28.2
3
+ Version: 0.28.3
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.28.2"
7
+ version = "0.28.3"
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"
@@ -82,6 +82,10 @@ from simulo.interfaces.platform.asset_catalog import (
82
82
  #: recipe can never silently collide with today's identities.
83
83
  _CONTENT_DIGEST_SCHEMA = "simulo.asset_content.v1"
84
84
 
85
+ #: Local-cache metadata written after extraction. Like the backend validator,
86
+ #: the packager excludes this exact name only at the package root.
87
+ _ASSET_CACHE_SIDECAR_FILENAME = "simulo.asset.json"
88
+
85
89
  #: USD layer suffixes the inspector parses for references. ``.usdz`` is a
86
90
  #: sealed package — included as an opaque file, valid as an entry only when
87
91
  #: named explicitly via ``--entry`` (never auto-detected).
@@ -516,6 +520,8 @@ def _collect_tree_files(root: Path) -> list[tuple[str, Path]]:
516
520
  if abspath.is_symlink():
517
521
  continue
518
522
  rel = PurePosixPath(abspath.relative_to(root).as_posix())
523
+ if str(rel) == _ASSET_CACHE_SIDECAR_FILENAME:
524
+ continue
519
525
  collected.append((str(rel), abspath))
520
526
  collected.sort(key=lambda item: item[0])
521
527
  return collected
@@ -7222,6 +7222,11 @@ def _cmd_systems(args: argparse.Namespace) -> int:
7222
7222
  #: not by any prose document.
7223
7223
  ASSET_CACHE_ROOT = Path.home() / ".simulo" / "assets"
7224
7224
 
7225
+ #: Sidecar consumed by ``simulo-backend`` when a cached asset path names a
7226
+ #: directory rather than the loadable file inside it. Kept private because the
7227
+ #: thin client must not import the backend package just to share this filename.
7228
+ _ASSET_SIDECAR_FILENAME = "simulo.asset.json"
7229
+
7225
7230
  #: Conservative Windows ``MAX_PATH`` guard (260 chars, minus 1) applied to
7226
7231
  #: every extracted cache path — a defensive REFUSAL, not a guarantee: a
7227
7232
  #: legitimately deep USD tree can still need Windows long-path support
@@ -7350,7 +7355,7 @@ def _validate_asset_archive_member(member: tarfile.TarInfo, dest_root: Path) ->
7350
7355
  return resolved, None
7351
7356
 
7352
7357
 
7353
- def _extract_asset_archive(tar_path: Path, dest_dir: Path) -> None:
7358
+ def _extract_asset_archive(tar_path: Path, dest_dir: Path, *, entry_path: Optional[str] = None) -> Optional[str]:
7354
7359
  """Safely extract *tar_path* (a digest-verified asset archive) into
7355
7360
  *dest_dir* — member-by-member, never ``TarFile.extractall``. This
7356
7361
  function IS the safety filter ``extractall``'s ``filter=`` parameter
@@ -7363,8 +7368,12 @@ def _extract_asset_archive(tar_path: Path, dest_dir: Path) -> None:
7363
7368
  single unsafe member aborts the whole extraction with nothing written,
7364
7369
  never a partial mix of safe and unsafe content.
7365
7370
 
7366
- Raises :class:`AssetExtractionError` on any unsafe/malformed member, a
7367
- non-tar file, or a local filesystem failure.
7371
+ When *entry_path* is provided, it must name one of the archive's regular
7372
+ files through the same containment check as every member. The normalized
7373
+ package-relative path is returned for the platform sidecar.
7374
+
7375
+ Raises :class:`AssetExtractionError` on any unsafe/malformed member or
7376
+ entry, a non-tar file, or a local filesystem failure.
7368
7377
  """
7369
7378
  dest_dir.mkdir(parents=True, exist_ok=True)
7370
7379
  dest_root = dest_dir.resolve()
@@ -7378,6 +7387,20 @@ def _extract_asset_archive(tar_path: Path, dest_dir: Path) -> None:
7378
7387
  f"Refusing to extract {member.name!r}: {reason}. Nothing was written for this asset."
7379
7388
  )
7380
7389
  planned.append((member, target))
7390
+ entry_target: Optional[Path] = None
7391
+ if entry_path is not None:
7392
+ entry_member = tarfile.TarInfo(entry_path)
7393
+ entry_member.type = tarfile.REGTYPE
7394
+ entry_target, reason = _validate_asset_archive_member(entry_member, dest_root)
7395
+ if entry_target is None:
7396
+ raise AssetExtractionError(
7397
+ f"Refusing asset entry path {entry_path!r}: {reason}. Nothing was written for this asset."
7398
+ )
7399
+ if not any(member.isfile() and target == entry_target for member, target in planned):
7400
+ raise AssetExtractionError(
7401
+ f"Refusing asset entry path {entry_path!r}: it does not name a regular file in the "
7402
+ "downloaded archive. Nothing was written for this asset."
7403
+ )
7381
7404
  for member, target in planned:
7382
7405
  if member.isdir():
7383
7406
  target.mkdir(parents=True, exist_ok=True)
@@ -7388,10 +7411,50 @@ def _extract_asset_archive(tar_path: Path, dest_dir: Path) -> None:
7388
7411
  raise AssetExtractionError(f"Archive member {member.name!r} has no readable content.")
7389
7412
  with src, open(target, "wb") as dst:
7390
7413
  shutil.copyfileobj(src, dst)
7414
+ if entry_target is not None:
7415
+ return entry_target.relative_to(dest_root).as_posix()
7391
7416
  except tarfile.TarError as exc:
7392
7417
  raise AssetExtractionError(f"The downloaded archive is not a valid tar file: {exc}") from exc
7393
7418
  except OSError as exc:
7394
7419
  raise AssetExtractionError(f"Could not write the extracted asset to {dest_dir}: {exc}") from exc
7420
+ return None
7421
+
7422
+
7423
+ def _write_asset_cache_sidecar(dest_dir: Path, *, uri: str, entry: str, kind: str, archive_sha256: str) -> None:
7424
+ """Write the backend-compatible entry record after trusted extraction."""
7425
+ sidecar = {
7426
+ "uri": uri,
7427
+ "entry": entry,
7428
+ "kind": kind,
7429
+ "digest": f"sha256:{archive_sha256}",
7430
+ }
7431
+ temp_path: Optional[Path] = None
7432
+ try:
7433
+ dest_root = dest_dir.resolve()
7434
+ with tempfile.NamedTemporaryFile(
7435
+ mode="w",
7436
+ encoding="utf-8",
7437
+ dir=dest_root,
7438
+ prefix=f".{_ASSET_SIDECAR_FILENAME}.",
7439
+ delete=False,
7440
+ ) as temp_file:
7441
+ temp_path = Path(temp_file.name)
7442
+ temp_file.write(json.dumps(sidecar, indent=2) + "\n")
7443
+ # NamedTemporaryFile creates the file owner-only (0600). Give the
7444
+ # record the permissions a plain write would get, so anything that
7445
+ # reads the cache as another user keeps working.
7446
+ if hasattr(os, "fchmod"):
7447
+ current_umask = os.umask(0)
7448
+ os.umask(current_umask)
7449
+ os.fchmod(temp_file.fileno(), 0o666 & ~current_umask)
7450
+ os.replace(temp_path, dest_root / _ASSET_SIDECAR_FILENAME)
7451
+ except OSError as exc:
7452
+ if temp_path is not None:
7453
+ try:
7454
+ temp_path.unlink(missing_ok=True)
7455
+ except OSError:
7456
+ pass
7457
+ raise AssetExtractionError(f"Could not write the asset entry record in {dest_dir}: {exc}") from exc
7395
7458
 
7396
7459
 
7397
7460
  #: Statuses an ``AssetVersionSummary`` can carry while it is still mid-pipeline
@@ -8004,6 +8067,13 @@ def _asset_get_impl(args: argparse.Namespace) -> int:
8004
8067
  expected_sha256 = str(target.get("archive_sha256") or "").lower().removeprefix("sha256:")
8005
8068
  display_ref = f"{parsed.kind}/{parsed.name}:v{version_number}"
8006
8069
 
8070
+ if not entry_path:
8071
+ raise AssetExtractionError(f"{display_ref} has no loadable entry path. Nothing was written.")
8072
+ if "\x00" in entry_path:
8073
+ raise AssetExtractionError(
8074
+ f"Refusing asset entry path {entry_path!r}: NUL characters are refused. Nothing was written for this asset."
8075
+ )
8076
+
8007
8077
  # SECURITY (LOW, PR-8 trio review): a downloadable version with NO
8008
8078
  # recorded digest is refused outright, before any network call — the
8009
8079
  # earlier code treated a missing `archive_sha256` as "skip verification"
@@ -8038,9 +8108,18 @@ def _asset_get_impl(args: argparse.Namespace) -> int:
8038
8108
  with tempfile.TemporaryDirectory(prefix="simulo-asset-get-") as tmp_dir:
8039
8109
  tar_path = Path(tmp_dir) / "package.tar"
8040
8110
  tar_path.write_bytes(data)
8041
- _extract_asset_archive(tar_path, dest_dir)
8111
+ safe_entry_path = _extract_asset_archive(tar_path, dest_dir, entry_path=entry_path)
8112
+ if safe_entry_path is None:
8113
+ raise AssetExtractionError(f"{display_ref} has no loadable entry path. Nothing was written.")
8114
+ _write_asset_cache_sidecar(
8115
+ dest_dir,
8116
+ uri=f"{publisher}/{display_ref}",
8117
+ entry=safe_entry_path,
8118
+ kind=parsed.kind,
8119
+ archive_sha256=actual_sha256,
8120
+ )
8042
8121
 
8043
- echo(f"Downloaded to {dest_dir}/ (entry: {entry_path or '?'})")
8122
+ echo(f"Downloaded to {dest_dir}/ (entry: {sanitize_server_text(entry_path)})")
8044
8123
  if args.json:
8045
8124
  print(
8046
8125
  json.dumps(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simulo
3
- Version: 0.28.2
3
+ Version: 0.28.3
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