x4d-devkit 0.45.0__tar.gz → 0.45.1__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.
- {x4d_devkit-0.45.0/x4d_devkit.egg-info → x4d_devkit-0.45.1}/PKG-INFO +1 -1
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/pyproject.toml +1 -1
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_sdk_api.py +1 -1
- x4d_devkit-0.45.1/tests/test_work_session_validation_reuse.py +109 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/work_session.py +85 -1
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1/x4d_devkit.egg-info}/PKG-INFO +1 -1
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit.egg-info/SOURCES.txt +1 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/LICENSE +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/README.md +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/setup.cfg +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_annotation_contract.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_annotation_reference.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_assets_client.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_boundary.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_box.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_build_info.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_clean_basemap.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_clean_basemap_client.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_clean_basemap_preannotation.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_cli_auth.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_client_json_contract.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_client_projects.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_clip_archives.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_contract_validation.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_coordinate_frames.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_current_state.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_current_state_identity_consumers_example.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_dataset_contract.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_dataset_identity.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_detection_eval.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_evaluate.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_export_bundle.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_image_review.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_inference_input.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_inference_schemas.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_inference_service.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_label_schemas.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_lidar_materialization.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_loader.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_matching.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_metrics.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_models.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_platform_pose.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_preannotation_client.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_reconstruction_client.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_release_gate.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_roadmark_preview.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_semantic_scene_client.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_semantic_scene_contract.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_semantic_scene_native_clip.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_source_attributes.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_time_contract.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_token.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_tracking_eval.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_training_identity.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_transform.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_transform_chain.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_validation.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_vector_map.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_vector_map_geometry.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_vector_map_session.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_work_session.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/__init__.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/annotation_contract.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/annotation_reference.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/build_info.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/clean_basemap.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/cli.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/client/__init__.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/client/_origin.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/client/annotation_reference.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/client/assets.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/client/bags.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/client/capabilities.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/client/checkpoints.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/client/clean_basemap.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/client/client.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/client/clip_work_sessions.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/client/clips.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/client/dataset_identity.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/client/image_review.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/client/label_schemas.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/client/preannotation.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/client/projects.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/client/reconstruction.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/client/semantic_scene.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/client/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/client/services.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/client/test_sets.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/consumer_identity.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/coordinate_contract.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/core/__init__.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/core/loader.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/core/models.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/core/token.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/core/transform.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/current_state.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/dataset_contract.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/dataset_identity.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/eval/__init__.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/eval/api.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/eval/box.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/eval/detection.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/eval/matching.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/eval/metrics.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/eval/tracking.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/export_bundle.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/image_review.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/inference/__init__.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/inference/input.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/inference/models.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/inference/schemas.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/inference/service.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/inference/tasks.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/json_contract.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/label_schema.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/lidar_materialization.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/materialize.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/roadmark_preview.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/semantic_scene_contract.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/time_contract.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/training_identity.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/validation/__init__.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/validation/contract.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/validation/platform_pose.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/validation/report.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/validation/validator.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/vector_map.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit/vector_map_geometry.py +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit.egg-info/dependency_links.txt +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit.egg-info/entry_points.txt +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit.egg-info/requires.txt +0 -0
- {x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/x4d_devkit.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "x4d-devkit"
|
|
7
|
-
version = "0.45.
|
|
7
|
+
version = "0.45.1"
|
|
8
8
|
description = "Official X-4D external SDK and CLI for datasets, platform APIs, training bridges, and automation"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -11,7 +11,7 @@ from x4d_devkit.eval.matching import MetricData
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def test_package_version_is_current_release():
|
|
14
|
-
assert x4d_devkit.__version__ == "0.45.
|
|
14
|
+
assert x4d_devkit.__version__ == "0.45.1"
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def test_loader_exposes_and_enforces_only_current_schema(tmp_path):
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Identical metadata may reuse validation; changed or mutable inputs may not."""
|
|
2
|
+
|
|
3
|
+
from collections import OrderedDict
|
|
4
|
+
import copy
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from tests.test_work_session import _manifest
|
|
10
|
+
import x4d_devkit.work_session as sessions
|
|
11
|
+
import x4d_devkit.validation.validator as validator
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture(autouse=True)
|
|
15
|
+
def isolated_validation_cache(monkeypatch):
|
|
16
|
+
monkeypatch.setattr(sessions, "_validated_sessions", OrderedDict())
|
|
17
|
+
monkeypatch.setattr(sessions, "_validated_session_bytes", 0)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.fixture
|
|
21
|
+
def validations(monkeypatch):
|
|
22
|
+
calls = []
|
|
23
|
+
original = validator.validate_clip_payload
|
|
24
|
+
|
|
25
|
+
def counted(*args, **kwargs):
|
|
26
|
+
calls.append(None)
|
|
27
|
+
return original(*args, **kwargs)
|
|
28
|
+
|
|
29
|
+
monkeypatch.setattr(validator, "validate_clip_payload", counted)
|
|
30
|
+
return calls
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_repeated_open_and_identity_comparison_validate_identical_payload_once(validations):
|
|
34
|
+
payload = _manifest()
|
|
35
|
+
first = sessions.parse_clip_work_session(payload)
|
|
36
|
+
second = sessions.parse_clip_work_session(first.to_dict())
|
|
37
|
+
assert sessions.compare_work_session(first, second).same
|
|
38
|
+
assert len(validations) == 1
|
|
39
|
+
assert first is not second
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_returned_snapshots_and_caller_input_cannot_poison_validation():
|
|
43
|
+
payload = _manifest()
|
|
44
|
+
original = copy.deepcopy(payload)
|
|
45
|
+
first = sessions.parse_clip_work_session(payload)
|
|
46
|
+
first.meta["sensors"]["lidar"]["frame_id"] = "changed-by-caller"
|
|
47
|
+
first.samples[0]["timestamp"]["sec"] = 99
|
|
48
|
+
payload["calibrated_sensor"][0]["translation"]["x"] = 100.0
|
|
49
|
+
second = sessions.parse_clip_work_session(original)
|
|
50
|
+
assert second.to_dict() == original
|
|
51
|
+
second.annotations[0]["bbox_3d"]["size"]["width"] = 999.0
|
|
52
|
+
assert sessions.parse_clip_work_session(original).to_dict() == original
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@pytest.mark.parametrize("changed", ["annotation", "sample_data", "typed_manifest"])
|
|
56
|
+
def test_changed_payload_is_revalidated_even_with_identical_revisions(changed):
|
|
57
|
+
payload = _manifest()
|
|
58
|
+
parsed = sessions.parse_clip_work_session(payload)
|
|
59
|
+
if changed == "annotation":
|
|
60
|
+
payload["annotations"][0]["bbox_3d"]["size"]["width"] = -1.0
|
|
61
|
+
elif changed == "sample_data":
|
|
62
|
+
payload["sample_data"][0]["sample_token"] = "missing-sample"
|
|
63
|
+
else:
|
|
64
|
+
parsed.meta["schema_version"] = "0.17"
|
|
65
|
+
payload = parsed
|
|
66
|
+
with pytest.raises(ValueError):
|
|
67
|
+
sessions.parse_clip_work_session(payload)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_cache_does_not_coerce_tuple_tables_into_valid_json_lists():
|
|
71
|
+
payload = _manifest()
|
|
72
|
+
sessions.parse_clip_work_session(payload)
|
|
73
|
+
payload["samples"] = tuple(payload["samples"])
|
|
74
|
+
with pytest.raises(ValueError, match="samples must be a list"):
|
|
75
|
+
sessions.parse_clip_work_session(payload)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_cache_does_not_coerce_integer_object_keys_into_strings():
|
|
79
|
+
payload = _manifest()
|
|
80
|
+
urls = {}
|
|
81
|
+
for index, row in enumerate(payload["sample_data"]):
|
|
82
|
+
urls[str(index)] = payload["asset_urls"][row["sample_data_token"]]
|
|
83
|
+
row["sample_data_token"] = str(index)
|
|
84
|
+
payload["asset_urls"] = urls
|
|
85
|
+
sessions.parse_clip_work_session(payload)
|
|
86
|
+
payload["asset_urls"] = {int(key): value for key, value in urls.items()}
|
|
87
|
+
with pytest.raises((ValueError, TypeError)):
|
|
88
|
+
sessions.parse_clip_work_session(payload)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@pytest.mark.parametrize("budget", ["entries", "bytes"])
|
|
92
|
+
def test_validation_reuse_has_bounded_lru_storage(monkeypatch, validations, budget):
|
|
93
|
+
first = _manifest()
|
|
94
|
+
second = copy.deepcopy(first)
|
|
95
|
+
second["business_metadata_revision"] = "sha256:other-business-metadata"
|
|
96
|
+
if budget == "entries":
|
|
97
|
+
monkeypatch.setattr(sessions, "_VALIDATED_SESSION_MAX_ENTRIES", 1)
|
|
98
|
+
else:
|
|
99
|
+
sizes = [len(json.dumps(value, sort_keys=True, ensure_ascii=False,
|
|
100
|
+
separators=(",", ":")).encode())
|
|
101
|
+
for value in (first, second)]
|
|
102
|
+
monkeypatch.setattr(sessions, "_VALIDATED_SESSION_MAX_BYTES", sum(sizes) - 1)
|
|
103
|
+
sessions.parse_clip_work_session(first)
|
|
104
|
+
sessions.parse_clip_work_session(second)
|
|
105
|
+
sessions.parse_clip_work_session(second)
|
|
106
|
+
assert len(validations) == 2
|
|
107
|
+
sessions.parse_clip_work_session(first)
|
|
108
|
+
assert len(validations) == 3
|
|
109
|
+
assert len(sessions._validated_sessions) == 1
|
|
@@ -7,9 +7,12 @@ without requiring a prebuilt archive.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import copy
|
|
10
|
+
from collections import OrderedDict
|
|
10
11
|
from dataclasses import dataclass
|
|
12
|
+
import json
|
|
11
13
|
from pathlib import PurePosixPath
|
|
12
14
|
import re
|
|
15
|
+
import threading
|
|
13
16
|
from typing import Any, Mapping
|
|
14
17
|
from urllib.parse import urljoin
|
|
15
18
|
|
|
@@ -290,6 +293,82 @@ class WorkSessionCompareResult:
|
|
|
290
293
|
return {"status": self.status, "changed": list(self.changed)}
|
|
291
294
|
|
|
292
295
|
|
|
296
|
+
_VALIDATED_SESSION_MAX_ENTRIES = 16
|
|
297
|
+
_VALIDATED_SESSION_MAX_BYTES = 32 * 1024 * 1024
|
|
298
|
+
_validated_sessions: OrderedDict[bytes, ClipWorkSessionManifest] = OrderedDict()
|
|
299
|
+
_validated_session_bytes = 0
|
|
300
|
+
_validated_session_guard = threading.Lock()
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _validation_payload(data: dict[str, Any]) -> bytes | None:
|
|
304
|
+
# JSON serialization must never coerce an invalid key or container into a
|
|
305
|
+
# previously validated payload. Other Python values take the normal parser.
|
|
306
|
+
pending: list[Any] = [data]
|
|
307
|
+
seen: set[int] = set()
|
|
308
|
+
while pending:
|
|
309
|
+
value = pending.pop()
|
|
310
|
+
if type(value) in (dict, list):
|
|
311
|
+
if id(value) in seen:
|
|
312
|
+
continue
|
|
313
|
+
seen.add(id(value))
|
|
314
|
+
if type(value) is dict:
|
|
315
|
+
if any(type(key) is not str for key in value):
|
|
316
|
+
return None
|
|
317
|
+
pending.extend(value.values())
|
|
318
|
+
else:
|
|
319
|
+
pending.extend(value)
|
|
320
|
+
elif type(value) not in (str, int, float, bool, type(None)):
|
|
321
|
+
return None
|
|
322
|
+
try:
|
|
323
|
+
payload = json.dumps(
|
|
324
|
+
data, sort_keys=True, ensure_ascii=False, allow_nan=False,
|
|
325
|
+
separators=(",", ":"),
|
|
326
|
+
).encode("utf-8")
|
|
327
|
+
except (ValueError, TypeError, UnicodeError, RecursionError):
|
|
328
|
+
return None
|
|
329
|
+
return payload if len(payload) <= _VALIDATED_SESSION_MAX_BYTES else None
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _session_snapshot(session: ClipWorkSessionManifest) -> ClipWorkSessionManifest:
|
|
333
|
+
# Schema internals contain immutable mapping proxies; reconstruct their
|
|
334
|
+
# small public snapshot and isolate every mutable table from the cache.
|
|
335
|
+
schema = parse_clip_label_schema_snapshot(session.label_schema.to_dict())
|
|
336
|
+
return copy.deepcopy(session, {id(session.label_schema): schema})
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _cached_session(data: dict[str, Any]) -> ClipWorkSessionManifest | None:
|
|
340
|
+
payload = _validation_payload(data)
|
|
341
|
+
if payload is None:
|
|
342
|
+
return None
|
|
343
|
+
with _validated_session_guard:
|
|
344
|
+
session = _validated_sessions.get(payload)
|
|
345
|
+
if session is not None:
|
|
346
|
+
_validated_sessions.move_to_end(payload)
|
|
347
|
+
return _session_snapshot(session) if session is not None else None
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _remember_session(session: ClipWorkSessionManifest) -> None:
|
|
351
|
+
global _validated_session_bytes
|
|
352
|
+
# Bind the validated result, never a caller-owned dictionary that could
|
|
353
|
+
# have changed during parsing. Bytes equality covers the complete payload.
|
|
354
|
+
payload = _validation_payload(session.to_dict())
|
|
355
|
+
if payload is None:
|
|
356
|
+
return
|
|
357
|
+
snapshot = _session_snapshot(session)
|
|
358
|
+
with _validated_session_guard:
|
|
359
|
+
if payload in _validated_sessions:
|
|
360
|
+
_validated_sessions.move_to_end(payload)
|
|
361
|
+
return
|
|
362
|
+
_validated_sessions[payload] = snapshot
|
|
363
|
+
_validated_session_bytes += len(payload)
|
|
364
|
+
while (
|
|
365
|
+
len(_validated_sessions) > _VALIDATED_SESSION_MAX_ENTRIES
|
|
366
|
+
or _validated_session_bytes > _VALIDATED_SESSION_MAX_BYTES
|
|
367
|
+
):
|
|
368
|
+
evicted, _ = _validated_sessions.popitem(last=False)
|
|
369
|
+
_validated_session_bytes -= len(evicted)
|
|
370
|
+
|
|
371
|
+
|
|
293
372
|
def parse_clip_work_session(
|
|
294
373
|
value: ClipWorkSessionManifest | Mapping[str, Any],
|
|
295
374
|
) -> ClipWorkSessionManifest:
|
|
@@ -301,6 +380,9 @@ def parse_clip_work_session(
|
|
|
301
380
|
data = dict(value)
|
|
302
381
|
else:
|
|
303
382
|
raise TypeError("work-session payload must be an object")
|
|
383
|
+
cached = _cached_session(data)
|
|
384
|
+
if cached is not None:
|
|
385
|
+
return cached
|
|
304
386
|
if data.get("contract_version") != WORK_SESSION_CONTRACT_VERSION:
|
|
305
387
|
raise ValueError(
|
|
306
388
|
"work-session contract_version must be "
|
|
@@ -562,7 +644,7 @@ def parse_clip_work_session(
|
|
|
562
644
|
)
|
|
563
645
|
if not report.ok:
|
|
564
646
|
raise ValueError(f"work-session payload violates the current clip contract:\n{report}")
|
|
565
|
-
|
|
647
|
+
session = ClipWorkSessionManifest(
|
|
566
648
|
project_id=project_id,
|
|
567
649
|
clip_id=clip_id,
|
|
568
650
|
ready=ready,
|
|
@@ -587,6 +669,8 @@ def parse_clip_work_session(
|
|
|
587
669
|
asset_urls=asset_urls,
|
|
588
670
|
semantic_scene_asset_urls=semantic_scene_asset_urls,
|
|
589
671
|
)
|
|
672
|
+
_remember_session(session)
|
|
673
|
+
return session
|
|
590
674
|
|
|
591
675
|
|
|
592
676
|
def compare_work_session(
|
|
@@ -55,6 +55,7 @@ tests/test_vector_map.py
|
|
|
55
55
|
tests/test_vector_map_geometry.py
|
|
56
56
|
tests/test_vector_map_session.py
|
|
57
57
|
tests/test_work_session.py
|
|
58
|
+
tests/test_work_session_validation_reuse.py
|
|
58
59
|
x4d_devkit/__init__.py
|
|
59
60
|
x4d_devkit/annotation_contract.py
|
|
60
61
|
x4d_devkit/annotation_reference.py
|
|
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
|
{x4d_devkit-0.45.0 → x4d_devkit-0.45.1}/tests/test_current_state_identity_consumers_example.py
RENAMED
|
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
|
|
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
|
|
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
|
|
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
|