x4d-devkit 0.48.0rc7__tar.gz → 0.48.0rc8__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.48.0rc7 → x4d_devkit-0.48.0rc8}/PKG-INFO +1 -1
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/pyproject.toml +1 -1
- x4d_devkit-0.48.0rc8/tests/test_image_schema_upgrade.py +239 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_sdk_api.py +1 -1
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/__init__.py +3 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/client.py +2 -0
- x4d_devkit-0.48.0rc8/x4d_devkit/client/image_schemas.py +115 -0
- x4d_devkit-0.48.0rc8/x4d_devkit/image_schema_upgrade.py +347 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit.egg-info/PKG-INFO +1 -1
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit.egg-info/SOURCES.txt +3 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/LICENSE +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/README.md +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/setup.cfg +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_accounts.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_annotation_contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_annotation_reference.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_assets_client.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_boundary.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_box.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_build_info.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_category_audit.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_clean_basemap.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_clean_basemap_client.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_cli_auth.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_client_json_contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_client_projects.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_clip_archives.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_contract_validation.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_coordinate_frames.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_current_state.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_current_state_identity_consumers_example.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_dataset_contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_dataset_identity.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_detection_eval.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_evaluate.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_export_bundle.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_image_clip.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_image_export.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_image_geometry.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_image_preannotation.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_image_reference.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_image_review.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_image_work_session.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_inference_input.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_inference_schemas.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_inference_service.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_label_schemas.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_lidar_materialization.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_loader.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_matching.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_metrics.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_models.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_platform_pose.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_preannotation_client.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_reconstruction_client.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_release_gate.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_roadmark_preview.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_semantic_scene_client.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_semantic_scene_contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_semantic_scene_independent.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_semantic_scene_native_clip.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_source_attributes.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_time_contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_token.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_tracking_eval.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_training_identity.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_transform.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_transform_chain.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_validation.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_vector_map.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_vector_map_geometry.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_vector_map_session.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_work_session.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/tests/test_work_session_validation_reuse.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/__init__.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/_image_contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/accounts.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/annotation_contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/annotation_reference.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/audit.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/build_info.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/clean_basemap.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/cli.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/_origin.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/accounts.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/annotation_reference.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/assets.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/bags.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/capabilities.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/checkpoints.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/clean_basemap.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/clip_work_sessions.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/clips.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/dataset_identity.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/image_clips.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/image_exports.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/image_preannotations.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/image_review.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/image_work_sessions.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/label_schemas.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/preannotation.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/projects.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/reconstruction.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/semantic_scene.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/services.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/client/test_sets.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/consumer_identity.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/coordinate_contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/core/__init__.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/core/loader.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/core/models.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/core/token.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/core/transform.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/current_state.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/dataset_contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/dataset_identity.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/eval/__init__.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/eval/api.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/eval/box.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/eval/detection.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/eval/matching.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/eval/metrics.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/eval/tracking.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/export_bundle.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/image_clip.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/image_export.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/image_geometry.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/image_preannotation.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/image_reference.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/image_review.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/image_schema.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/image_work_session.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/inference/__init__.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/inference/input.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/inference/models.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/inference/schemas.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/inference/service.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/inference/tasks.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/json_contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/label_schema.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/lidar_materialization.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/materialize.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/roadmark_preview.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/semantic_scene_contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/time_contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/training_identity.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/validation/__init__.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/validation/contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/validation/platform_pose.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/validation/report.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/validation/validator.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/vector_map.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/vector_map_geometry.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit/work_session.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit.egg-info/dependency_links.txt +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit.egg-info/entry_points.txt +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/x4d_devkit.egg-info/requires.txt +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc8}/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.48.
|
|
7
|
+
version = "0.48.0rc8"
|
|
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"
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from x4d_devkit import X4DClient
|
|
8
|
+
from x4d_devkit._image_contract import fingerprint
|
|
9
|
+
from x4d_devkit.image_clip import build_image_identity
|
|
10
|
+
from x4d_devkit.image_schema import (
|
|
11
|
+
parse_image_schema_body,
|
|
12
|
+
parse_image_schema_record,
|
|
13
|
+
build_image_schema_snapshot,
|
|
14
|
+
)
|
|
15
|
+
from x4d_devkit.image_schema_upgrade import (
|
|
16
|
+
RECEIPT_CONTRACT,
|
|
17
|
+
additive_image_schema_changes,
|
|
18
|
+
image_schema_upgrade_candidate,
|
|
19
|
+
build_image_schema_upgrade_preview,
|
|
20
|
+
build_image_schema_upgrade_request,
|
|
21
|
+
parse_image_schema_upgrade_preview,
|
|
22
|
+
parse_image_schema_upgrade_request,
|
|
23
|
+
parse_image_schema_upgrade_receipt,
|
|
24
|
+
)
|
|
25
|
+
from tests.image_fixtures import clip_payload, line_object
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def target(clip):
|
|
29
|
+
body = deepcopy(clip["label_schema"]["body"])
|
|
30
|
+
body["version"] = "1.1.0"
|
|
31
|
+
body["categories"][0]["attributes"].append(
|
|
32
|
+
{
|
|
33
|
+
"id": "optional",
|
|
34
|
+
"display_name": "Optional",
|
|
35
|
+
"type": "boolean",
|
|
36
|
+
"required": False,
|
|
37
|
+
"scopes": ["object", "segment"],
|
|
38
|
+
"values": [],
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
for category in body["categories"]:
|
|
42
|
+
for attribute in category["attributes"]:
|
|
43
|
+
if attribute["type"] == "enum":
|
|
44
|
+
attribute["values"].append({"id": "extra", "display_name": "Extra"})
|
|
45
|
+
return build_image_schema_snapshot(
|
|
46
|
+
parse_image_schema_record(
|
|
47
|
+
{
|
|
48
|
+
"contract_version": "x4d-image-label-schema-record-v1",
|
|
49
|
+
"status": "active",
|
|
50
|
+
"body": body,
|
|
51
|
+
"fingerprint": parse_image_schema_body(body).identity.fingerprint,
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_additive_upgrade_preserves_complete_human_geometry_ranges_and_completion():
|
|
58
|
+
clip = clip_payload()
|
|
59
|
+
clip["image_annotation"]["objects"] = [line_object()]
|
|
60
|
+
clip["identity"] = build_image_identity(clip)
|
|
61
|
+
snapshot = target(clip)
|
|
62
|
+
candidate = image_schema_upgrade_candidate(clip, snapshot).to_dict()
|
|
63
|
+
assert all(
|
|
64
|
+
candidate[key] == clip[key]
|
|
65
|
+
for key in clip
|
|
66
|
+
if key not in {"label_schema", "identity"}
|
|
67
|
+
)
|
|
68
|
+
assert (
|
|
69
|
+
candidate["identity"]["source_revision"] == clip["identity"]["source_revision"]
|
|
70
|
+
)
|
|
71
|
+
assert (
|
|
72
|
+
candidate["identity"]["annotation_revision"]
|
|
73
|
+
== clip["identity"]["annotation_revision"]
|
|
74
|
+
)
|
|
75
|
+
assert (
|
|
76
|
+
candidate["identity"]["schema_fingerprint"]
|
|
77
|
+
!= clip["identity"]["schema_fingerprint"]
|
|
78
|
+
)
|
|
79
|
+
assert "optional" not in candidate["image_annotation"]["objects"][0]["attributes"]
|
|
80
|
+
preview = build_image_schema_upgrade_preview(clip, snapshot)
|
|
81
|
+
assert (
|
|
82
|
+
parse_image_schema_upgrade_preview(preview.to_dict()).to_dict()
|
|
83
|
+
== preview.to_dict()
|
|
84
|
+
)
|
|
85
|
+
request = build_image_schema_upgrade_request(preview, "explicit-test-key")
|
|
86
|
+
assert (
|
|
87
|
+
parse_image_schema_upgrade_request(request.to_dict()).to_dict()
|
|
88
|
+
== request.to_dict()
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@pytest.mark.parametrize(
|
|
93
|
+
"change",
|
|
94
|
+
[
|
|
95
|
+
"same-version",
|
|
96
|
+
"backwards",
|
|
97
|
+
"schema-id",
|
|
98
|
+
"category",
|
|
99
|
+
"geometry",
|
|
100
|
+
"view",
|
|
101
|
+
"attribute-remove",
|
|
102
|
+
"required",
|
|
103
|
+
"scope",
|
|
104
|
+
"enum-meaning",
|
|
105
|
+
"new-required",
|
|
106
|
+
],
|
|
107
|
+
)
|
|
108
|
+
def test_incompatible_reinterpretations_require_a_separate_migration(change):
|
|
109
|
+
clip = clip_payload()
|
|
110
|
+
old, new = clip["label_schema"]["body"], target(clip)["body"]
|
|
111
|
+
if change == "same-version":
|
|
112
|
+
new["version"] = old["version"]
|
|
113
|
+
if change == "backwards":
|
|
114
|
+
new["version"] = "0.1.0"
|
|
115
|
+
if change == "schema-id":
|
|
116
|
+
new["schema_id"] = "another"
|
|
117
|
+
if change == "category":
|
|
118
|
+
new["categories"].pop()
|
|
119
|
+
if change == "geometry":
|
|
120
|
+
new["categories"][0]["geometry_type"] = "box_2d"
|
|
121
|
+
if change == "view":
|
|
122
|
+
new["task_views"][0]["display_name"] = "Reinterpreted"
|
|
123
|
+
if change == "attribute-remove":
|
|
124
|
+
new["categories"][0]["attributes"].pop(0)
|
|
125
|
+
if change == "required":
|
|
126
|
+
new["categories"][0]["attributes"][0]["required"] = True
|
|
127
|
+
if change == "scope":
|
|
128
|
+
new["categories"][0]["attributes"][0]["scopes"] = ["object"]
|
|
129
|
+
if change == "enum-meaning":
|
|
130
|
+
new["categories"][0]["attributes"][1]["values"][0]["display_name"] = (
|
|
131
|
+
"Different meaning"
|
|
132
|
+
)
|
|
133
|
+
if change == "new-required":
|
|
134
|
+
new["categories"][0]["attributes"][-1]["required"] = True
|
|
135
|
+
with pytest.raises(ValueError):
|
|
136
|
+
additive_image_schema_changes(old, new)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@pytest.mark.parametrize(
|
|
140
|
+
"part",
|
|
141
|
+
["digest", "source", "human", "schema", "changes", "foreign-clip", "unknown-field"],
|
|
142
|
+
)
|
|
143
|
+
def test_preview_rejects_tampered_transition_even_with_recomputed_fingerprint(part):
|
|
144
|
+
clip = clip_payload()
|
|
145
|
+
value = build_image_schema_upgrade_preview(clip, target(clip)).to_dict()
|
|
146
|
+
if part == "source":
|
|
147
|
+
value["after_identity"]["components"]["sample"] = "sha256:" + "a" * 64
|
|
148
|
+
if part == "human":
|
|
149
|
+
value["after_identity"]["annotation_revision"] = "sha256:" + "a" * 64
|
|
150
|
+
if part == "schema":
|
|
151
|
+
value["target_schema"]["body"]["version"] = "1.2.0"
|
|
152
|
+
if part == "changes":
|
|
153
|
+
value["changes"]["added_attributes"] = []
|
|
154
|
+
if part == "foreign-clip":
|
|
155
|
+
value["after_identity"]["clip_id"] = "foreign"
|
|
156
|
+
if part == "unknown-field":
|
|
157
|
+
value["automatic_repair"] = True
|
|
158
|
+
value["preview_fingerprint"] = (
|
|
159
|
+
"sha256:" + "0" * 64
|
|
160
|
+
if part == "digest"
|
|
161
|
+
else fingerprint({k: v for k, v in value.items() if k != "preview_fingerprint"})
|
|
162
|
+
)
|
|
163
|
+
with pytest.raises(ValueError):
|
|
164
|
+
parse_image_schema_upgrade_preview(value)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_typed_image_schema_reads_and_upgrade_verify_the_exact_request():
|
|
168
|
+
clip = clip_payload()
|
|
169
|
+
snapshot = target(clip)
|
|
170
|
+
preview = build_image_schema_upgrade_preview(clip, snapshot).to_dict()
|
|
171
|
+
record = {
|
|
172
|
+
"contract_version": "x4d-image-label-schema-record-v1",
|
|
173
|
+
"status": "active",
|
|
174
|
+
"body": snapshot["body"],
|
|
175
|
+
"fingerprint": snapshot["identity"]["fingerprint"],
|
|
176
|
+
}
|
|
177
|
+
requests, bad = [], {"enabled": False}
|
|
178
|
+
|
|
179
|
+
def handler(request):
|
|
180
|
+
requests.append(request)
|
|
181
|
+
path = request.url.path
|
|
182
|
+
if path == "/api/v1/label-schemas":
|
|
183
|
+
assert request.url.params["project_type"] == "2d"
|
|
184
|
+
return httpx.Response(200, json={"items": [record], "total": 1})
|
|
185
|
+
if path == "/api/v1/projects/7/label-schema":
|
|
186
|
+
return httpx.Response(
|
|
187
|
+
200,
|
|
188
|
+
json={
|
|
189
|
+
"contract_version": "x4d-image-project-label-schema-v1",
|
|
190
|
+
"project_id": 7,
|
|
191
|
+
"label_schema": record,
|
|
192
|
+
},
|
|
193
|
+
)
|
|
194
|
+
if path.startswith("/api/v1/label-schemas/"):
|
|
195
|
+
return httpx.Response(200, json=record)
|
|
196
|
+
if path.endswith("/preview"):
|
|
197
|
+
assert json.loads(request.content) == {
|
|
198
|
+
"project_id": 7,
|
|
199
|
+
"clip_id": "clip_test",
|
|
200
|
+
"target_schema": snapshot["identity"],
|
|
201
|
+
}
|
|
202
|
+
return httpx.Response(200, json=preview)
|
|
203
|
+
body = json.loads(request.content)
|
|
204
|
+
receipt = {
|
|
205
|
+
"contract_version": RECEIPT_CONTRACT,
|
|
206
|
+
"project_id": 7,
|
|
207
|
+
"clip_id": "clip_test",
|
|
208
|
+
"idempotency_key": body["idempotency_key"],
|
|
209
|
+
"preview_fingerprint": preview["preview_fingerprint"],
|
|
210
|
+
"identity": preview["after_identity"],
|
|
211
|
+
"changed": True,
|
|
212
|
+
}
|
|
213
|
+
if bad["enabled"]:
|
|
214
|
+
receipt["idempotency_key"] = "other-request"
|
|
215
|
+
return httpx.Response(200, json=receipt)
|
|
216
|
+
|
|
217
|
+
with X4DClient("http://x4d.test", token="test") as client:
|
|
218
|
+
client._http.close()
|
|
219
|
+
client._http = httpx.Client(
|
|
220
|
+
base_url="http://x4d.test", transport=httpx.MockTransport(handler)
|
|
221
|
+
)
|
|
222
|
+
assert client.image_schemas.list()["total"] == 1
|
|
223
|
+
assert (
|
|
224
|
+
client.image_schemas.get(
|
|
225
|
+
schema_id=snapshot["identity"]["schema_id"], version="1.1.0"
|
|
226
|
+
).body
|
|
227
|
+
== snapshot["body"]
|
|
228
|
+
)
|
|
229
|
+
assert client.image_schemas.get_project(7).to_dict()["project_id"] == 7
|
|
230
|
+
parsed = client.image_schemas.preview_upgrade(clip=clip, target_schema=snapshot)
|
|
231
|
+
request = client.image_schemas.upgrade_request(parsed, "explicit-request")
|
|
232
|
+
receipt = client.image_schemas.commit_upgrade(request)
|
|
233
|
+
assert (
|
|
234
|
+
parse_image_schema_upgrade_receipt(receipt.to_dict()).to_dict()["identity"]
|
|
235
|
+
== preview["after_identity"]
|
|
236
|
+
)
|
|
237
|
+
bad["enabled"] = True
|
|
238
|
+
with pytest.raises(ValueError, match="another request"):
|
|
239
|
+
client.image_schemas.commit_upgrade(request)
|
|
@@ -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.48.
|
|
14
|
+
assert x4d_devkit.__version__ == "0.48.0rc8"
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def test_loader_exposes_and_enforces_only_current_schema(tmp_path):
|
|
@@ -42,7 +42,10 @@ from x4d_devkit.client.reconstruction import (
|
|
|
42
42
|
from x4d_devkit.client.services import ServicesAPI
|
|
43
43
|
from x4d_devkit.client.test_sets import TestSetsAPI
|
|
44
44
|
|
|
45
|
+
from x4d_devkit.client.image_schemas import ImageSchemasAPI
|
|
46
|
+
|
|
45
47
|
__all__ = [
|
|
48
|
+
"ImageSchemasAPI",
|
|
46
49
|
"ImageWorkSessionsAPI",
|
|
47
50
|
"ImageExportsAPI",
|
|
48
51
|
"CleanBasemapState",
|
|
@@ -28,6 +28,7 @@ from x4d_devkit.client.image_clips import ImageClipsAPI
|
|
|
28
28
|
from x4d_devkit.client.image_work_sessions import ImageWorkSessionsAPI
|
|
29
29
|
from x4d_devkit.client.image_preannotations import ImagePreannotationsAPI
|
|
30
30
|
from x4d_devkit.client.image_exports import ImageExportsAPI
|
|
31
|
+
from x4d_devkit.client.image_schemas import ImageSchemasAPI
|
|
31
32
|
from x4d_devkit.client.semantic_scene import SemanticSceneAPI
|
|
32
33
|
from x4d_devkit.client.semantic_scene_authoring import (
|
|
33
34
|
SemanticSceneAuthoringWorkSessionsAPI,
|
|
@@ -80,6 +81,7 @@ class X4DClient:
|
|
|
80
81
|
self.image_work_sessions = ImageWorkSessionsAPI(self)
|
|
81
82
|
self.image_preannotations = ImagePreannotationsAPI(self)
|
|
82
83
|
self.image_exports = ImageExportsAPI(self)
|
|
84
|
+
self.image_schemas = ImageSchemasAPI(self)
|
|
83
85
|
self.label_schemas = LabelSchemasAPI(self)
|
|
84
86
|
self.semantic_scene = SemanticSceneAPI(self)
|
|
85
87
|
self.semantic_scene_authoring_work_sessions = (
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Separate native image registry reads and explicit admin upgrade adapters."""
|
|
2
|
+
|
|
3
|
+
from x4d_devkit._image_contract import fields, integer, require
|
|
4
|
+
from x4d_devkit.image_schema import (
|
|
5
|
+
parse_image_schema_record,
|
|
6
|
+
parse_image_project_schema,
|
|
7
|
+
)
|
|
8
|
+
from x4d_devkit.image_schema_upgrade import (
|
|
9
|
+
build_image_schema_upgrade_preview,
|
|
10
|
+
build_image_schema_upgrade_request,
|
|
11
|
+
parse_image_schema_upgrade_preview,
|
|
12
|
+
parse_image_schema_upgrade_request,
|
|
13
|
+
parse_image_schema_upgrade_receipt,
|
|
14
|
+
)
|
|
15
|
+
from x4d_devkit.label_schema import LabelSchemaBinding
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ImageSchemasAPI:
|
|
19
|
+
def __init__(self, client):
|
|
20
|
+
self._client = client
|
|
21
|
+
|
|
22
|
+
def list(self, *, status=None):
|
|
23
|
+
params = {"project_type": "2d"}
|
|
24
|
+
if status is not None:
|
|
25
|
+
require(
|
|
26
|
+
status in ("draft", "active", "deprecated"),
|
|
27
|
+
"Invalid lifecycle filter",
|
|
28
|
+
"image_schemas.status",
|
|
29
|
+
)
|
|
30
|
+
params["status"] = status
|
|
31
|
+
value = self._client.get("/label-schemas", params=params)
|
|
32
|
+
fields(value, {"items", "total"}, "image_schemas")
|
|
33
|
+
require(
|
|
34
|
+
type(value["items"]) is list,
|
|
35
|
+
"Expected explicit records",
|
|
36
|
+
"image_schemas.items",
|
|
37
|
+
)
|
|
38
|
+
integer(value["total"], "image_schemas.total")
|
|
39
|
+
records = [parse_image_schema_record(row) for row in value["items"]]
|
|
40
|
+
require(
|
|
41
|
+
value["total"] == len(records)
|
|
42
|
+
and len({(row.schema_id, row.version) for row in records}) == len(records),
|
|
43
|
+
"Inconsistent registry list",
|
|
44
|
+
"image_schemas",
|
|
45
|
+
)
|
|
46
|
+
return {
|
|
47
|
+
"items": [record.to_dict() for record in records],
|
|
48
|
+
"total": len(records),
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
def get(self, *, schema_id, version):
|
|
52
|
+
LabelSchemaBinding(schema_id, version)
|
|
53
|
+
record = parse_image_schema_record(
|
|
54
|
+
self._client.get(f"/label-schemas/{schema_id}/versions/{version}")
|
|
55
|
+
)
|
|
56
|
+
require(
|
|
57
|
+
(record.schema_id, record.version) == (schema_id, version),
|
|
58
|
+
"Registry returned another Schema",
|
|
59
|
+
"image_schemas.identity",
|
|
60
|
+
)
|
|
61
|
+
return record
|
|
62
|
+
|
|
63
|
+
def get_project(self, project_id):
|
|
64
|
+
integer(project_id, "project_id", minimum=1)
|
|
65
|
+
value = parse_image_project_schema(
|
|
66
|
+
self._client.get(f"/projects/{project_id}/label-schema")
|
|
67
|
+
)
|
|
68
|
+
require(
|
|
69
|
+
value.to_dict()["project_id"] == project_id,
|
|
70
|
+
"Registry returned another project",
|
|
71
|
+
"image_schemas.project_id",
|
|
72
|
+
)
|
|
73
|
+
return value
|
|
74
|
+
|
|
75
|
+
def preview_upgrade(self, *, clip, target_schema):
|
|
76
|
+
expected = build_image_schema_upgrade_preview(clip, target_schema).to_dict()
|
|
77
|
+
result = parse_image_schema_upgrade_preview(
|
|
78
|
+
self._client.post(
|
|
79
|
+
"/image-schema-upgrades/preview",
|
|
80
|
+
json={
|
|
81
|
+
"project_id": expected["project_id"],
|
|
82
|
+
"clip_id": expected["clip_id"],
|
|
83
|
+
"target_schema": target_schema["identity"],
|
|
84
|
+
},
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
require(
|
|
88
|
+
result.to_dict() == expected,
|
|
89
|
+
"Upgrade preview differs from the exact Clip and target",
|
|
90
|
+
"image_schemas.preview",
|
|
91
|
+
)
|
|
92
|
+
return result
|
|
93
|
+
|
|
94
|
+
def commit_upgrade(self, request):
|
|
95
|
+
body = parse_image_schema_upgrade_request(
|
|
96
|
+
request.to_dict() if hasattr(request, "to_dict") else request
|
|
97
|
+
).to_dict()
|
|
98
|
+
result = parse_image_schema_upgrade_receipt(
|
|
99
|
+
self._client.post("/image-schema-upgrades/commit", json=body)
|
|
100
|
+
)
|
|
101
|
+
value, preview = result.to_dict(), body["preview"]
|
|
102
|
+
require(
|
|
103
|
+
all(
|
|
104
|
+
value[key] == preview[key]
|
|
105
|
+
for key in ("project_id", "clip_id", "preview_fingerprint")
|
|
106
|
+
)
|
|
107
|
+
and value["idempotency_key"] == body["idempotency_key"]
|
|
108
|
+
and value["identity"] == preview["after_identity"],
|
|
109
|
+
"Upgrade receipt belongs to another request",
|
|
110
|
+
"image_schemas.receipt",
|
|
111
|
+
)
|
|
112
|
+
return result
|
|
113
|
+
|
|
114
|
+
def upgrade_request(self, preview, idempotency_key):
|
|
115
|
+
return build_image_schema_upgrade_request(preview, idempotency_key)
|