x4d-devkit 0.50.0__tar.gz → 0.51.0__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.
Files changed (167) hide show
  1. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/PKG-INFO +1 -1
  2. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/pyproject.toml +1 -1
  3. x4d_devkit-0.51.0/tests/test_annotation_document_hash.py +111 -0
  4. x4d_devkit-0.51.0/tests/test_parse_equivalence.py +978 -0
  5. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_sdk_api.py +1 -1
  6. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_work_session.py +13 -0
  7. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/annotation_contract.py +111 -15
  8. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/core/loader.py +31 -17
  9. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/json_contract.py +41 -0
  10. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/work_session.py +184 -45
  11. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit.egg-info/PKG-INFO +1 -1
  12. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit.egg-info/SOURCES.txt +2 -0
  13. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/LICENSE +0 -0
  14. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/README.md +0 -0
  15. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/setup.cfg +0 -0
  16. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_accounts.py +0 -0
  17. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_annotation_contract.py +0 -0
  18. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_annotation_reference.py +0 -0
  19. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_assets_client.py +0 -0
  20. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_boundary.py +0 -0
  21. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_box.py +0 -0
  22. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_build_info.py +0 -0
  23. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_category_audit.py +0 -0
  24. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_clean_basemap.py +0 -0
  25. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_clean_basemap_client.py +0 -0
  26. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_cli_auth.py +0 -0
  27. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_client_json_contract.py +0 -0
  28. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_client_projects.py +0 -0
  29. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_clip_archives.py +0 -0
  30. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_contract_validation.py +0 -0
  31. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_coordinate_frames.py +0 -0
  32. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_current_state.py +0 -0
  33. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_current_state_identity_consumers_example.py +0 -0
  34. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_dataset_contract.py +0 -0
  35. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_dataset_identity.py +0 -0
  36. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_detection_eval.py +0 -0
  37. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_evaluate.py +0 -0
  38. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_export_bundle.py +0 -0
  39. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_image_clip.py +0 -0
  40. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_image_export.py +0 -0
  41. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_image_geometry.py +0 -0
  42. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_image_groups.py +0 -0
  43. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_image_preannotation.py +0 -0
  44. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_image_reference.py +0 -0
  45. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_image_review.py +0 -0
  46. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_image_schema_upgrade.py +0 -0
  47. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_image_work_session.py +0 -0
  48. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_inference_input.py +0 -0
  49. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_inference_schemas.py +0 -0
  50. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_inference_service.py +0 -0
  51. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_label_schemas.py +0 -0
  52. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_lidar_materialization.py +0 -0
  53. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_loader.py +0 -0
  54. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_matching.py +0 -0
  55. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_metrics.py +0 -0
  56. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_models.py +0 -0
  57. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_platform_pose.py +0 -0
  58. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_preannotation_client.py +0 -0
  59. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_project_discovery.py +0 -0
  60. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_reconstruction_client.py +0 -0
  61. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_release_gate.py +0 -0
  62. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_roadmark_preview.py +0 -0
  63. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_semantic_scene_authoring.py +0 -0
  64. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_semantic_scene_client.py +0 -0
  65. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_semantic_scene_contract.py +0 -0
  66. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_semantic_scene_independent.py +0 -0
  67. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_semantic_scene_native_clip.py +0 -0
  68. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_source_attributes.py +0 -0
  69. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_time_contract.py +0 -0
  70. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_token.py +0 -0
  71. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_tracking_eval.py +0 -0
  72. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_training_identity.py +0 -0
  73. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_transform.py +0 -0
  74. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_transform_chain.py +0 -0
  75. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_validation.py +0 -0
  76. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_vector_map.py +0 -0
  77. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_vector_map_geometry.py +0 -0
  78. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_vector_map_session.py +0 -0
  79. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/tests/test_work_session_validation_reuse.py +0 -0
  80. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/__init__.py +0 -0
  81. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/_image_contract.py +0 -0
  82. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/accounts.py +0 -0
  83. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/annotation_reference.py +0 -0
  84. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/audit.py +0 -0
  85. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/build_info.py +0 -0
  86. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/clean_basemap.py +0 -0
  87. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/cli.py +0 -0
  88. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/__init__.py +0 -0
  89. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/_origin.py +0 -0
  90. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/accounts.py +0 -0
  91. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/annotation_reference.py +0 -0
  92. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/assets.py +0 -0
  93. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/bags.py +0 -0
  94. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/capabilities.py +0 -0
  95. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/checkpoints.py +0 -0
  96. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/clean_basemap.py +0 -0
  97. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/client.py +0 -0
  98. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/clip_work_sessions.py +0 -0
  99. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/clips.py +0 -0
  100. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/dataset_identity.py +0 -0
  101. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/image_clips.py +0 -0
  102. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/image_exports.py +0 -0
  103. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/image_preannotations.py +0 -0
  104. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/image_review.py +0 -0
  105. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/image_schemas.py +0 -0
  106. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/image_work_sessions.py +0 -0
  107. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/label_schemas.py +0 -0
  108. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/preannotation.py +0 -0
  109. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/projects.py +0 -0
  110. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/reconstruction.py +0 -0
  111. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/semantic_scene.py +0 -0
  112. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/semantic_scene_authoring.py +0 -0
  113. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/services.py +0 -0
  114. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/client/test_sets.py +0 -0
  115. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/consumer_identity.py +0 -0
  116. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/coordinate_contract.py +0 -0
  117. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/core/__init__.py +0 -0
  118. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/core/models.py +0 -0
  119. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/core/token.py +0 -0
  120. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/core/transform.py +0 -0
  121. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/current_state.py +0 -0
  122. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/dataset_contract.py +0 -0
  123. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/dataset_identity.py +0 -0
  124. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/eval/__init__.py +0 -0
  125. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/eval/api.py +0 -0
  126. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/eval/box.py +0 -0
  127. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/eval/detection.py +0 -0
  128. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/eval/matching.py +0 -0
  129. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/eval/metrics.py +0 -0
  130. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/eval/tracking.py +0 -0
  131. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/export_bundle.py +0 -0
  132. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/image_clip.py +0 -0
  133. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/image_export.py +0 -0
  134. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/image_geometry.py +0 -0
  135. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/image_groups.py +0 -0
  136. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/image_preannotation.py +0 -0
  137. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/image_reference.py +0 -0
  138. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/image_review.py +0 -0
  139. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/image_schema.py +0 -0
  140. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/image_schema_upgrade.py +0 -0
  141. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/image_work_session.py +0 -0
  142. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/inference/__init__.py +0 -0
  143. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/inference/input.py +0 -0
  144. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/inference/models.py +0 -0
  145. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/inference/schemas.py +0 -0
  146. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/inference/service.py +0 -0
  147. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/inference/tasks.py +0 -0
  148. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/label_schema.py +0 -0
  149. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/lidar_materialization.py +0 -0
  150. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/materialize.py +0 -0
  151. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/project_discovery.py +0 -0
  152. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/roadmark_preview.py +0 -0
  153. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/semantic_scene_authoring.py +0 -0
  154. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/semantic_scene_contract.py +0 -0
  155. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/time_contract.py +0 -0
  156. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/training_identity.py +0 -0
  157. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/validation/__init__.py +0 -0
  158. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/validation/contract.py +0 -0
  159. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/validation/platform_pose.py +0 -0
  160. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/validation/report.py +0 -0
  161. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/validation/validator.py +0 -0
  162. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/vector_map.py +0 -0
  163. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit/vector_map_geometry.py +0 -0
  164. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit.egg-info/dependency_links.txt +0 -0
  165. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit.egg-info/entry_points.txt +0 -0
  166. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit.egg-info/requires.txt +0 -0
  167. {x4d_devkit-0.50.0 → x4d_devkit-0.51.0}/x4d_devkit.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: x4d-devkit
3
- Version: 0.50.0
3
+ Version: 0.51.0
4
4
  Summary: Official X-4D external SDK and CLI for datasets, platform APIs, training bridges, and automation
5
5
  Author: windzu
6
6
  License-Expression: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "x4d-devkit"
7
- version = "0.50.0"
7
+ version = "0.51.0"
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,111 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ import hashlib
5
+ import random
6
+
7
+ import pytest
8
+ import rfc8785
9
+
10
+ from tests.test_annotation_contract import _annotation
11
+ from x4d_devkit.annotation_contract import (
12
+ ANNOTATION_CONTRACT_VERSION,
13
+ AnnotationContractError,
14
+ ClipAnnotationDocument,
15
+ build_clip_annotations,
16
+ parse_clip_annotations,
17
+ )
18
+
19
+ _EDGE_FLOATS = (0.0, -0.0, 1e21, 1e-7, 5e-324, 1.7976931348623157e308, 0.1 + 0.2, -123.456)
20
+ _EDGE_TEXT = ("", "é", "雪", "\U0001f697", "a\"b\\c", "
", "\x7f")
21
+
22
+
23
+ def _sha(payload) -> str:
24
+ return f"sha256:{hashlib.sha256(rfc8785.dumps(payload)).hexdigest()}"
25
+
26
+
27
+ def _random_rows(rng: random.Random, count: int) -> list[dict]:
28
+ rows = []
29
+ for index in range(count):
30
+ row = _annotation(
31
+ f"ann-{rng.randrange(10**9):09d}-{index}",
32
+ f"sample-{rng.randrange(5)}",
33
+ instance=f"instance-{rng.randrange(4)}",
34
+ )
35
+ row["bbox_3d"]["translation"]["x"] = rng.choice(_EDGE_FLOATS)
36
+ row["bbox_3d"]["size"]["width"] = rng.random() * 10
37
+ row["attributes"] = {rng.choice(_EDGE_TEXT) or "k": rng.choice(_EDGE_TEXT)}
38
+ if rng.random() < 0.5:
39
+ row["velocity"] = {"vx": rng.choice(_EDGE_FLOATS), "vy": 0.0, "vz": -1.5}
40
+ rows.append(row)
41
+ for instance in {row["instance_token"] for row in rows}:
42
+ motion = rng.choice(("auto", "static", "dynamic"))
43
+ for row in rows:
44
+ if row["instance_token"] == instance:
45
+ row["motion"] = motion
46
+ return rows
47
+
48
+
49
+ def test_single_pass_digests_match_full_rfc8785_canonicalization():
50
+ rng = random.Random(8785)
51
+ for count in (0, 1, 2, 17, 120):
52
+ document = build_clip_annotations("clip-a", "base_link", _random_rows(rng, count))
53
+ payload = document.to_dict()
54
+ revision_payload = {key: value for key, value in payload.items() if key != "annotation_revision"}
55
+ assert document.annotation_revision == _sha(revision_payload)
56
+ assert document.document_hash() == _sha(payload)
57
+
58
+ parsed = parse_clip_annotations(payload)
59
+ assert parsed.annotation_revision == document.annotation_revision
60
+ assert parsed.document_hash() == _sha(payload)
61
+
62
+
63
+ def test_document_hash_is_derived_for_directly_constructed_and_replaced_documents():
64
+ document = build_clip_annotations("clip-a", "base_link", _random_rows(random.Random(1), 9))
65
+ direct = ClipAnnotationDocument(
66
+ clip_id=document.clip_id,
67
+ annotation_frame_id=document.annotation_frame_id,
68
+ annotation_revision=document.annotation_revision,
69
+ annotations=document.annotations,
70
+ )
71
+ assert direct.document_hash() == document.document_hash()
72
+
73
+ replaced = dataclasses.replace(document, annotations=document.annotations[:3])
74
+ assert replaced.document_hash() == _sha(replaced.to_dict())
75
+ assert replaced.document_hash() != document.document_hash()
76
+
77
+
78
+ def test_document_hash_is_not_part_of_document_equality():
79
+ rows = _random_rows(random.Random(2), 5)
80
+ built = build_clip_annotations("clip-a", "base_link", rows)
81
+ parsed = parse_clip_annotations(built.to_dict())
82
+ plain = ClipAnnotationDocument(
83
+ clip_id=built.clip_id,
84
+ annotation_frame_id=built.annotation_frame_id,
85
+ annotation_revision=built.annotation_revision,
86
+ annotations=built.annotations,
87
+ )
88
+ assert built == parsed == plain
89
+ assert "_document_hash" not in {field.name for field in dataclasses.fields(built)}
90
+ assert built.to_dict()["contract_version"] == ANNOTATION_CONTRACT_VERSION
91
+
92
+
93
+ def test_parse_reuses_only_documents_this_module_verified():
94
+ built = build_clip_annotations("clip-a", "base_link", _random_rows(random.Random(3), 4))
95
+ assert parse_clip_annotations(built) is built
96
+ assert parse_clip_annotations(parse_clip_annotations(built.to_dict())) is not built
97
+
98
+ forged = ClipAnnotationDocument(
99
+ clip_id=built.clip_id,
100
+ annotation_frame_id=built.annotation_frame_id,
101
+ annotation_revision="sha256:" + "0" * 64,
102
+ annotations=built.annotations,
103
+ )
104
+ forged.document_hash() # caching the hash must not mark the document verified
105
+ with pytest.raises(AnnotationContractError) as error:
106
+ parse_clip_annotations(forged)
107
+ assert error.value.code == "annotation_revision_mismatch"
108
+
109
+ replaced = dataclasses.replace(built, annotation_revision="sha256:" + "1" * 64)
110
+ with pytest.raises(AnnotationContractError):
111
+ parse_clip_annotations(replaced)