sequence-base 0.4.6__tar.gz → 0.4.7__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.6 → sequence_base-0.4.7}/PKG-INFO +2 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/pyproject.toml +2 -2
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/constants/version.py +1 -1
- sequence_base-0.4.7/sequence_base/presets/frames.py +180 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/ActRequest.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/ActResponse.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/ActionChunk.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/ActionContract.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/ActionStep.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/AdapterTransform.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/AddLocal.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/Autoscaler.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/BenchmarkConditionSpec.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/BenchmarkRegisterRequest.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/BenchmarkSpec.json +2 -2
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/BenchmarkStatusResponse.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/BootstrapResponse.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/Camera.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/ClampInfo.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/CodeBundle.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/Concurrency.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/Conditions.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/ConnectRequest.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/ConnectResponse.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/DeploymentAccepted.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/DeploymentPolicy.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/DeploymentRequest.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/DeploymentStatusResponse.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/Embodiment.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/ErrorBody.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/ErrorCodes.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/Estimate.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/EvalDryRunResponse.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/EvalReport.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/EvalRequest.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/EvalResponse.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/ImageFrame.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/ImageSpec.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/ImageStepAddLocal.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/ImageStepApt.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/ImageStepEnv.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/ImageStepRun.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/ImageStepTemplateFiles.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/ImageStepUvPip.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/InferParam.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/Lease.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/LossyTransform.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/MetricAgg.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/Observation.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/ObservationContract.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/PolicyRegistration.json +2 -2
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/PolicySpec.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/Proprioception.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/RenderSpec.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/ReplaySpec.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/Resources.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/RolloutResult.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/SecretCreateRequest.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/SecretInfo.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/SecretRef.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/SeqFlags.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/Step.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/SuiteAgg.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/TaskAgg.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/TemplateFiles.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/Timeouts.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/UploadTarget.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/Variant.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/VolumeAttach.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/VolumeFile.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/VolumeInfo.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/VolumeMount.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/VolumeRef.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/VolumeUploadRequest.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/VolumeUploadResponse.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schemas/WarmingError.json +1 -1
- {sequence_base-0.4.6 → sequence_base-0.4.7}/.gitignore +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/README.md +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/__init__.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/auth.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/codec/__init__.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/codec/image.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/constants/__init__.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/constants/headers.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/contract/__init__.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/contract/accounts.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/contract/act.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/contract/benchmarks.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/contract/connect.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/contract/deployments.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/contract/errors.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/contract/eval.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/contract/flags.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/contract/io.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/contract/policy.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/contract/templates.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/contract/volumes.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/contract/wire.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/errors/__init__.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/errors/transport.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/obs/__init__.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/obs/log.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/obs/rid.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/presets/__init__.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/presets/robot_arm.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/schema_export.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/sequence_base/telemetry/__init__.py +0 -0
- {sequence_base-0.4.6 → sequence_base-0.4.7}/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.7
|
|
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
|
|
@@ -9,5 +9,6 @@ Provides-Extra: codec
|
|
|
9
9
|
Requires-Dist: numpy>=1.26; extra == 'codec'
|
|
10
10
|
Requires-Dist: pillow>=10; extra == 'codec'
|
|
11
11
|
Provides-Extra: test
|
|
12
|
+
Requires-Dist: hypothesis>=6; extra == 'test'
|
|
12
13
|
Requires-Dist: jsonschema>=4; extra == 'test'
|
|
13
14
|
Requires-Dist: pytest; extra == 'test'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sequence-base"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.7"
|
|
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"]
|
|
13
|
+
test = ["pytest", "jsonschema>=4", "hypothesis>=6"]
|
|
14
14
|
|
|
15
15
|
[build-system]
|
|
16
16
|
requires = ["hatchling"]
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The robot-arm preset's /act body as binary frames (application/x-seq-frames, version 1): the camera frames' own bytes
|
|
3
|
+
after the act's JSON instead of base64 inside it. A robot's direct acts only, and only to a worker that says it takes
|
|
4
|
+
them. Here and not in the core contract: it knows the preset's Observation (its `images`), which the core never does.
|
|
5
|
+
|
|
6
|
+
In one sentence: the SDK takes the act's envelope (the JSON it would send) → every `images[i].data` of the act's inputs
|
|
7
|
+
that is canonical base64 is decoded and moved out as a segment, its place in the JSON holding the segment's number → the
|
|
8
|
+
body is a 12-byte head, the segments' lengths, the JSON, then the segments → the worker checks every length before it
|
|
9
|
+
reads the JSON, puts each segment back as the base64 it was, and serves the envelope as it serves a JSON act: the
|
|
10
|
+
contract check, the policy and every other reader see the very same request.
|
|
11
|
+
|
|
12
|
+
The act's inputs are where the worker reads them from a JSON act: `inputs`, else `observation` (a robot's Observation
|
|
13
|
+
act, as the SDK sends it: {"model", "observation": {"images", "proprioception", ...}, "episode_id", ...}), else the body
|
|
14
|
+
itself.
|
|
15
|
+
|
|
16
|
+
WHY lossless by construction: a frame moves only when its string is canonical base64 - b64encode(b64decode(s)) == s -
|
|
17
|
+
so the worker's b64encode of the segment is that string again, character for character; any other string (line breaks,
|
|
18
|
+
no padding, a data: URI, a character outside base64) stays in the JSON as it was. The rest of the JSON is the same dict
|
|
19
|
+
through json.dumps and json.loads on both paths, so every float comes back to the bit.
|
|
20
|
+
WHY the worker turns the bytes back into base64: `ImageFrame.data` is base64, and a policy reads it as such - many decode
|
|
21
|
+
it themselves - so the request a policy sees must not change. What binary frames save is the upload: base64 is four
|
|
22
|
+
characters for three bytes, a third more on every act.
|
|
23
|
+
WHY a worker must say it takes them (FORMATS_HEADER on each of its answers): a worker before them reads any body that is
|
|
24
|
+
not JSON as the policy's own inputs (custom I/O), so frames reaching it would go to the policy untouched.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
Layout, little-endian:
|
|
29
|
+
|
|
30
|
+
offset length what
|
|
31
|
+
0 4 MAGIC, b"SQF1" (its last byte the version)
|
|
32
|
+
4 4 json_len, u32
|
|
33
|
+
8 4 n, the segments, u32 (at most MAX_SEGMENTS)
|
|
34
|
+
12 4 n each segment's length, u32
|
|
35
|
+
12 + 4 n json_len the envelope's JSON (UTF-8): a moved frame's `data` is its segment's number
|
|
36
|
+
then sum the segments, in order
|
|
37
|
+
|
|
38
|
+
The body is exactly that long, and each segment is named once, by an `images[i].data` of the act's inputs.
|
|
39
|
+
|
|
40
|
+
body = frames.pack(envelope) # None: nothing would move - send the envelope as JSON
|
|
41
|
+
envelope = frames.unpack(body) # FramesError: not version 1 (the worker's 400, reason "frames")
|
|
42
|
+
"""
|
|
43
|
+
from __future__ import annotations
|
|
44
|
+
|
|
45
|
+
import base64
|
|
46
|
+
import binascii
|
|
47
|
+
import json
|
|
48
|
+
import struct
|
|
49
|
+
from typing import Any
|
|
50
|
+
|
|
51
|
+
__all__ = ["FORMAT", "FORMATS_HEADER", "MAGIC", "MAX_SEGMENTS", "MEDIA_TYPE", "FramesError", "formats_of", "pack",
|
|
52
|
+
"unpack"]
|
|
53
|
+
|
|
54
|
+
MEDIA_TYPE = "application/x-seq-frames"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
FORMATS_HEADER = "X-Seq-Act-Formats"
|
|
58
|
+
FORMAT = "x-seq-frames/1"
|
|
59
|
+
MAGIC = b"SQF1"
|
|
60
|
+
|
|
61
|
+
MAX_SEGMENTS = 256
|
|
62
|
+
|
|
63
|
+
_HEAD = struct.Struct("<4sII")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class FramesError(ValueError):
|
|
67
|
+
"""A body that is not binary frames version 1: the worker answers 400 (reason "frames") with this message."""
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def formats_of(value: str | None) -> set[str]:
|
|
71
|
+
"""The formats a FORMATS_HEADER value names ("x-seq-frames/1, ..."): an empty set for none."""
|
|
72
|
+
return {part.strip().lower() for part in (value or "").split(",") if part.strip()}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _key(envelope: dict) -> str | None:
|
|
76
|
+
"""Where the act's inputs are, as the worker reads a JSON act: `inputs`, else `observation`, else None - the body
|
|
77
|
+
itself."""
|
|
78
|
+
return "inputs" if "inputs" in envelope else "observation" if "observation" in envelope else None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _images(envelope: Any) -> list | None:
|
|
82
|
+
"""The `images` of the act's inputs when they have the robot-arm preset's shape (a list of objects), else None."""
|
|
83
|
+
if not isinstance(envelope, dict):
|
|
84
|
+
return None
|
|
85
|
+
key = _key(envelope)
|
|
86
|
+
inputs = envelope[key] if key is not None else envelope
|
|
87
|
+
images = inputs.get("images") if isinstance(inputs, dict) else None
|
|
88
|
+
if not isinstance(images, list) or not all(isinstance(frame, dict) for frame in images):
|
|
89
|
+
return None
|
|
90
|
+
return images
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _canonical(data: Any) -> bytes | None:
|
|
94
|
+
"""The bytes a frame's `data` stands for, when it is canonical base64 (b64encode gives the string back exactly)."""
|
|
95
|
+
if not isinstance(data, str):
|
|
96
|
+
return None
|
|
97
|
+
try:
|
|
98
|
+
raw = base64.b64decode(data, validate=True)
|
|
99
|
+
except (binascii.Error, ValueError):
|
|
100
|
+
return None
|
|
101
|
+
return raw if base64.b64encode(raw).decode("ascii") == data else None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _a_number(data: Any) -> bool:
|
|
105
|
+
"""Whether a frame's `data` would read as a segment's number (unpack takes any int but a bool as one)."""
|
|
106
|
+
return isinstance(data, int) and not isinstance(data, bool)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def pack(envelope: dict) -> bytes | None:
|
|
110
|
+
"""`envelope` - the act's JSON body - as binary frames; None when none of its frames would move, when a frame that
|
|
111
|
+
stays has a number for its `data` (the worker would read it as a segment's), or when its JSON would not encode (a
|
|
112
|
+
NaN, a lone surrogate): the caller sends it as JSON, as before. The caller's dict is untouched."""
|
|
113
|
+
images = _images(envelope)
|
|
114
|
+
if not images:
|
|
115
|
+
return None
|
|
116
|
+
moved: dict[int, int] = {}
|
|
117
|
+
segments: list[bytes] = []
|
|
118
|
+
for i, frame in enumerate(images):
|
|
119
|
+
raw = _canonical(frame.get("data"))
|
|
120
|
+
if raw is not None:
|
|
121
|
+
moved[i] = len(segments)
|
|
122
|
+
segments.append(raw)
|
|
123
|
+
elif _a_number(frame.get("data")):
|
|
124
|
+
return None
|
|
125
|
+
if not segments or len(segments) > MAX_SEGMENTS:
|
|
126
|
+
return None
|
|
127
|
+
key = _key(envelope)
|
|
128
|
+
inputs = dict(envelope[key] if key is not None else envelope)
|
|
129
|
+
inputs["images"] = [{**frame, "data": moved[i]} if i in moved else frame for i, frame in enumerate(images)]
|
|
130
|
+
try:
|
|
131
|
+
text = json.dumps({**envelope, key: inputs} if key is not None else inputs, ensure_ascii=False,
|
|
132
|
+
separators=(",", ":"), allow_nan=False).encode("utf-8")
|
|
133
|
+
except (TypeError, ValueError):
|
|
134
|
+
return None
|
|
135
|
+
head = _HEAD.pack(MAGIC, len(text), len(segments)) + struct.pack(f"<{len(segments)}I", *map(len, segments))
|
|
136
|
+
return b"".join((head, text, *segments))
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def unpack(body: bytes) -> dict:
|
|
140
|
+
"""The act's JSON body from binary frames, each segment back in its frame as the base64 it was. Raises FramesError,
|
|
141
|
+
saying what is wrong, for anything that is not exactly version 1 - checked before the JSON is read."""
|
|
142
|
+
size = len(body)
|
|
143
|
+
if size < _HEAD.size:
|
|
144
|
+
raise FramesError(f"the body is {size} bytes; binary frames begin with a {_HEAD.size}-byte head")
|
|
145
|
+
magic, json_len, count = _HEAD.unpack_from(body, 0)
|
|
146
|
+
if magic != MAGIC:
|
|
147
|
+
raise FramesError(f"the body does not begin with {MAGIC!r}: not binary frames version 1")
|
|
148
|
+
if count > MAX_SEGMENTS:
|
|
149
|
+
raise FramesError(f"the head names {count} segments; at most {MAX_SEGMENTS}")
|
|
150
|
+
table_end = _HEAD.size + 4 * count
|
|
151
|
+
if size < table_end:
|
|
152
|
+
raise FramesError(f"the body is {size} bytes and ends inside its table of {count} segment lengths")
|
|
153
|
+
lengths = struct.unpack_from(f"<{count}I", body, _HEAD.size)
|
|
154
|
+
expected = table_end + json_len + sum(lengths)
|
|
155
|
+
if size != expected:
|
|
156
|
+
raise FramesError(f"the body is {size} bytes; its head and segment lengths make it {expected}")
|
|
157
|
+
try:
|
|
158
|
+
envelope = json.loads(body[table_end:table_end + json_len].decode("utf-8"))
|
|
159
|
+
except (UnicodeDecodeError, ValueError):
|
|
160
|
+
raise FramesError("the JSON part does not parse") from None
|
|
161
|
+
if not isinstance(envelope, dict):
|
|
162
|
+
raise FramesError("the JSON part is not an object")
|
|
163
|
+
starts, at = [], table_end + json_len
|
|
164
|
+
for length in lengths:
|
|
165
|
+
starts.append(at)
|
|
166
|
+
at += length
|
|
167
|
+
named: set[int] = set()
|
|
168
|
+
for frame in _images(envelope) or ():
|
|
169
|
+
ref = frame.get("data")
|
|
170
|
+
if not _a_number(ref):
|
|
171
|
+
continue
|
|
172
|
+
if not 0 <= ref < count:
|
|
173
|
+
raise FramesError(f"a frame names segment {ref}; the body has {count}")
|
|
174
|
+
if ref in named:
|
|
175
|
+
raise FramesError(f"segment {ref} is named by two frames")
|
|
176
|
+
named.add(ref)
|
|
177
|
+
frame["data"] = base64.b64encode(body[starts[ref]:starts[ref] + lengths[ref]]).decode("ascii")
|
|
178
|
+
if len(named) != count:
|
|
179
|
+
raise FramesError(f"{count - len(named)} of the body's {count} segments are named by no frame")
|
|
180
|
+
return envelope
|
|
@@ -368,7 +368,7 @@
|
|
|
368
368
|
],
|
|
369
369
|
"title": "SeqFlags",
|
|
370
370
|
"type": "integer",
|
|
371
|
-
"x-contract-version": "0.5.
|
|
371
|
+
"x-contract-version": "0.5.7",
|
|
372
372
|
"x-flag-names": {
|
|
373
373
|
"CHECK": 65536,
|
|
374
374
|
"INFER": 16,
|
|
@@ -625,5 +625,5 @@
|
|
|
625
625
|
],
|
|
626
626
|
"title": "BenchmarkSpec",
|
|
627
627
|
"type": "object",
|
|
628
|
-
"x-contract-version": "0.5.
|
|
628
|
+
"x-contract-version": "0.5.7"
|
|
629
629
|
}
|
|
@@ -852,7 +852,7 @@
|
|
|
852
852
|
],
|
|
853
853
|
"title": "SeqFlags",
|
|
854
854
|
"type": "integer",
|
|
855
|
-
"x-contract-version": "0.5.
|
|
855
|
+
"x-contract-version": "0.5.7",
|
|
856
856
|
"x-flag-names": {
|
|
857
857
|
"CHECK": 65536,
|
|
858
858
|
"INFER": 16,
|
|
@@ -981,5 +981,5 @@
|
|
|
981
981
|
],
|
|
982
982
|
"title": "PolicyRegistration",
|
|
983
983
|
"type": "object",
|
|
984
|
-
"x-contract-version": "0.5.
|
|
984
|
+
"x-contract-version": "0.5.7"
|
|
985
985
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|