bec-lib 4.0.0__tar.gz → 4.0.2__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 (156) hide show
  1. {bec_lib-4.0.0 → bec_lib-4.0.2}/PKG-INFO +1 -1
  2. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/messages.py +24 -14
  3. {bec_lib-4.0.0 → bec_lib-4.0.2}/pyproject.toml +1 -1
  4. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_bec_messages.py +36 -14
  5. {bec_lib-4.0.0 → bec_lib-4.0.2}/.gitignore +0 -0
  6. {bec_lib-4.0.0 → bec_lib-4.0.2}/README.md +0 -0
  7. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/__init__.py +0 -0
  8. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/_print_versions.py +0 -0
  9. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/acl_login.py +0 -0
  10. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/actors.py +0 -0
  11. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/alarm_handler.py +0 -0
  12. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/atlas_models.py +0 -0
  13. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/bec_errors.py +0 -0
  14. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/bec_service.py +0 -0
  15. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/bec_yaml_loader.py +0 -0
  16. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/bl_state_manager.py +0 -0
  17. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/bl_states.py +0 -0
  18. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/builtin_actor_hli.py +0 -0
  19. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/callback_handler.py +0 -0
  20. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/channel_monitor.py +0 -0
  21. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/client.py +0 -0
  22. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/codecs.py +0 -0
  23. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/config_helper.py +0 -0
  24. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/config_values.py +0 -0
  25. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/configs/__init__.py +0 -0
  26. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/configs/demo_config.yaml +0 -0
  27. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/configs/openapi_schema.json +0 -0
  28. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/connector.py +0 -0
  29. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/dap_plugin_objects.py +0 -0
  30. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/dap_plugins.py +0 -0
  31. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/device.py +0 -0
  32. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/devicemanager.py +0 -0
  33. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/endpoints.py +0 -0
  34. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/file_utils.py +0 -0
  35. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/live_scan_data.py +0 -0
  36. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/lmfit_serializer.py +0 -0
  37. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/logger.py +0 -0
  38. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/macro_update_handler.py +0 -0
  39. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/messaging_hooks.py +0 -0
  40. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/messaging_services.py +0 -0
  41. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/metadata_schema.py +0 -0
  42. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/numpy_encoder.py +0 -0
  43. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/observer.py +0 -0
  44. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/pdf_writer.py +0 -0
  45. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/plugin_helper.py +0 -0
  46. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/procedures/__init__.py +0 -0
  47. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/procedures/helper.py +0 -0
  48. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/procedures/hli.py +0 -0
  49. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/procedures/status.py +0 -0
  50. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/queue_items.py +0 -0
  51. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/redis_connector/__init__.py +0 -0
  52. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/redis_connector/constants.py +0 -0
  53. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/redis_connector/hli.py +0 -0
  54. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/redis_connector/managed_redis_connection.py +0 -0
  55. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/redis_connector/streams.py +0 -0
  56. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/redis_connector/validation.py +0 -0
  57. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/request_context.py +0 -0
  58. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/request_items.py +0 -0
  59. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/scan_args.py +0 -0
  60. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/scan_data_container.py +0 -0
  61. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/scan_history.py +0 -0
  62. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/scan_input_validator.py +0 -0
  63. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/scan_items.py +0 -0
  64. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/scan_manager.py +0 -0
  65. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/scan_number_container.py +0 -0
  66. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/scan_repeat.py +0 -0
  67. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/scan_report.py +0 -0
  68. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/scans.py +0 -0
  69. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/scibec_validator.py +0 -0
  70. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/script_executor.py +0 -0
  71. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/serialization.py +0 -0
  72. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/serialization_registry.py +0 -0
  73. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/service_config.py +0 -0
  74. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/signature_serializer.py +0 -0
  75. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/tests/__init__.py +0 -0
  76. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/tests/fixtures.py +0 -0
  77. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/tests/test_config.yaml +0 -0
  78. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/tests/test_service_config.yaml +0 -0
  79. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/tests/utils.py +0 -0
  80. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/user_macros.py +0 -0
  81. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/utils/__init__.py +0 -0
  82. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/utils/copier_jinja_filters.py +0 -0
  83. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/utils/deprecation.py +0 -0
  84. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/utils/import_utils.py +0 -0
  85. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/utils/json_extended.py +0 -0
  86. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/utils/plugin_manager/__init__.py +0 -0
  87. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/utils/plugin_manager/_constants.py +0 -0
  88. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/utils/plugin_manager/_util.py +0 -0
  89. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/utils/plugin_manager/create/__init__.py +0 -0
  90. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/utils/plugin_manager/create/device.py +0 -0
  91. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/utils/plugin_manager/create/scan.py +0 -0
  92. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/utils/plugin_manager/create/templates/scan.py.jinja +0 -0
  93. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/utils/plugin_manager/main.py +0 -0
  94. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/utils/proxy.py +0 -0
  95. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/utils/pydantic_pretty_print.py +0 -0
  96. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/utils/rpc_utils.py +0 -0
  97. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/utils/scan_utils.py +0 -0
  98. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/utils/threading_utils.py +0 -0
  99. {bec_lib-4.0.0 → bec_lib-4.0.2}/bec_lib/utils/user_acls_test.py +0 -0
  100. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/__init__.py +0 -0
  101. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/_additional_for_signature_serializer_test.py +0 -0
  102. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/conftest.py +0 -0
  103. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_acl_login.py +0 -0
  104. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_alarm_handler.py +0 -0
  105. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_atlas_models.py +0 -0
  106. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_beamline_states.py +0 -0
  107. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_bec_logger.py +0 -0
  108. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_bec_service.py +0 -0
  109. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_callback_handler.py +0 -0
  110. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_channel_monitor.py +0 -0
  111. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_client.py +0 -0
  112. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_config_helper.py +0 -0
  113. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_core_utils.py +0 -0
  114. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_dap_plugins.py +0 -0
  115. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_data_container.py +0 -0
  116. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_deprecation.py +0 -0
  117. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_device_hashing.py +0 -0
  118. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_device_manager.py +0 -0
  119. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_devices.py +0 -0
  120. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_file_utils.py +0 -0
  121. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_hli_connector_fakeredis.py +0 -0
  122. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_hli_redis_connector.py +0 -0
  123. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_import_utils.py +0 -0
  124. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_jinja_plugins.py +0 -0
  125. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_lmfit_serializer.py +0 -0
  126. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_managed_config_values.py +0 -0
  127. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_managed_redis_connection.py +0 -0
  128. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_messaging_hooks.py +0 -0
  129. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_messaging_service.py +0 -0
  130. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_metadata_schema.py +0 -0
  131. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_observer.py +0 -0
  132. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_pdf_writer.py +0 -0
  133. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_plugin_helper.py +0 -0
  134. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_plugin_manager.py +0 -0
  135. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_plugin_system.py +0 -0
  136. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_pydantic_pretty_print.py +0 -0
  137. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_queue_items.py +0 -0
  138. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_redis_connector_fakeredis.py +0 -0
  139. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_scan_context.py +0 -0
  140. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_scan_data.py +0 -0
  141. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_scan_history.py +0 -0
  142. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_scan_items.py +0 -0
  143. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_scan_manager.py +0 -0
  144. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_scan_object.py +0 -0
  145. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_scan_repeat.py +0 -0
  146. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_scan_report.py +0 -0
  147. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_scibec_validator.py +0 -0
  148. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_script_executor.py +0 -0
  149. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_serializer.py +0 -0
  150. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_signature_serializer.py +0 -0
  151. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_signature_serializer_with_future_import.py +0 -0
  152. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_stream_subs.py +0 -0
  153. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_user_macros.py +0 -0
  154. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_utils.py +0 -0
  155. {bec_lib-4.0.0 → bec_lib-4.0.2}/tests/test_yaml_loader.py +0 -0
  156. {bec_lib-4.0.0 → bec_lib-4.0.2}/util_scripts/init_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: bec_lib
3
- Version: 4.0.0
3
+ Version: 4.0.2
4
4
  Summary: BEC library
5
5
  Project-URL: Bug Tracker, https://github.com/bec-project/bec/issues
6
6
  Project-URL: Homepage, https://github.com/bec-project/bec
@@ -731,8 +731,8 @@ class DeviceAsyncUpdate(BaseModel):
731
731
  """Model for validating async update metadata sent with device data.
732
732
 
733
733
  The async update metadata controls how data is aggregated into datasets during a scan:
734
- - add: Appends data to the existing dataset along the first axis
735
- - add_slice: Appends a slice of data at a specific index (max 2D datasets)
734
+ - add: Appends data to the existing dataset along the first axis; ignores index
735
+ - add_slice: Extends the contents of the row selected by a nonnegative index (2D datasets only)
736
736
  - replace: Replaces the existing dataset (written after scan completion)
737
737
 
738
738
  Args:
@@ -741,8 +741,10 @@ class DeviceAsyncUpdate(BaseModel):
741
741
  Use None for unlimited dimensions. E.g., [None] for 1D unlimited.
742
742
  None values must only appear at the beginning (e.g., [None, 1024] is valid, [1024, None] is not).
743
743
  When all dimensions are None, maximum is 2 dimensions (e.g., [None, None] is valid, [None, None, None] is not).
744
- For 'add_slice' type, max_shape cannot exceed two dimensions.
745
- index (int, optional): Row index for 'add_slice' operations. Required only for 'add_slice' type.
744
+ For 'add_slice' type, max_shape must have exactly two dimensions.
745
+ index (int, optional): Required nonnegative row index for 'add_slice' operations.
746
+ Must be smaller than max_shape[0] when the first axis is bounded.
747
+ Ignored by 'add' and 'replace'. Use 'add' to append new rows.
746
748
 
747
749
  Examples:
748
750
  >>> DeviceAsyncUpdate(type="add", max_shape=[None])
@@ -783,23 +785,31 @@ class DeviceAsyncUpdate(BaseModel):
783
785
  )
784
786
 
785
787
  # If all dimensions are None, maximum is 2 dimensions
786
- if all(dim is None for dim in self.max_shape):
787
- if len(self.max_shape) > 2:
788
- raise ValueError(
789
- f"Invalid max_shape {self.max_shape}: when all dimensions are None, "
790
- f"maximum number of dimensions is 2, got {len(self.max_shape)}"
791
- )
788
+ if all(dim is None for dim in self.max_shape) and len(self.max_shape) > 2:
789
+ raise ValueError(
790
+ f"Invalid max_shape {self.max_shape}: when all dimensions are None, "
791
+ f"maximum number of dimensions is 2, got {len(self.max_shape)}"
792
+ )
792
793
 
793
794
  if self.type == "add_slice":
795
+ max_shape = self.max_shape
794
796
  if self.index is None:
795
797
  raise ValueError("index is required for async update type 'add_slice'")
796
- if not isinstance(self.index, int) or (self.index < -1):
798
+ if not isinstance(self.index, int) or (self.index < 0):
799
+ raise ValueError(
800
+ f"index must be an integer >= 0 for async update type 'add_slice', got {self.index}"
801
+ )
802
+ if max_shape is not None and len(max_shape) != 2:
797
803
  raise ValueError(
798
- f"index must be an integer >= -1 for async update type 'add_slice', got {self.index}"
804
+ "max_shape for async update type 'add_slice' must have exactly two dimensions, "
805
+ f"got {len(max_shape)} dimensions"
799
806
  )
800
- if self.max_shape is not None and len(self.max_shape) > 2:
807
+ # max_shape is required for add_slice and has been validated above.
808
+ # pylint: disable-next=unsubscriptable-object
809
+ if max_shape is not None and max_shape[0] is not None and self.index >= max_shape[0]:
801
810
  raise ValueError(
802
- f"max_shape for async update type 'add_slice' cannot exceed two dimensions, got {len(self.max_shape)} dimensions"
811
+ "index must be smaller than max_shape[0] for async update type 'add_slice', "
812
+ f"got index {self.index} with max_shape {max_shape}"
803
813
  )
804
814
  return self
805
815
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "bec_lib"
7
- version = "4.0.0"
7
+ version = "4.0.2"
8
8
  description = "BEC library"
9
9
  requires-python = ">=3.11"
10
10
  classifiers = [
@@ -630,12 +630,11 @@ class TestDeviceAsyncUpdate:
630
630
  assert update.max_shape == [None, 1024]
631
631
  assert update.index == 5
632
632
 
633
- def test_valid_add_slice_type_with_1d_max_shape(self):
634
- """Test add_slice type with 1D max_shape"""
635
- update = messages.DeviceAsyncUpdate(type="add_slice", max_shape=[None], index=0)
636
- assert update.type == "add_slice"
637
- assert update.max_shape == [None]
638
- assert update.index == 0
633
+ @pytest.mark.parametrize("max_shape", [[None], [100]])
634
+ def test_invalid_add_slice_type_with_1d_max_shape(self, max_shape):
635
+ """Test that add_slice rejects one-dimensional target datasets."""
636
+ with pytest.raises(pydantic.ValidationError, match="must have exactly two dimensions"):
637
+ messages.DeviceAsyncUpdate(type="add_slice", max_shape=max_shape, index=0)
639
638
 
640
639
  def test_valid_add_slice_type_with_variable_size(self):
641
640
  """Test add_slice type with variable size in second dimension"""
@@ -673,7 +672,7 @@ class TestDeviceAsyncUpdate:
673
672
  """Test that add_slice type cannot have more than 2D max_shape"""
674
673
  with pytest.raises(pydantic.ValidationError) as exc_info:
675
674
  messages.DeviceAsyncUpdate(type="add_slice", max_shape=[None, 1024, 1024], index=0)
676
- assert "cannot exceed two dimensions" in str(exc_info.value)
675
+ assert "must have exactly two dimensions" in str(exc_info.value)
677
676
 
678
677
  def test_invalid_max_shape_none_in_middle(self):
679
678
  """Test that None values cannot appear in the middle of max_shape"""
@@ -713,8 +712,7 @@ class TestDeviceAsyncUpdate:
713
712
  assert update.max_shape == max_shape
714
713
 
715
714
  @pytest.mark.parametrize(
716
- "max_shape,index",
717
- [([None], 0), ([None, 100], 5), ([None, None], 10), ([100], 2), ([100, 200], 15)],
715
+ "max_shape,index", [([None, 100], 5), ([None, None], 10), ([100, 200], 15)]
718
716
  )
719
717
  def test_valid_max_shape_patterns_for_add_slice(self, max_shape, index):
720
718
  """Test various valid max_shape patterns for add_slice type"""
@@ -768,13 +766,37 @@ class TestDeviceAsyncUpdate:
768
766
  messages.DeviceAsyncUpdate(type="add", max_shape=[None, bad_value])
769
767
  assert "all non-None dimensions must be positive integers" in str(exc_info.value)
770
768
 
771
- def test_invalid_add_slice_negative_index(self):
772
- """Test that negative index less than -1 is rejected for add_slice"""
769
+ @pytest.mark.parametrize("index", [-1, -2])
770
+ @pytest.mark.parametrize("max_shape", [[None, 1024], [None, None], [100, 1024]])
771
+ def test_invalid_add_slice_negative_index(self, index, max_shape):
772
+ """Test that all negative row indices are rejected for add_slice"""
773
773
  with pytest.raises(pydantic.ValidationError) as exc_info:
774
- messages.DeviceAsyncUpdate(type="add_slice", max_shape=[None, 1024], index=-2)
775
- assert "index must be an integer >= -1" in str(exc_info.value)
774
+ messages.DeviceAsyncUpdate(type="add_slice", max_shape=max_shape, index=index)
775
+ assert "index must be an integer >= 0" in str(exc_info.value)
776
+
777
+ @pytest.mark.parametrize("max_shape", [[4, 4], [4, 1024]])
778
+ @pytest.mark.parametrize("index", [4, 5])
779
+ def test_invalid_add_slice_index_exceeds_row_limit(self, max_shape, index):
780
+ """Reject indices at or beyond the finite first-axis limit."""
781
+ with pytest.raises(
782
+ pydantic.ValidationError, match=r"index must be smaller than max_shape\[0\]"
783
+ ):
784
+ messages.DeviceAsyncUpdate(type="add_slice", max_shape=max_shape, index=index)
785
+
786
+ @pytest.mark.parametrize("max_shape", [[4, 4], [4, 1024]])
787
+ @pytest.mark.parametrize("index", [0, 3])
788
+ def test_valid_add_slice_index_within_row_limit(self, max_shape, index):
789
+ """Both the first and last row within the finite limit are valid."""
790
+ update = messages.DeviceAsyncUpdate(type="add_slice", max_shape=max_shape, index=index)
791
+ assert update.index == index
792
+
793
+ @pytest.mark.parametrize("update_type", ["add", "replace"])
794
+ def test_other_update_types_ignore_index_row_limit(self, update_type):
795
+ """The row-index bound applies only to add_slice."""
796
+ update = messages.DeviceAsyncUpdate(type=update_type, max_shape=[4, 1024], index=4)
797
+ assert update.index == 4
776
798
 
777
- @pytest.mark.parametrize("index", [0, 1, 10, 100, -1])
799
+ @pytest.mark.parametrize("index", [0, 1, 10, 100])
778
800
  def test_valid_add_slice_various_indices(self, index):
779
801
  """Test various valid index values for add_slice type"""
780
802
  update = messages.DeviceAsyncUpdate(type="add_slice", max_shape=[None, 1024], index=index)
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