sequence-base 0.4.24__tar.gz → 0.4.26__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.
- {sequence_base-0.4.24 → sequence_base-0.4.26}/PKG-INFO +2 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/pyproject.toml +2 -2
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/__init__.py +5 -2
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/constants/version.py +2 -1
- sequence_base-0.4.26/sequence_base/construct.py +103 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/contract/__init__.py +5 -2
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/contract/benchmarks.py +6 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/contract/deployments.py +6 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/contract/policy.py +133 -3
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schema_export.py +4 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/ActRequest.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/ActResponse.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/ActionChunk.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/ActionContract.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/ActionStep.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/AdapterPair.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/AddLocal.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/Autoscaler.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/BenchmarkConditionSpec.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/BenchmarkRegisterRequest.json +104 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/BenchmarkSpec.json +93 -2
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/BenchmarkStatusResponse.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/BootstrapResponse.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/Camera.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/CodeBundle.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/Concurrency.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/Conditions.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/ConnectRequest.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/ConnectResponse.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/DeploymentAccepted.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/DeploymentPolicy.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/DeploymentRequest.json +104 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/DeploymentStatusResponse.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/Embodiment.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/ErrorBody.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/ErrorCodes.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/Estimate.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/EvalDryRunResponse.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/EvalReport.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/EvalRequest.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/EvalResponse.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/ImageFrame.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/ImageSpec.json +92 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/ImageStepAddLocal.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/ImageStepApt.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/ImageStepEnv.json +1 -1
- sequence_base-0.4.26/sequence_base/schemas/ImageStepProject.json +91 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/ImageStepRun.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/ImageStepTemplateFiles.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/ImageStepUvPip.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/InferParam.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/Lease.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/MetricAgg.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/Observation.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/ObservationContract.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/PolicyRegistration.json +139 -3
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/PolicySpec.json +92 -1
- sequence_base-0.4.26/sequence_base/schemas/Project.json +76 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/Proprioception.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/RenderSpec.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/ReplaySpec.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/Resources.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/RolloutResult.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/SecretCreateRequest.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/SecretInfo.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/SecretRef.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/SeqFlags.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/Step.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/TaskAgg.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/TemplateFiles.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/Timeouts.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/UploadTarget.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/Variant.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/VolumeFile.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/VolumeInfo.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/VolumeMount.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/VolumeRef.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/VolumeUploadRequest.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/VolumeUploadResponse.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/WarmingError.json +1 -1
- {sequence_base-0.4.24 → sequence_base-0.4.26}/.gitignore +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/README.md +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/auth.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/codec/__init__.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/codec/image.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/constants/__init__.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/constants/cards.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/constants/headers.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/constants/limits.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/contract/accounts.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/contract/act.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/contract/adapters.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/contract/connect.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/contract/errors.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/contract/eval.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/contract/flags.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/contract/io.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/contract/templates.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/contract/volumes.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/contract/wire.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/errors/__init__.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/errors/transport.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/msgpack_io.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/obs/__init__.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/obs/log.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/obs/rid.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/presets/__init__.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/presets/frames.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/presets/robot_arm.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/telemetry/__init__.py +0 -0
- {sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/telemetry/events.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sequence-base
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.26
|
|
4
4
|
Summary: Shared foundation for the Sequences platform — wire contract, correlation, error taxonomy, constants. Used by the robot SDK, gateway, and container harness.
|
|
5
5
|
Project-URL: Changelog, https://generalsequences.com/docs/changelog/
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -12,3 +12,4 @@ Provides-Extra: test
|
|
|
12
12
|
Requires-Dist: hypothesis>=6; extra == 'test'
|
|
13
13
|
Requires-Dist: jsonschema>=4; extra == 'test'
|
|
14
14
|
Requires-Dist: pytest; extra == 'test'
|
|
15
|
+
Requires-Dist: pyyaml; extra == 'test'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sequence-base"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.26"
|
|
4
4
|
description = "Shared foundation for the Sequences platform — wire contract, correlation, error taxonomy, constants. Used by the robot SDK, gateway, and container harness."
|
|
5
5
|
requires-python = ">=3.10"
|
|
6
6
|
dependencies = ["pydantic>=2.0"]
|
|
@@ -10,7 +10,7 @@ Changelog = "https://generalsequences.com/docs/changelog/"
|
|
|
10
10
|
|
|
11
11
|
[project.optional-dependencies]
|
|
12
12
|
codec = ["pillow>=10", "numpy>=1.26"]
|
|
13
|
-
test = ["pytest", "jsonschema>=4", "hypothesis>=6"]
|
|
13
|
+
test = ["pytest", "jsonschema>=4", "hypothesis>=6", "pyyaml"]
|
|
14
14
|
|
|
15
15
|
[build-system]
|
|
16
16
|
requires = ["hatchling"]
|
|
@@ -21,6 +21,8 @@ from sequence_base.contract import (
|
|
|
21
21
|
FROZEN_PATH,
|
|
22
22
|
TemplateFiles,
|
|
23
23
|
ImageStepTemplateFiles,
|
|
24
|
+
Project,
|
|
25
|
+
ImageStepProject,
|
|
24
26
|
Camera,
|
|
25
27
|
Embodiment,
|
|
26
28
|
ObservationContract,
|
|
@@ -76,6 +78,7 @@ from sequence_base.contract import (
|
|
|
76
78
|
Lease,
|
|
77
79
|
MetricAgg,
|
|
78
80
|
Observation,
|
|
81
|
+
PolicyConstructor,
|
|
79
82
|
PolicyRegistration,
|
|
80
83
|
PolicySpec,
|
|
81
84
|
Proprioception,
|
|
@@ -130,9 +133,9 @@ __all__ = [
|
|
|
130
133
|
"ActRequest", "ActResponse",
|
|
131
134
|
|
|
132
135
|
"SeqFlags", "ImageStepUvPip", "ImageStepApt", "ImageStepEnv", "ImageStepRun", "AddLocal", "ImageStepAddLocal",
|
|
133
|
-
"FROZEN_PATH", "TemplateFiles", "ImageStepTemplateFiles", "ImageStep", "ImageSpec",
|
|
136
|
+
"FROZEN_PATH", "TemplateFiles", "ImageStepTemplateFiles", "Project", "ImageStepProject", "ImageStep", "ImageSpec",
|
|
134
137
|
"VolumeMount", "VolumeRef", "Concurrency", "ActionContract", "Camera", "ObservationContract", "Embodiment",
|
|
135
|
-
"PolicySpec", "PolicyRegistration",
|
|
138
|
+
"PolicySpec", "PolicyRegistration", "PolicyConstructor",
|
|
136
139
|
|
|
137
140
|
"MAX_CODE_BUNDLE_BYTES", "CodeBundle",
|
|
138
141
|
"Resources", "Autoscaler", "Timeouts", "DeploymentPolicy", "InferParam", "DeploymentRequest",
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
How a policy class that names its own methods is built (PolicyRegistration.constructor): its constructor - or the
|
|
3
|
+
factory it names - called with the arguments its decorator was given, once two forms in them are filled in.
|
|
4
|
+
|
|
5
|
+
In one sentence: a value {"$config": path, "$set": {...}} becomes the YAML or JSON file at `path` - from the folder of
|
|
6
|
+
the class's file - read where the policy runs, with `$set`'s keys laid over its top level → in every string,
|
|
7
|
+
"{weights}" becomes the weights directory and "{weights.<part>}" a named part's → `cls.<make>(*args, **kwargs)`, or
|
|
8
|
+
`cls(*args, **kwargs)` without a factory.
|
|
9
|
+
|
|
10
|
+
WHY a config file is read where the policy runs: it is part of the project (XPolicyLab's deploy.yml beside its
|
|
11
|
+
model.py) and goes up with it, so one copy - the one the policy runs with - is read, and "{weights}" in it means the
|
|
12
|
+
worker's directory.
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import re
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
__all__ = ["CONFIG_KEY", "SET_KEY", "ConstructError", "build", "config", "expand", "is_config", "read_config"]
|
|
23
|
+
|
|
24
|
+
CONFIG_KEY, SET_KEY = "$config", "$set"
|
|
25
|
+
_WEIGHTS = re.compile(r"\{weights(?:\.([A-Za-z_][A-Za-z0-9_-]*))?\}")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ConstructError(ValueError):
|
|
29
|
+
"""Arguments that cannot be filled in: a config file missing, unreadable or of another kind, or a weights
|
|
30
|
+
placeholder with no weights - or no such part - to fill it."""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def config(path: str, **overrides: Any) -> dict:
|
|
34
|
+
"""A config file's contents with `overrides` laid over its top level, as the worker reads it (`seq.config`)."""
|
|
35
|
+
if not isinstance(path, str) or not path:
|
|
36
|
+
raise ConstructError("a config file is a path, from the folder of the class's file")
|
|
37
|
+
if os.path.isabs(path):
|
|
38
|
+
raise ConstructError(f"{path}: give a config file's path from the folder of the class's file - the policy runs "
|
|
39
|
+
"it where the upload puts it")
|
|
40
|
+
return {CONFIG_KEY: path, SET_KEY: dict(overrides)}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def is_config(value: Any) -> bool:
|
|
44
|
+
return isinstance(value, dict) and set(value) == {CONFIG_KEY, SET_KEY} and isinstance(value[CONFIG_KEY], str)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def read_config(path: str) -> Any:
|
|
48
|
+
"""The YAML (.yml, .yaml) or JSON (.json) file at `path`."""
|
|
49
|
+
kind = os.path.splitext(path)[1].lower()
|
|
50
|
+
if kind not in (".json", ".yml", ".yaml"):
|
|
51
|
+
raise ConstructError(f"{path}: a config file is .json, .yml or .yaml")
|
|
52
|
+
if not os.path.isfile(path):
|
|
53
|
+
raise ConstructError(f"{path}: no such config file")
|
|
54
|
+
with open(path, encoding="utf-8") as f:
|
|
55
|
+
if kind == ".json":
|
|
56
|
+
return json.load(f)
|
|
57
|
+
try:
|
|
58
|
+
import yaml
|
|
59
|
+
except ImportError:
|
|
60
|
+
raise ConstructError(f"{path}: reading YAML needs pyyaml") from None
|
|
61
|
+
return yaml.safe_load(f)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def expand(value: Any, *, base_dir: str, weights: Any) -> Any:
|
|
65
|
+
"""`value` with its config files read (from `base_dir`) and its weights placeholders filled from `weights` - the
|
|
66
|
+
weights directory, or {part: directory} for named parts, or None for a policy without weights."""
|
|
67
|
+
if is_config(value):
|
|
68
|
+
loaded = read_config(os.path.join(base_dir, value[CONFIG_KEY]))
|
|
69
|
+
if value[SET_KEY] and not isinstance(loaded, dict):
|
|
70
|
+
raise ConstructError(f"{value[CONFIG_KEY]} holds a {type(loaded).__name__}, which has no keys to set")
|
|
71
|
+
merged = {**loaded, **value[SET_KEY]} if isinstance(loaded, dict) else loaded
|
|
72
|
+
return expand(merged, base_dir=base_dir, weights=weights)
|
|
73
|
+
if isinstance(value, dict):
|
|
74
|
+
return {k: expand(v, base_dir=base_dir, weights=weights) for k, v in value.items()}
|
|
75
|
+
if isinstance(value, (list, tuple)):
|
|
76
|
+
return [expand(v, base_dir=base_dir, weights=weights) for v in value]
|
|
77
|
+
if isinstance(value, str):
|
|
78
|
+
return _WEIGHTS.sub(lambda m: _weights_of(m.group(1), weights), value)
|
|
79
|
+
return value
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _weights_of(part: str | None, weights: Any) -> str:
|
|
83
|
+
if weights is None:
|
|
84
|
+
raise ConstructError("{weights} names the weights directory, and this policy declares no weights=")
|
|
85
|
+
if isinstance(weights, dict):
|
|
86
|
+
if part is None:
|
|
87
|
+
raise ConstructError(f"its weights come in named parts ({', '.join(sorted(weights))}): name one, "
|
|
88
|
+
f"{{weights.<part>}}")
|
|
89
|
+
if part not in weights:
|
|
90
|
+
raise ConstructError(f"{{weights.{part}}}: its weights have no part {part!r} "
|
|
91
|
+
f"({', '.join(sorted(weights))})")
|
|
92
|
+
return str(weights[part])
|
|
93
|
+
if part is not None:
|
|
94
|
+
raise ConstructError(f"{{weights.{part}}} names a part, and its weights come whole: {{weights}}")
|
|
95
|
+
return str(weights)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def build(cls: type, constructor: Any, *, base_dir: str, weights: Any) -> Any:
|
|
99
|
+
"""The instance `constructor` (a PolicyConstructor) makes of `cls`: `cls.<make>(*args, **kwargs)` or `cls(...)`."""
|
|
100
|
+
args = expand(list(constructor.args), base_dir=base_dir, weights=weights)
|
|
101
|
+
kwargs = expand(dict(constructor.kwargs), base_dir=base_dir, weights=weights)
|
|
102
|
+
make = getattr(cls, constructor.make) if constructor.make else cls
|
|
103
|
+
return make(*args, **kwargs)
|
|
@@ -34,6 +34,9 @@ from sequence_base.contract.policy import (
|
|
|
34
34
|
FROZEN_PATH,
|
|
35
35
|
TemplateFiles,
|
|
36
36
|
ImageStepTemplateFiles,
|
|
37
|
+
ImageStepProject,
|
|
38
|
+
Project,
|
|
39
|
+
PolicyConstructor,
|
|
37
40
|
PolicyRegistration,
|
|
38
41
|
PolicySpec,
|
|
39
42
|
VolumeMount,
|
|
@@ -119,9 +122,9 @@ __all__ = [
|
|
|
119
122
|
|
|
120
123
|
"SeqFlags",
|
|
121
124
|
"ImageStepUvPip", "ImageStepApt", "ImageStepEnv", "ImageStepRun", "AddLocal", "ImageStepAddLocal",
|
|
122
|
-
"TemplateFiles", "ImageStepTemplateFiles", "FROZEN_PATH", "ImageStep", "ImageSpec",
|
|
125
|
+
"TemplateFiles", "ImageStepTemplateFiles", "Project", "ImageStepProject", "FROZEN_PATH", "ImageStep", "ImageSpec",
|
|
123
126
|
"VolumeMount", "VolumeRef", "Concurrency", "ActionContract", "Camera", "ObservationContract", "Embodiment",
|
|
124
|
-
"PolicySpec", "PolicyRegistration",
|
|
127
|
+
"PolicySpec", "PolicyRegistration", "PolicyConstructor",
|
|
125
128
|
|
|
126
129
|
"MAX_CODE_BUNDLE_BYTES", "CodeBundle",
|
|
127
130
|
"Resources", "Autoscaler", "Timeouts", "DeploymentPolicy", "InferParam", "DeploymentRequest",
|
|
@@ -296,6 +296,12 @@ class DeploymentRequest(BaseModel):
|
|
|
296
296
|
weights_name: str | None = None
|
|
297
297
|
extra_weights: dict[str, VolumeMount] | None = None
|
|
298
298
|
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
freeze: bool | None = None
|
|
304
|
+
|
|
299
305
|
@field_validator("name")
|
|
300
306
|
@classmethod
|
|
301
307
|
def _name_is_a_slug(cls, v: str) -> str:
|
|
@@ -10,7 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
import re
|
|
12
12
|
|
|
13
|
-
from typing import Literal
|
|
13
|
+
from typing import Any, Literal
|
|
14
14
|
|
|
15
15
|
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
|
16
16
|
|
|
@@ -35,6 +35,8 @@ __all__ = [
|
|
|
35
35
|
"FROZEN_PATH",
|
|
36
36
|
"TemplateFiles",
|
|
37
37
|
"ImageStepTemplateFiles",
|
|
38
|
+
"Project",
|
|
39
|
+
"ImageStepProject",
|
|
38
40
|
"ImageStep",
|
|
39
41
|
"ImageSpec",
|
|
40
42
|
"VolumeMount",
|
|
@@ -126,7 +128,101 @@ class ImageStepTemplateFiles(BaseModel):
|
|
|
126
128
|
template_files: TemplateFiles
|
|
127
129
|
|
|
128
130
|
|
|
129
|
-
|
|
131
|
+
def _inside_bundle(what: str, v: str) -> str:
|
|
132
|
+
"""A path inside the deploy's code bundle: relative, '/'-separated, never above it."""
|
|
133
|
+
if v.startswith("/") or "\\" in v or ".." in v.split("/"):
|
|
134
|
+
raise ValueError(f"project {what} {v!r} must be a relative path inside the code bundle, without '..'")
|
|
135
|
+
return v
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
_DIST_NAME = r"^[A-Za-z0-9]([A-Za-z0-9._-]{0,126}[A-Za-z0-9])?$"
|
|
140
|
+
_DIST_VERSION = r"^[A-Za-z0-9.+!_-]{1,64}$"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class Project(BaseModel):
|
|
144
|
+
"""A project's own dependency files, which the image installs from (`seq.Image.from_project`, or a policy that
|
|
145
|
+
declares no image and whose project has them): each read from the deploy's code bundle. The platform turns them into
|
|
146
|
+
the install when the image builds - for Linux and the worker's card, whatever machine the author wrote them on: the
|
|
147
|
+
lock's exact versions, else the declared ones held to the versions the author's machine has (`pins`), the local
|
|
148
|
+
packages the code imports with their own, less `exclude`, then `extra`.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
model_config = ConfigDict(extra="forbid")
|
|
152
|
+
|
|
153
|
+
root: str = Field("", max_length=512, description="The project's folder inside the code bundle ('' its root)")
|
|
154
|
+
lock: str | None = Field(None, min_length=1, max_length=512,
|
|
155
|
+
description="Its lock file, relative to root: uv.lock, poetry.lock, pdm.lock, Pipfile.lock "
|
|
156
|
+
"or a requirements file whose every line pins a version")
|
|
157
|
+
declared: list[str] = Field(default_factory=list, max_length=8,
|
|
158
|
+
description="Its declared dependency files, relative to root: pyproject.toml, "
|
|
159
|
+
"requirements*.txt, setup.cfg, setup.py")
|
|
160
|
+
packages: list[str] = Field(default_factory=list, max_length=64,
|
|
161
|
+
description="Folders inside the code bundle, each a package of its own (a pyproject.toml "
|
|
162
|
+
"or setup.py) the code imports, installed with its dependencies")
|
|
163
|
+
pins: dict[str, str] = Field(default_factory=dict, max_length=4096,
|
|
164
|
+
description="The version of each package the author's machine has, which an install "
|
|
165
|
+
"from declared ranges keeps")
|
|
166
|
+
exclude: list[str] = Field(default_factory=list, max_length=256, description="Packages left out, by name")
|
|
167
|
+
extra: list[str] = Field(default_factory=list, max_length=64,
|
|
168
|
+
description="Requirements added after the project's own: 'name==version', or "
|
|
169
|
+
"'name @ https://…' for a wheel")
|
|
170
|
+
|
|
171
|
+
@field_validator("root")
|
|
172
|
+
@classmethod
|
|
173
|
+
def _root_inside(cls, v: str) -> str:
|
|
174
|
+
return _inside_bundle("root", v) if v else v
|
|
175
|
+
|
|
176
|
+
@field_validator("lock")
|
|
177
|
+
@classmethod
|
|
178
|
+
def _lock_inside(cls, v: str | None) -> str | None:
|
|
179
|
+
return _inside_bundle("lock", v) if v is not None else v
|
|
180
|
+
|
|
181
|
+
@field_validator("declared", "packages")
|
|
182
|
+
@classmethod
|
|
183
|
+
def _paths_inside(cls, v: list[str]) -> list[str]:
|
|
184
|
+
for p in v:
|
|
185
|
+
if not p or len(p) > 512:
|
|
186
|
+
raise ValueError("a project file or package path is 1 to 512 characters")
|
|
187
|
+
_inside_bundle("path", p)
|
|
188
|
+
return v
|
|
189
|
+
|
|
190
|
+
@field_validator("pins")
|
|
191
|
+
@classmethod
|
|
192
|
+
def _pins_well_formed(cls, v: dict[str, str]) -> dict[str, str]:
|
|
193
|
+
import re
|
|
194
|
+
|
|
195
|
+
for name, version in v.items():
|
|
196
|
+
if not re.match(_DIST_NAME, name) or not re.match(_DIST_VERSION, version or ""):
|
|
197
|
+
raise ValueError(f"project pin {name!r}=={version!r} is not a package name and a version")
|
|
198
|
+
return v
|
|
199
|
+
|
|
200
|
+
@field_validator("exclude")
|
|
201
|
+
@classmethod
|
|
202
|
+
def _names(cls, v: list[str]) -> list[str]:
|
|
203
|
+
import re
|
|
204
|
+
|
|
205
|
+
bad = [n for n in v if not re.match(_DIST_NAME, n)]
|
|
206
|
+
if bad:
|
|
207
|
+
raise ValueError(f"project exclude takes package names: {bad[:3]}")
|
|
208
|
+
return v
|
|
209
|
+
|
|
210
|
+
@field_validator("extra")
|
|
211
|
+
@classmethod
|
|
212
|
+
def _requirements(cls, v: list[str]) -> list[str]:
|
|
213
|
+
for r in v:
|
|
214
|
+
if not r.strip() or len(r) > 1024 or "\n" in r:
|
|
215
|
+
raise ValueError("a project extra is one requirement of 1 to 1024 characters, on one line")
|
|
216
|
+
return v
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class ImageStepProject(BaseModel):
|
|
220
|
+
model_config = ConfigDict(extra="forbid")
|
|
221
|
+
project: Project
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
ImageStep = (ImageStepUvPip | ImageStepApt | ImageStepEnv | ImageStepRun | ImageStepAddLocal | ImageStepTemplateFiles
|
|
225
|
+
| ImageStepProject)
|
|
130
226
|
|
|
131
227
|
|
|
132
228
|
class ImageSpec(BaseModel):
|
|
@@ -357,10 +453,31 @@ def check_weights_parts(weights_name: str | None, extra: dict) -> None:
|
|
|
357
453
|
raise ValueError(f"at most {MAX_WEIGHTS_PARTS} weights parts")
|
|
358
454
|
|
|
359
455
|
|
|
456
|
+
class PolicyConstructor(BaseModel):
|
|
457
|
+
"""How the worker builds a policy class that names its own methods (`@seq.policy(infer="get_action", ...)`): its
|
|
458
|
+
own constructor, or the factory it names (`make`, e.g. LeRobot's `from_pretrained`), called with `args` and
|
|
459
|
+
`kwargs` where `@seq.load` would run - the replica's cold start, the weights on disk. The values are the user's own,
|
|
460
|
+
as JSON carries them, but for two forms the worker fills in first (sequence_base.construct.expand): a config file's
|
|
461
|
+
contents, `{"$config": <its path from the class's file>, "$set": {<keys laid over it>}}`, YAML or JSON, and in any
|
|
462
|
+
string `"{weights}"` (the weights directory) or `"{weights.<part>}"` (a named part's).
|
|
463
|
+
"""
|
|
464
|
+
|
|
465
|
+
model_config = ConfigDict(extra="forbid")
|
|
466
|
+
|
|
467
|
+
make: str | None = Field(default=None, pattern=r"^[A-Za-z_][A-Za-z0-9_]*$",
|
|
468
|
+
description="a classmethod or staticmethod that returns the instance; None: the class")
|
|
469
|
+
args: list[Any] = Field(default_factory=list, description="positional arguments, in order")
|
|
470
|
+
kwargs: dict[str, Any] = Field(default_factory=dict, description="keyword arguments")
|
|
471
|
+
|
|
472
|
+
|
|
360
473
|
class PolicyRegistration(BaseModel):
|
|
361
474
|
"""What the harness loader introspects: the spec, the SeqFlags→method-name bindings, and the
|
|
362
475
|
`handler_ref` it importlib-loads the policy class from. From one of these the loader recovers which
|
|
363
|
-
method is which lifecycle entrypoint (load / infer / on_episode / plan …).
|
|
476
|
+
method is which lifecycle entrypoint (load / infer / on_episode / plan …).
|
|
477
|
+
|
|
478
|
+
A class that names its own methods (`constructor` set) is built by its constructor or factory, so it binds no LOAD,
|
|
479
|
+
and its bindings name its own methods: INFER (what answers an act), and as declared OBSERVE (each observation an
|
|
480
|
+
act carries, before INFER is called with none), RESET (an episode's first act) and ON_EPISODE (its end)."""
|
|
364
481
|
|
|
365
482
|
model_config = ConfigDict(extra="forbid")
|
|
366
483
|
|
|
@@ -374,3 +491,16 @@ class PolicyRegistration(BaseModel):
|
|
|
374
491
|
plan_every_s: float | None = Field(
|
|
375
492
|
default=None, gt=0, description="Seconds between @seq.plan calls; set iff a PLAN binding exists"
|
|
376
493
|
)
|
|
494
|
+
|
|
495
|
+
constructor: PolicyConstructor | None = None
|
|
496
|
+
|
|
497
|
+
@model_validator(mode="after")
|
|
498
|
+
def _built_by_its_constructor(self) -> "PolicyRegistration":
|
|
499
|
+
if self.constructor is not None:
|
|
500
|
+
if SeqFlags.LOAD in self.bindings:
|
|
501
|
+
raise ValueError("a policy built by its constructor (constructor) has no @seq.load: its constructor is "
|
|
502
|
+
"its load")
|
|
503
|
+
if SeqFlags.INFER not in self.bindings:
|
|
504
|
+
raise ValueError("a policy built by its constructor (constructor) names the method that answers an act "
|
|
505
|
+
"(an INFER binding)")
|
|
506
|
+
return self
|
|
@@ -90,6 +90,8 @@ from sequence_base.contract.policy import (
|
|
|
90
90
|
ImageStepAddLocal,
|
|
91
91
|
ImageStepRun,
|
|
92
92
|
ImageStepTemplateFiles,
|
|
93
|
+
ImageStepProject,
|
|
94
|
+
Project,
|
|
93
95
|
TemplateFiles,
|
|
94
96
|
ImageStepUvPip,
|
|
95
97
|
PolicyRegistration,
|
|
@@ -121,6 +123,8 @@ WIRE_MODELS: dict[str, type[BaseModel]] = {
|
|
|
121
123
|
"ImageStepAddLocal": ImageStepAddLocal,
|
|
122
124
|
"TemplateFiles": TemplateFiles,
|
|
123
125
|
"ImageStepTemplateFiles": ImageStepTemplateFiles,
|
|
126
|
+
"Project": Project,
|
|
127
|
+
"ImageStepProject": ImageStepProject,
|
|
124
128
|
"ImageSpec": ImageSpec,
|
|
125
129
|
"VolumeMount": VolumeMount,
|
|
126
130
|
"VolumeRef": VolumeRef,
|
{sequence_base-0.4.24 → sequence_base-0.4.26}/sequence_base/schemas/BenchmarkRegisterRequest.json
RENAMED
|
@@ -296,6 +296,9 @@
|
|
|
296
296
|
},
|
|
297
297
|
{
|
|
298
298
|
"$ref": "#/$defs/ImageStepTemplateFiles"
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
"$ref": "#/$defs/ImageStepProject"
|
|
299
302
|
}
|
|
300
303
|
]
|
|
301
304
|
},
|
|
@@ -353,6 +356,19 @@
|
|
|
353
356
|
"title": "ImageStepEnv",
|
|
354
357
|
"type": "object"
|
|
355
358
|
},
|
|
359
|
+
"ImageStepProject": {
|
|
360
|
+
"additionalProperties": false,
|
|
361
|
+
"properties": {
|
|
362
|
+
"project": {
|
|
363
|
+
"$ref": "#/$defs/Project"
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
"required": [
|
|
367
|
+
"project"
|
|
368
|
+
],
|
|
369
|
+
"title": "ImageStepProject",
|
|
370
|
+
"type": "object"
|
|
371
|
+
},
|
|
356
372
|
"ImageStepRun": {
|
|
357
373
|
"additionalProperties": false,
|
|
358
374
|
"properties": {
|
|
@@ -517,6 +533,81 @@
|
|
|
517
533
|
"title": "InferParam",
|
|
518
534
|
"type": "object"
|
|
519
535
|
},
|
|
536
|
+
"Project": {
|
|
537
|
+
"additionalProperties": false,
|
|
538
|
+
"description": "A project's own dependency files, which the image installs from (`seq.Image.from_project`, or a policy that\ndeclares no image and whose project has them): each read from the deploy's code bundle. The platform turns them into\nthe install when the image builds - for Linux and the worker's card, whatever machine the author wrote them on: the\nlock's exact versions, else the declared ones held to the versions the author's machine has (`pins`), the local\npackages the code imports with their own, less `exclude`, then `extra`.",
|
|
539
|
+
"properties": {
|
|
540
|
+
"declared": {
|
|
541
|
+
"description": "Its declared dependency files, relative to root: pyproject.toml, requirements*.txt, setup.cfg, setup.py",
|
|
542
|
+
"items": {
|
|
543
|
+
"type": "string"
|
|
544
|
+
},
|
|
545
|
+
"maxItems": 8,
|
|
546
|
+
"title": "Declared",
|
|
547
|
+
"type": "array"
|
|
548
|
+
},
|
|
549
|
+
"exclude": {
|
|
550
|
+
"description": "Packages left out, by name",
|
|
551
|
+
"items": {
|
|
552
|
+
"type": "string"
|
|
553
|
+
},
|
|
554
|
+
"maxItems": 256,
|
|
555
|
+
"title": "Exclude",
|
|
556
|
+
"type": "array"
|
|
557
|
+
},
|
|
558
|
+
"extra": {
|
|
559
|
+
"description": "Requirements added after the project's own: 'name==version', or 'name @ https://…' for a wheel",
|
|
560
|
+
"items": {
|
|
561
|
+
"type": "string"
|
|
562
|
+
},
|
|
563
|
+
"maxItems": 64,
|
|
564
|
+
"title": "Extra",
|
|
565
|
+
"type": "array"
|
|
566
|
+
},
|
|
567
|
+
"lock": {
|
|
568
|
+
"anyOf": [
|
|
569
|
+
{
|
|
570
|
+
"maxLength": 512,
|
|
571
|
+
"minLength": 1,
|
|
572
|
+
"type": "string"
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
"type": "null"
|
|
576
|
+
}
|
|
577
|
+
],
|
|
578
|
+
"default": null,
|
|
579
|
+
"description": "Its lock file, relative to root: uv.lock, poetry.lock, pdm.lock, Pipfile.lock or a requirements file whose every line pins a version",
|
|
580
|
+
"title": "Lock"
|
|
581
|
+
},
|
|
582
|
+
"packages": {
|
|
583
|
+
"description": "Folders inside the code bundle, each a package of its own (a pyproject.toml or setup.py) the code imports, installed with its dependencies",
|
|
584
|
+
"items": {
|
|
585
|
+
"type": "string"
|
|
586
|
+
},
|
|
587
|
+
"maxItems": 64,
|
|
588
|
+
"title": "Packages",
|
|
589
|
+
"type": "array"
|
|
590
|
+
},
|
|
591
|
+
"pins": {
|
|
592
|
+
"additionalProperties": {
|
|
593
|
+
"type": "string"
|
|
594
|
+
},
|
|
595
|
+
"description": "The version of each package the author's machine has, which an install from declared ranges keeps",
|
|
596
|
+
"maxProperties": 4096,
|
|
597
|
+
"title": "Pins",
|
|
598
|
+
"type": "object"
|
|
599
|
+
},
|
|
600
|
+
"root": {
|
|
601
|
+
"default": "",
|
|
602
|
+
"description": "The project's folder inside the code bundle ('' its root)",
|
|
603
|
+
"maxLength": 512,
|
|
604
|
+
"title": "Root",
|
|
605
|
+
"type": "string"
|
|
606
|
+
}
|
|
607
|
+
},
|
|
608
|
+
"title": "Project",
|
|
609
|
+
"type": "object"
|
|
610
|
+
},
|
|
520
611
|
"RenderSpec": {
|
|
521
612
|
"additionalProperties": false,
|
|
522
613
|
"description": "a.k.a. `seq.Record` — declares the mp4 the platform records (money-shot is a platform tool, not user\nglue). `camera` picks which camera stream to pull frames from; `fps` is the output frame rate. Used two\nways: as `BenchmarkSpec.render` (record the whole eval) and as `Step.record` (a per-frame hint).",
|
|
@@ -809,6 +900,18 @@
|
|
|
809
900
|
"default": null,
|
|
810
901
|
"title": "Envs"
|
|
811
902
|
},
|
|
903
|
+
"freeze": {
|
|
904
|
+
"anyOf": [
|
|
905
|
+
{
|
|
906
|
+
"type": "boolean"
|
|
907
|
+
},
|
|
908
|
+
{
|
|
909
|
+
"type": "null"
|
|
910
|
+
}
|
|
911
|
+
],
|
|
912
|
+
"default": null,
|
|
913
|
+
"title": "Freeze"
|
|
914
|
+
},
|
|
812
915
|
"gpu": {
|
|
813
916
|
"anyOf": [
|
|
814
917
|
{
|
|
@@ -974,5 +1077,5 @@
|
|
|
974
1077
|
],
|
|
975
1078
|
"title": "BenchmarkRegisterRequest",
|
|
976
1079
|
"type": "object",
|
|
977
|
-
"x-contract-version": "0.5.
|
|
1080
|
+
"x-contract-version": "0.5.23"
|
|
978
1081
|
}
|