x4d-devkit 0.48.0rc7__tar.gz → 0.48.0rc9__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.0rc9}/PKG-INFO +1 -1
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/pyproject.toml +1 -1
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_image_clip.py +34 -0
- x4d_devkit-0.48.0rc9/tests/test_image_schema_upgrade.py +239 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_sdk_api.py +1 -1
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/__init__.py +3 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/client.py +2 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/image_clips.py +26 -11
- x4d_devkit-0.48.0rc9/x4d_devkit/client/image_schemas.py +115 -0
- x4d_devkit-0.48.0rc9/x4d_devkit/image_schema_upgrade.py +347 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit.egg-info/PKG-INFO +1 -1
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit.egg-info/SOURCES.txt +3 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/LICENSE +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/README.md +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/setup.cfg +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_accounts.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_annotation_contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_annotation_reference.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_assets_client.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_boundary.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_box.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_build_info.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_category_audit.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_clean_basemap.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_clean_basemap_client.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_cli_auth.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_client_json_contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_client_projects.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_clip_archives.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_contract_validation.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_coordinate_frames.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_current_state.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_current_state_identity_consumers_example.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_dataset_contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_dataset_identity.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_detection_eval.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_evaluate.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_export_bundle.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_image_export.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_image_geometry.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_image_preannotation.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_image_reference.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_image_review.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_image_work_session.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_inference_input.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_inference_schemas.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_inference_service.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_label_schemas.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_lidar_materialization.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_loader.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_matching.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_metrics.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_models.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_platform_pose.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_preannotation_client.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_reconstruction_client.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_release_gate.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_roadmark_preview.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_semantic_scene_client.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_semantic_scene_contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_semantic_scene_independent.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_semantic_scene_native_clip.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_source_attributes.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_time_contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_token.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_tracking_eval.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_training_identity.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_transform.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_transform_chain.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_validation.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_vector_map.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_vector_map_geometry.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_vector_map_session.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_work_session.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/tests/test_work_session_validation_reuse.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/__init__.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/_image_contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/accounts.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/annotation_contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/annotation_reference.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/audit.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/build_info.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/clean_basemap.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/cli.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/_origin.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/accounts.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/annotation_reference.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/assets.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/bags.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/capabilities.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/checkpoints.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/clean_basemap.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/clip_work_sessions.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/clips.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/dataset_identity.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/image_exports.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/image_preannotations.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/image_review.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/image_work_sessions.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/label_schemas.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/preannotation.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/projects.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/reconstruction.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/semantic_scene.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/services.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/client/test_sets.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/consumer_identity.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/coordinate_contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/core/__init__.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/core/loader.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/core/models.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/core/token.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/core/transform.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/current_state.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/dataset_contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/dataset_identity.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/eval/__init__.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/eval/api.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/eval/box.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/eval/detection.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/eval/matching.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/eval/metrics.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/eval/tracking.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/export_bundle.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/image_clip.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/image_export.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/image_geometry.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/image_preannotation.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/image_reference.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/image_review.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/image_schema.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/image_work_session.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/inference/__init__.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/inference/input.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/inference/models.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/inference/schemas.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/inference/service.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/inference/tasks.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/json_contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/label_schema.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/lidar_materialization.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/materialize.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/roadmark_preview.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/semantic_scene_contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/time_contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/training_identity.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/validation/__init__.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/validation/contract.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/validation/platform_pose.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/validation/report.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/validation/validator.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/vector_map.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/vector_map_geometry.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit/work_session.py +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit.egg-info/dependency_links.txt +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit.egg-info/entry_points.txt +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/x4d_devkit.egg-info/requires.txt +0 -0
- {x4d_devkit-0.48.0rc7 → x4d_devkit-0.48.0rc9}/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.0rc9"
|
|
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"
|
|
@@ -308,6 +308,40 @@ def test_typed_client_validates_metadata_then_reads_one_authorized_verified_imag
|
|
|
308
308
|
assert len(requests) == 3
|
|
309
309
|
|
|
310
310
|
|
|
311
|
+
@pytest.mark.parametrize("failure", [None, "source", "descriptor", "origin", "contract"])
|
|
312
|
+
def test_reference_only_read_validates_context_without_requesting_media(failure):
|
|
313
|
+
value, requests = clip_payload(), []
|
|
314
|
+
|
|
315
|
+
def handler(request):
|
|
316
|
+
requests.append(request)
|
|
317
|
+
assert request.url.path.endswith("/images/image_a")
|
|
318
|
+
result = {
|
|
319
|
+
"contract_version": "x4d-image-asset-reference-v1",
|
|
320
|
+
"clip_id": "clip_test", "image_id": "image_a",
|
|
321
|
+
"source_revision": value["identity"]["source_revision"],
|
|
322
|
+
"asset": dict(value["sample_data"][0]["asset"]),
|
|
323
|
+
"uri": "/api/v1/files/clips/clip_test/samples/cam_a/10000000020.jpg",
|
|
324
|
+
}
|
|
325
|
+
if failure == "source":
|
|
326
|
+
result["source_revision"] = "sha256:" + "f" * 64
|
|
327
|
+
elif failure == "descriptor":
|
|
328
|
+
result["asset"]["size"] += 1
|
|
329
|
+
elif failure == "origin":
|
|
330
|
+
result["uri"] = "https://untrusted.example/api/v1/files/private"
|
|
331
|
+
elif failure == "contract":
|
|
332
|
+
result["contract_version"] = "other"
|
|
333
|
+
return httpx.Response(200, json=result)
|
|
334
|
+
|
|
335
|
+
with client_for(handler) as client:
|
|
336
|
+
clip = parse_image_clip(value)
|
|
337
|
+
if failure:
|
|
338
|
+
with pytest.raises(ValueError):
|
|
339
|
+
client.image_clips.get_asset(clip, "image_a")
|
|
340
|
+
else:
|
|
341
|
+
assert client.image_clips.get_asset(clip, "image_a")["asset"] == clip.images[0]["asset"]
|
|
342
|
+
assert len(requests) == 1
|
|
343
|
+
|
|
344
|
+
|
|
311
345
|
@pytest.mark.parametrize(
|
|
312
346
|
"failure",
|
|
313
347
|
[
|
|
@@ -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.0rc9"
|
|
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 = (
|
|
@@ -7,6 +7,7 @@ from urllib.parse import quote
|
|
|
7
7
|
|
|
8
8
|
from x4d_devkit._image_contract import fields, identifier, integer, require
|
|
9
9
|
from x4d_devkit.client.assets import PlatformAsset
|
|
10
|
+
from x4d_devkit.client.assets import _validated_file_uri
|
|
10
11
|
from x4d_devkit.image_clip import ImageClip, parse_image_clip
|
|
11
12
|
|
|
12
13
|
|
|
@@ -47,22 +48,15 @@ class ImageClipsAPI:
|
|
|
47
48
|
value = self._client.get(f"/image-clips/{quote(clip_id, safe='')}")
|
|
48
49
|
return parse_image_clip(value, expected_clip_id=clip_id)
|
|
49
50
|
|
|
50
|
-
def
|
|
51
|
-
|
|
52
|
-
) -> PlatformAsset:
|
|
51
|
+
def get_asset(self, clip: ImageClip, image_id: str) -> dict:
|
|
52
|
+
"""Validate an authorized reference without downloading original bytes."""
|
|
53
53
|
require(
|
|
54
54
|
type(clip) is ImageClip, "expected a validated native image Clip", "clip"
|
|
55
55
|
)
|
|
56
56
|
identifier(image_id, "image_id")
|
|
57
|
-
integer(max_bytes, "max_bytes", minimum=1)
|
|
58
57
|
image = next((item for item in clip.images if item["id"] == image_id), None)
|
|
59
58
|
require(image is not None, "image does not belong to this Clip", "image_id")
|
|
60
59
|
expected = image["asset"]
|
|
61
|
-
require(
|
|
62
|
-
expected["size"] <= max_bytes,
|
|
63
|
-
"image exceeds the configured read budget",
|
|
64
|
-
"image.asset.size",
|
|
65
|
-
)
|
|
66
60
|
value = self._client.get(
|
|
67
61
|
f"/image-clips/{quote(clip.clip_id, safe='')}/images/{quote(image_id, safe='')}"
|
|
68
62
|
)
|
|
@@ -82,12 +76,33 @@ class ImageClipsAPI:
|
|
|
82
76
|
value["contract_version"] == IMAGE_ASSET_REFERENCE_VERSION
|
|
83
77
|
and value["clip_id"] == clip.clip_id
|
|
84
78
|
and value["image_id"] == image_id
|
|
85
|
-
and value["source_revision"]
|
|
86
|
-
== clip.to_dict()["identity"]["source_revision"]
|
|
87
79
|
and value["asset"] == expected,
|
|
88
80
|
"image asset context changed",
|
|
89
81
|
"image_asset",
|
|
90
82
|
)
|
|
83
|
+
require(
|
|
84
|
+
value["source_revision"] == clip.to_dict()["identity"]["source_revision"],
|
|
85
|
+
"image source revision changed",
|
|
86
|
+
"image_asset.source_revision",
|
|
87
|
+
)
|
|
88
|
+
_validated_file_uri(value["uri"], api_url=self._client.api_url)
|
|
89
|
+
return value
|
|
90
|
+
|
|
91
|
+
def read_image(
|
|
92
|
+
self, clip: ImageClip, image_id: str, *, max_bytes: int = 32 * 1024 * 1024
|
|
93
|
+
) -> PlatformAsset:
|
|
94
|
+
require(type(clip) is ImageClip, "expected a validated native image Clip", "clip")
|
|
95
|
+
identifier(image_id, "image_id")
|
|
96
|
+
integer(max_bytes, "max_bytes", minimum=1)
|
|
97
|
+
image = next((item for item in clip.images if item["id"] == image_id), None)
|
|
98
|
+
require(image is not None, "image does not belong to this Clip", "image_id")
|
|
99
|
+
expected = image["asset"]
|
|
100
|
+
require(
|
|
101
|
+
expected["size"] <= max_bytes,
|
|
102
|
+
"image exceeds the configured read budget",
|
|
103
|
+
"image.asset.size",
|
|
104
|
+
)
|
|
105
|
+
value = self.get_asset(clip, image_id)
|
|
91
106
|
asset = self._client.assets.fetch(value["uri"], max_bytes=expected["size"])
|
|
92
107
|
require(
|
|
93
108
|
len(asset.content) == expected["size"]
|
|
@@ -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)
|