bec-server 4.1.0__tar.gz → 4.1.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.
- {bec_server-4.1.0 → bec_server-4.1.2}/PKG-INFO +1 -1
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/file_writer/file_writer_manager.py +60 -56
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/scan_actions.py +1 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/scan_base.py +3 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/pyproject.toml +1 -1
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_file_writer/test_file_writer_manager.py +316 -36
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/scans/test_scan_actions.py +24 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/.gitignore +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/.zed/debug.json +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/README.md +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/__init__.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/actors/__init__.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/actors/actor.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/actors/builtin_actor_manager.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/actors/manager.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/actors/scan_interlock.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/actors/worker.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/bec_server_utils/__init__.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/bec_server_utils/launch.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/bec_server_utils/service_handler.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/bec_server_utils/subprocess_launch.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/bec_server_utils/tmux_launch.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/data_processing/__init__.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/data_processing/cli/__init__.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/data_processing/cli/launch.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/data_processing/dap_server.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/data_processing/dap_service.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/data_processing/dap_service_manager.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/data_processing/lmfit1d_service.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/device_server/__init__.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/device_server/bec_message_handler.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/device_server/cli/__init__.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/device_server/cli/launch.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/device_server/device_server.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/device_server/devices/__init__.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/device_server/devices/config_update_handler.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/device_server/devices/device_serializer.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/device_server/devices/devicemanager.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/device_server/friendly_device_exceptions.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/device_server/ophyd_callback_monitor.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/device_server/rpc_handler.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/device_server/tests/__init__.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/device_server/tests/utils.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/file_writer/__init__.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/file_writer/async_writer.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/file_writer/cli/__init__.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/file_writer/cli/launch.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/file_writer/default_writer.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/file_writer/file_writer.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/file_writer/merged_dicts.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/procedures/Containerfile.bec_linux_base +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/procedures/Containerfile.requirements +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/procedures/Containerfile.worker +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/procedures/__init__.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/procedures/_dev_runner.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/procedures/builtin_procedures.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/procedures/constants.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/procedures/container_utils.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/procedures/container_worker.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/procedures/manager.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/procedures/oop_worker_base.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/procedures/procedure_registry.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/procedures/protocol.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/procedures/subprocess_worker.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/procedures/worker_base.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_bundler/__init__.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_bundler/bec_emitter.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_bundler/cli/__init__.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_bundler/cli/launch.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_bundler/emitter.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_bundler/scan_bundler.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/__init__.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/beamline_state_manager.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/cli/__init__.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/cli/launch.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/device_lock_registry.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/direct_scan_worker.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/errors.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/instruction_handler.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scan_assembler.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scan_guard.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scan_manager.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scan_queue.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scan_server.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scan_worker.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/__init__.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/acquire.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/cont_line_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/device_rpc.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/fermat_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/grid_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/hexagonal_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/line_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/line_sweep_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/list_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/log_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/move.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/multi_region_grid_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/multi_region_line_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/path_optimization.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/position_generators.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/round_roi_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/round_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/scan_argument_modifier.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/scan_components.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/scan_modifier.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/scan_status.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/time_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/scans/updated_move.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/tests/__init__.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/tests/fixtures.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/tests/scan_fixtures.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/tests/scan_hook_tests.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scan_server/tests/utils.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scihub/__init__.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scihub/atlas/__init__.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scihub/atlas/atlas_active_client_emitter.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scihub/atlas/atlas_connector.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scihub/atlas/atlas_forwarder.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scihub/atlas/atlas_metadata_handler.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scihub/atlas/atlas_plugin_repo_emitter.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scihub/atlas/config_handler.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scihub/cli/__init__.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scihub/cli/launch.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scihub/scihub.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scihub/service_handler/__init__.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/scihub/service_handler/service_handler.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/test/__init__.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/test/actor_test_utils.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/bec_server/test/helpers.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_actors/test_actors.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_bec_server_utils/test_main.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_bec_server_utils/test_service_handler.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_bec_server_utils/test_subprocess_launch.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_bec_server_utils/test_tmux_launch.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_data_processing/conftest.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_data_processing/test_dap_cli_launch.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_data_processing/test_dap_server.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_data_processing/test_dap_service_manager.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_data_processing/test_lmfit1d_service.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_device_server/conftest.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_device_server/test_config_handler.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_device_server/test_device_instructions.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_device_server/test_device_manager_ds.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_device_server/test_device_serializer.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_device_server/test_device_server.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_device_server/test_device_server_cli_launch.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_device_server/test_ophyd_callback_monitor.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_device_server/test_rpc_handler.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_file_writer/conftest.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_file_writer/test_async_file_writer.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_file_writer/test_file_writer.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_file_writer/test_file_writer_cli_launch.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_bundler/conftest.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_bundler/test_bec_emitter.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_bundler/test_scan_bundler.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_bundler/test_scan_bundler_cli_launch.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/conftest.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/scans/__init__.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/scans/test_acquire.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/scans/test_cont_line_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/scans/test_device_rpc_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/scans/test_fermat_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/scans/test_grid_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/scans/test_hexagonal_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/scans/test_line_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/scans/test_line_sweep_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/scans/test_list_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/scans/test_log_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/scans/test_move_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/scans/test_multi_region_grid_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/scans/test_multi_region_line_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/scans/test_position_generators.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/scans/test_relative_scan_return_to_start.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/scans/test_round_roi_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/scans/test_round_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/scans/test_scan_components.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/scans/test_scan_status.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/scans/test_time_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/scans/test_updated_move_scan.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/test_beamline_state_manager.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/test_builtin_actor_manager.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/test_container_utils.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/test_device_lock_registry.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/test_direct_scan_worker.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/test_instruction_handler.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/test_path_optimization.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/test_procedure_container_worker.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/test_procedures.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/test_scan_argument_modifier.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/test_scan_assembler.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/test_scan_fixture_imports.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/test_scan_guard.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/test_scan_interlock.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/test_scan_modifier.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/test_scan_server_cli_launch.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/test_scan_server_queue.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/test_scan_server_scan_manager.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scan_server/test_scan_worker.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scihub/conftest.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scihub/test_atlas_active_client_emitter.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scihub/test_atlas_config_handler.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scihub/test_atlas_connector.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scihub/test_atlas_forwarder.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scihub/test_atlas_metadata_handler.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scihub/test_atlas_plugin_repo_emitter.py +0 -0
- {bec_server-4.1.0 → bec_server-4.1.2}/tests/tests_scihub/test_scihub_cli_launch.py +0 -0
|
@@ -4,7 +4,7 @@ import os
|
|
|
4
4
|
import threading
|
|
5
5
|
import time
|
|
6
6
|
import traceback
|
|
7
|
-
from collections import defaultdict
|
|
7
|
+
from collections import defaultdict, deque
|
|
8
8
|
|
|
9
9
|
from bec_lib import messages, plugin_helper
|
|
10
10
|
from bec_lib.alarm_handler import Alarms
|
|
@@ -60,12 +60,23 @@ class ScanStorage:
|
|
|
60
60
|
"""
|
|
61
61
|
self.scan_segments[point_id] = data
|
|
62
62
|
|
|
63
|
+
@property
|
|
64
|
+
def baseline_ready(self) -> bool:
|
|
65
|
+
"""Whether no baseline read was requested or its bundled data has arrived."""
|
|
66
|
+
if self.status_msg is None:
|
|
67
|
+
return True
|
|
68
|
+
return not self.status_msg.info.get("baseline_readout_requested", False) or bool(
|
|
69
|
+
self.baseline
|
|
70
|
+
)
|
|
71
|
+
|
|
63
72
|
def ready_to_write(self) -> bool:
|
|
64
73
|
"""
|
|
65
74
|
Check if the scan is ready to be written to file.
|
|
66
75
|
"""
|
|
67
76
|
if self.forced_finish:
|
|
68
77
|
return True
|
|
78
|
+
if not self.baseline_ready:
|
|
79
|
+
return False
|
|
69
80
|
if self.enforce_sync:
|
|
70
81
|
# wait for all points to be received. Since this method will be called for every
|
|
71
82
|
# update of the scan segments, we can also accept to write after the scan is finished
|
|
@@ -106,8 +117,16 @@ class FileWriterManager(BECService):
|
|
|
106
117
|
self.available_beamline_states: list[messages.BeamlineStateConfig] = []
|
|
107
118
|
self.beamline_state_subscriptions: set[str] = set()
|
|
108
119
|
self.beamline_states: dict[str, messages.BeamlineStateMessage] = {}
|
|
120
|
+
self.scan_storage: dict[str, ScanStorage] = {}
|
|
121
|
+
self._pending_baseline_writes: dict[str, dict] = {}
|
|
122
|
+
self._pending_file_references: dict[str, dict[str, messages.FileMessage]] = {}
|
|
123
|
+
self._finished_scan_ids: deque[str] = deque(maxlen=1000)
|
|
109
124
|
self.connector.register(MessageEndpoints.scan_segment(), cb=self._scan_segment_callback)
|
|
110
125
|
self.connector.register(MessageEndpoints.scan_status(), cb=self._scan_status_callback)
|
|
126
|
+
self.connector.register(MessageEndpoints.scan_baseline(), cb=self._scan_baseline_callback)
|
|
127
|
+
self.connector.register(
|
|
128
|
+
patterns=MessageEndpoints.public_file("*", "*"), cb=self._file_reference_callback
|
|
129
|
+
)
|
|
111
130
|
self.connector.register(
|
|
112
131
|
patterns=MessageEndpoints.device_read_configuration("*"), # type: ignore
|
|
113
132
|
cb=self._device_configuration_callback,
|
|
@@ -122,7 +141,6 @@ class FileWriterManager(BECService):
|
|
|
122
141
|
)
|
|
123
142
|
self._run_storage_copy_plugin = self._load_storage_copy_plugin()
|
|
124
143
|
self.async_writer = None
|
|
125
|
-
self.scan_storage: dict[str, ScanStorage] = {}
|
|
126
144
|
self.file_writer = HDF5FileWriter(self)
|
|
127
145
|
self.status = messages.BECStatus.RUNNING
|
|
128
146
|
self.refresh_device_configs()
|
|
@@ -141,6 +159,43 @@ class FileWriterManager(BECService):
|
|
|
141
159
|
msg = msg.value
|
|
142
160
|
self.update_scan_storage_with_status(msg)
|
|
143
161
|
|
|
162
|
+
def _scan_baseline_callback(self, msg: MessageObject) -> None:
|
|
163
|
+
baseline_msg = msg.value
|
|
164
|
+
with self._lock:
|
|
165
|
+
if baseline_msg.scan_id in self._finished_scan_ids:
|
|
166
|
+
return
|
|
167
|
+
storage = self.scan_storage.get(baseline_msg.scan_id)
|
|
168
|
+
if storage is None:
|
|
169
|
+
self._pending_baseline_writes[baseline_msg.scan_id] = baseline_msg.data
|
|
170
|
+
return
|
|
171
|
+
storage.baseline = baseline_msg.data
|
|
172
|
+
self.check_storage_status(scan_id=baseline_msg.scan_id)
|
|
173
|
+
|
|
174
|
+
def _file_reference_callback(self, msg: MessageObject) -> None:
|
|
175
|
+
_, scan_id, _, name = msg.topic.rsplit("/", 3)
|
|
176
|
+
file_msg = msg.value
|
|
177
|
+
if name == "master" or file_msg.is_master_file:
|
|
178
|
+
return
|
|
179
|
+
with self._lock:
|
|
180
|
+
if scan_id in self._finished_scan_ids:
|
|
181
|
+
return
|
|
182
|
+
storage = self.scan_storage.get(scan_id)
|
|
183
|
+
if storage is None:
|
|
184
|
+
self._pending_file_references.setdefault(scan_id, {})[name] = file_msg
|
|
185
|
+
return
|
|
186
|
+
storage.file_references[name] = file_msg
|
|
187
|
+
|
|
188
|
+
def _get_or_create_scan_storage(self, scan_id: str, scan_number: int | None) -> ScanStorage:
|
|
189
|
+
"""Create scan storage and transfer any notifications received before its status or data."""
|
|
190
|
+
with self._lock:
|
|
191
|
+
storage = self.scan_storage.get(scan_id)
|
|
192
|
+
if storage is None:
|
|
193
|
+
storage = ScanStorage(scan_number=scan_number, scan_id=scan_id)
|
|
194
|
+
storage.baseline = self._pending_baseline_writes.pop(scan_id, {})
|
|
195
|
+
storage.file_references = self._pending_file_references.pop(scan_id, {})
|
|
196
|
+
self.scan_storage[scan_id] = storage
|
|
197
|
+
return storage
|
|
198
|
+
|
|
144
199
|
def _device_configuration_callback(self, msg):
|
|
145
200
|
topic, msg = msg.topic, msg.value
|
|
146
201
|
device = topic.split("/")[-1]
|
|
@@ -255,10 +310,7 @@ class FileWriterManager(BECService):
|
|
|
255
310
|
if scan_id is None:
|
|
256
311
|
return
|
|
257
312
|
|
|
258
|
-
|
|
259
|
-
self.scan_storage[scan_id] = ScanStorage(
|
|
260
|
-
scan_number=msg.content["info"].get("scan_number"), scan_id=scan_id
|
|
261
|
-
)
|
|
313
|
+
self._get_or_create_scan_storage(scan_id, msg.content["info"].get("scan_number"))
|
|
262
314
|
|
|
263
315
|
for state in self.beamline_states.values():
|
|
264
316
|
if state.name not in self.scan_storage[scan_id].beamline_states:
|
|
@@ -324,60 +376,13 @@ class FileWriterManager(BECService):
|
|
|
324
376
|
scan_id = msg.content.get("scan_id")
|
|
325
377
|
if scan_id is None:
|
|
326
378
|
return
|
|
327
|
-
|
|
328
|
-
self.scan_storage[scan_id] = ScanStorage(
|
|
329
|
-
scan_number=msg.metadata.get("scan_number"), scan_id=scan_id
|
|
330
|
-
)
|
|
379
|
+
self._get_or_create_scan_storage(scan_id, msg.metadata.get("scan_number"))
|
|
331
380
|
self.scan_storage[scan_id].append(
|
|
332
381
|
point_id=msg.content.get("point_id"), data=msg.content.get("data")
|
|
333
382
|
)
|
|
334
383
|
logger.debug(msg.content.get("point_id"))
|
|
335
384
|
self.check_storage_status(scan_id=scan_id)
|
|
336
385
|
|
|
337
|
-
def update_baseline_reading(self, scan_id: str) -> None:
|
|
338
|
-
"""
|
|
339
|
-
Update the baseline reading for the scan.
|
|
340
|
-
|
|
341
|
-
Args:
|
|
342
|
-
scan_id (str): Scan ID
|
|
343
|
-
"""
|
|
344
|
-
if not self.scan_storage.get(scan_id):
|
|
345
|
-
return
|
|
346
|
-
if self.scan_storage[scan_id].baseline:
|
|
347
|
-
return
|
|
348
|
-
baseline = self.connector.get(MessageEndpoints.public_scan_baseline(scan_id))
|
|
349
|
-
if not baseline:
|
|
350
|
-
return
|
|
351
|
-
self.scan_storage[scan_id].baseline = baseline.content["data"]
|
|
352
|
-
return
|
|
353
|
-
|
|
354
|
-
def update_file_references(self, scan_id: str) -> None:
|
|
355
|
-
"""
|
|
356
|
-
Update the file references for the scan.
|
|
357
|
-
All external files ought to be announced to the endpoint public_file before the scan finishes. This function
|
|
358
|
-
retrieves the file references and adds them to the scan storage.
|
|
359
|
-
|
|
360
|
-
Args:
|
|
361
|
-
scan_id (str): Scan ID
|
|
362
|
-
"""
|
|
363
|
-
if not self.scan_storage.get(scan_id):
|
|
364
|
-
return
|
|
365
|
-
msgs = self.connector.keys(MessageEndpoints.public_file(scan_id, "*"))
|
|
366
|
-
if not msgs:
|
|
367
|
-
return
|
|
368
|
-
|
|
369
|
-
# extract name from 'public/<scan_id>/file/<name>'
|
|
370
|
-
names = [msg.decode().split("/")[-1] for msg in msgs]
|
|
371
|
-
file_msgs = [
|
|
372
|
-
self.connector.get(MessageEndpoints.public_file(scan_id=scan_id, name=name))
|
|
373
|
-
for name in names
|
|
374
|
-
]
|
|
375
|
-
if not file_msgs:
|
|
376
|
-
return
|
|
377
|
-
for name, file_msg in zip(names, file_msgs):
|
|
378
|
-
self.scan_storage[scan_id].file_references[name] = file_msg
|
|
379
|
-
return
|
|
380
|
-
|
|
381
386
|
def update_device_configuration(self, device: str, msg: messages.DeviceMessage) -> None:
|
|
382
387
|
"""
|
|
383
388
|
Update the device configuration. Note that this is a global configuration and not specific to a scan.
|
|
@@ -397,8 +402,6 @@ class FileWriterManager(BECService):
|
|
|
397
402
|
with self._lock:
|
|
398
403
|
if not self.scan_storage.get(scan_id):
|
|
399
404
|
return
|
|
400
|
-
self.update_baseline_reading(scan_id)
|
|
401
|
-
self.update_file_references(scan_id)
|
|
402
405
|
if self.scan_storage[scan_id].ready_to_write():
|
|
403
406
|
self.write_file(scan_id)
|
|
404
407
|
|
|
@@ -481,6 +484,7 @@ class FileWriterManager(BECService):
|
|
|
481
484
|
logger.info(f"Writing to file {file_path} took {time.time() - start_time:.2f} seconds.")
|
|
482
485
|
|
|
483
486
|
self.scan_storage.pop(scan_id)
|
|
487
|
+
self._finished_scan_ids.append(scan_id)
|
|
484
488
|
self.connector.set_and_publish(
|
|
485
489
|
MessageEndpoints.public_file(scan_id, "master"),
|
|
486
490
|
messages.FileMessage(file_path=file_path, done=True, successful=successful),
|
|
@@ -116,6 +116,9 @@ class ScanInfo(BaseModel):
|
|
|
116
116
|
metadata: Annotated[dict, Field(description="Additional metadata for the scan")] = {}
|
|
117
117
|
|
|
118
118
|
# progress tracking
|
|
119
|
+
baseline_readout_requested: Annotated[
|
|
120
|
+
bool, Field(description="Whether a read of nonempty baseline devices was requested.")
|
|
121
|
+
] = False
|
|
119
122
|
num_monitored_readouts: Annotated[
|
|
120
123
|
int,
|
|
121
124
|
Field(
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# pylint: skip-file
|
|
2
2
|
import os
|
|
3
|
+
import threading
|
|
3
4
|
import time
|
|
4
5
|
from unittest import mock
|
|
5
6
|
|
|
@@ -201,15 +202,11 @@ def test_check_storage_status(file_writer_manager_mock, scan_storage_mock):
|
|
|
201
202
|
|
|
202
203
|
with (
|
|
203
204
|
mock.patch.object(scan_storage_mock, "ready_to_write") as mock_ready_to_write,
|
|
204
|
-
mock.patch.object(file_manager, "update_baseline_reading") as mock_update_baseline,
|
|
205
|
-
mock.patch.object(file_manager, "update_file_references") as mock_update_file_references,
|
|
206
205
|
mock.patch.object(file_manager, "write_file") as mock_write_file,
|
|
207
206
|
):
|
|
208
207
|
mock_ready_to_write.return_value = True
|
|
209
208
|
file_manager.check_storage_status(scan_id="scan_id")
|
|
210
209
|
assert mock_ready_to_write.called
|
|
211
|
-
assert mock_update_baseline.call_args == mock.call("scan_id")
|
|
212
|
-
assert mock_update_file_references.call_args == mock.call("scan_id")
|
|
213
210
|
assert mock_write_file.call_args == mock.call("scan_id")
|
|
214
211
|
|
|
215
212
|
|
|
@@ -331,18 +328,6 @@ def test_write_file_renames_tmp_file_on_exception(file_writer_manager_mock, scan
|
|
|
331
328
|
mock_rename.assert_called_once_with(tmp_file_path, final_file_path)
|
|
332
329
|
|
|
333
330
|
|
|
334
|
-
def test_update_baseline_reading(file_writer_manager_mock, scan_storage_mock):
|
|
335
|
-
file_manager = file_writer_manager_mock
|
|
336
|
-
file_manager.scan_storage["scan_id"] = scan_storage_mock
|
|
337
|
-
with mock.patch.object(file_manager, "connector") as mock_connector:
|
|
338
|
-
mock_connector.get.return_value = messages.ScanBaselineMessage(
|
|
339
|
-
scan_id="scan_id", data={"data": "data"}
|
|
340
|
-
)
|
|
341
|
-
file_manager.update_baseline_reading("scan_id")
|
|
342
|
-
assert file_manager.scan_storage["scan_id"].baseline == {"data": "data"}
|
|
343
|
-
mock_connector.get.assert_called_once_with(MessageEndpoints.public_scan_baseline("scan_id"))
|
|
344
|
-
|
|
345
|
-
|
|
346
331
|
def test_scan_storage_append(scan_storage_mock):
|
|
347
332
|
storage = scan_storage_mock
|
|
348
333
|
storage.append(1, {"data": "data"})
|
|
@@ -358,21 +343,6 @@ def test_scan_storage_ready_to_write(scan_storage_mock):
|
|
|
358
343
|
assert storage.ready_to_write() is True
|
|
359
344
|
|
|
360
345
|
|
|
361
|
-
def test_update_file_references(file_writer_manager_mock):
|
|
362
|
-
file_manager = file_writer_manager_mock
|
|
363
|
-
with mock.patch.object(file_manager, "connector") as mock_connector:
|
|
364
|
-
file_manager.update_file_references("scan_id")
|
|
365
|
-
mock_connector.keys.assert_not_called()
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
def test_update_file_references_gets_keys(file_writer_manager_mock, scan_storage_mock):
|
|
369
|
-
file_manager = file_writer_manager_mock
|
|
370
|
-
file_manager.scan_storage["scan_id"] = scan_storage_mock
|
|
371
|
-
with mock.patch.object(file_manager, "connector") as mock_connector:
|
|
372
|
-
file_manager.update_file_references("scan_id")
|
|
373
|
-
mock_connector.keys.assert_called_once_with(MessageEndpoints.public_file("scan_id", "*"))
|
|
374
|
-
|
|
375
|
-
|
|
376
346
|
def test_update_scan_storage_with_status_ignores_none(file_writer_manager_mock):
|
|
377
347
|
file_manager = file_writer_manager_mock
|
|
378
348
|
file_manager.update_scan_storage_with_status(
|
|
@@ -397,11 +367,7 @@ def test_update_scan_storage_with_status_waits_for_v4_sync_segments(file_writer_
|
|
|
397
367
|
info={"scan_number": 1, "monitor_sync": None},
|
|
398
368
|
)
|
|
399
369
|
|
|
400
|
-
with (
|
|
401
|
-
mock.patch.object(file_manager, "update_baseline_reading"),
|
|
402
|
-
mock.patch.object(file_manager, "update_file_references"),
|
|
403
|
-
mock.patch.object(file_manager, "write_file") as write_file,
|
|
404
|
-
):
|
|
370
|
+
with (mock.patch.object(file_manager, "write_file") as write_file,):
|
|
405
371
|
file_manager.update_scan_storage_with_status(msg)
|
|
406
372
|
|
|
407
373
|
assert storage.enforce_sync is True
|
|
@@ -559,3 +525,317 @@ def test_file_writer_manager_removes_beamline_state_subscription(file_writer_man
|
|
|
559
525
|
file_manager._update_available_beamline_states({"data": msg})
|
|
560
526
|
assert "State1" not in file_manager.beamline_state_subscriptions
|
|
561
527
|
assert "State1" not in file_manager.beamline_states
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
@pytest.mark.parametrize("enforce_sync,monitored", [(True, ["samx"]), (True, []), (False, [])])
|
|
531
|
+
def test_ready_to_write_waits_for_baseline(enforce_sync, monitored):
|
|
532
|
+
storage = ScanStorage(1, "scan_id")
|
|
533
|
+
storage.status_msg = messages.ScanStatusMessage(
|
|
534
|
+
scan_id="scan_id",
|
|
535
|
+
status="closed",
|
|
536
|
+
info={"baseline_readout_requested": True},
|
|
537
|
+
readout_priority={"monitored": monitored, "baseline": ["samz"]},
|
|
538
|
+
)
|
|
539
|
+
storage.enforce_sync = enforce_sync
|
|
540
|
+
storage.scan_finished = True
|
|
541
|
+
storage.num_monitored_readouts = 0
|
|
542
|
+
|
|
543
|
+
assert storage.baseline_ready is False
|
|
544
|
+
assert storage.ready_to_write() is False
|
|
545
|
+
|
|
546
|
+
storage.baseline = {"samz": {"samz": {"value": 42, "timestamp": 1}}}
|
|
547
|
+
|
|
548
|
+
assert storage.baseline_ready is True
|
|
549
|
+
assert storage.ready_to_write() is True
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
@pytest.mark.parametrize("arrival_order", ["baseline_first", "baseline_last"])
|
|
553
|
+
def test_baseline_arrival_finalizes_scan_once(file_writer_manager_mock, arrival_order):
|
|
554
|
+
manager = file_writer_manager_mock
|
|
555
|
+
manager.scan_storage["scan_id"] = ScanStorage(1, "scan_id")
|
|
556
|
+
baseline_msg = messages.ScanBaselineMessage(
|
|
557
|
+
scan_id="scan_id", data={"samz": {"samz": {"value": 42, "timestamp": 1}}}
|
|
558
|
+
)
|
|
559
|
+
baseline_event = MessageObject(
|
|
560
|
+
topic=MessageEndpoints.scan_baseline().endpoint, value=baseline_msg
|
|
561
|
+
)
|
|
562
|
+
closed_msg = messages.ScanStatusMessage(
|
|
563
|
+
scan_id="scan_id",
|
|
564
|
+
status="closed",
|
|
565
|
+
scan_number=1,
|
|
566
|
+
scan_type="software_triggered",
|
|
567
|
+
num_points=1,
|
|
568
|
+
num_monitored_readouts=1,
|
|
569
|
+
readout_priority={"monitored": ["samx"], "baseline": ["samz"]},
|
|
570
|
+
info={"scan_number": 1, "monitor_sync": "bec", "baseline_readout_requested": True},
|
|
571
|
+
)
|
|
572
|
+
segment = messages.ScanMessage(
|
|
573
|
+
scan_id="scan_id", point_id=0, data={"samx": {"samx": {"value": 1, "timestamp": 1}}}
|
|
574
|
+
)
|
|
575
|
+
written_baselines = []
|
|
576
|
+
|
|
577
|
+
def write_file(scan_id):
|
|
578
|
+
written_baselines.append(manager.scan_storage.pop(scan_id).baseline)
|
|
579
|
+
manager._finished_scan_ids.append(scan_id)
|
|
580
|
+
|
|
581
|
+
with (
|
|
582
|
+
mock.patch.object(manager.connector, "get", return_value=None) as get,
|
|
583
|
+
mock.patch.object(manager, "write_file", side_effect=write_file) as write,
|
|
584
|
+
):
|
|
585
|
+
if arrival_order == "baseline_first":
|
|
586
|
+
manager._scan_baseline_callback(baseline_event)
|
|
587
|
+
write.assert_not_called()
|
|
588
|
+
|
|
589
|
+
manager.insert_to_scan_storage(segment)
|
|
590
|
+
write.assert_not_called()
|
|
591
|
+
manager.update_scan_storage_with_status(closed_msg)
|
|
592
|
+
|
|
593
|
+
if arrival_order == "baseline_last":
|
|
594
|
+
write.assert_not_called()
|
|
595
|
+
manager._scan_baseline_callback(baseline_event)
|
|
596
|
+
|
|
597
|
+
write.assert_called_once_with("scan_id")
|
|
598
|
+
assert written_baselines == [baseline_msg.data]
|
|
599
|
+
get.assert_not_called()
|
|
600
|
+
assert "scan_id" not in manager.scan_storage
|
|
601
|
+
|
|
602
|
+
manager._scan_baseline_callback(baseline_event)
|
|
603
|
+
write.assert_called_once_with("scan_id")
|
|
604
|
+
assert "scan_id" not in manager.scan_storage
|
|
605
|
+
assert manager._pending_baseline_writes == {}
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def test_baseline_callback_does_not_create_scan_storage(file_writer_manager_mock):
|
|
609
|
+
manager = file_writer_manager_mock
|
|
610
|
+
baseline = messages.ScanBaselineMessage(scan_id="unknown_scan", data={"samz": {}})
|
|
611
|
+
with mock.patch.object(manager, "write_file") as write:
|
|
612
|
+
manager._scan_baseline_callback(
|
|
613
|
+
MessageObject(topic=MessageEndpoints.scan_baseline().endpoint, value=baseline)
|
|
614
|
+
)
|
|
615
|
+
write.assert_not_called()
|
|
616
|
+
assert manager.scan_storage == {}
|
|
617
|
+
assert manager._pending_baseline_writes == {"unknown_scan": baseline.data}
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
@pytest.mark.parametrize("status", ["aborted", "halted", "user_completed"])
|
|
621
|
+
def test_interrupted_scan_does_not_wait_for_baseline(file_writer_manager_mock, status):
|
|
622
|
+
manager = file_writer_manager_mock
|
|
623
|
+
storage = ScanStorage(1, "scan_id")
|
|
624
|
+
manager.scan_storage["scan_id"] = storage
|
|
625
|
+
status_msg = messages.ScanStatusMessage(
|
|
626
|
+
scan_id="scan_id",
|
|
627
|
+
status=status,
|
|
628
|
+
scan_number=1,
|
|
629
|
+
scan_type="software_triggered",
|
|
630
|
+
num_monitored_readouts=1,
|
|
631
|
+
readout_priority={"monitored": ["samx"], "baseline": ["samz"]},
|
|
632
|
+
info={"scan_number": 1, "monitor_sync": "bec", "baseline_readout_requested": True},
|
|
633
|
+
)
|
|
634
|
+
with (
|
|
635
|
+
mock.patch.object(manager.connector, "get", return_value=None),
|
|
636
|
+
mock.patch.object(manager, "write_file") as write,
|
|
637
|
+
):
|
|
638
|
+
manager.update_scan_storage_with_status(status_msg)
|
|
639
|
+
assert storage.baseline_ready is False
|
|
640
|
+
write.assert_called_once_with("scan_id")
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
@pytest.mark.parametrize("baseline_devices", [[], ["samz"]])
|
|
644
|
+
@pytest.mark.parametrize("requested_info", [{}, {"baseline_readout_requested": False}])
|
|
645
|
+
def test_scan_without_baseline_request_does_not_wait_for_baseline(
|
|
646
|
+
file_writer_manager_mock, baseline_devices, requested_info
|
|
647
|
+
):
|
|
648
|
+
manager = file_writer_manager_mock
|
|
649
|
+
manager.scan_storage["scan_id"] = ScanStorage(1, "scan_id")
|
|
650
|
+
closed_msg = messages.ScanStatusMessage(
|
|
651
|
+
scan_id="scan_id",
|
|
652
|
+
status="closed",
|
|
653
|
+
scan_number=1,
|
|
654
|
+
scan_type="software_triggered",
|
|
655
|
+
num_monitored_readouts=0,
|
|
656
|
+
readout_priority={"monitored": [], "baseline": baseline_devices},
|
|
657
|
+
info={"scan_number": 1, "monitor_sync": "bec", **requested_info},
|
|
658
|
+
)
|
|
659
|
+
with (
|
|
660
|
+
mock.patch.object(manager.connector, "get", return_value=None),
|
|
661
|
+
mock.patch.object(manager, "write_file") as write,
|
|
662
|
+
):
|
|
663
|
+
manager.update_scan_storage_with_status(closed_msg)
|
|
664
|
+
write.assert_called_once_with("scan_id")
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
@pytest.mark.parametrize("first_update", ["status", "segment"])
|
|
668
|
+
def test_pending_notifications_are_flushed_when_storage_is_created(
|
|
669
|
+
file_writer_manager_mock, first_update
|
|
670
|
+
):
|
|
671
|
+
manager = file_writer_manager_mock
|
|
672
|
+
baseline = messages.ScanBaselineMessage(
|
|
673
|
+
scan_id="scan_id", data={"samz": {"samz": {"value": 42, "timestamp": 1}}}
|
|
674
|
+
)
|
|
675
|
+
files = {
|
|
676
|
+
name: messages.FileMessage(file_path=f"/{name}.h5", done=True, successful=True)
|
|
677
|
+
for name in ["eiger", "pilatus"]
|
|
678
|
+
}
|
|
679
|
+
manager._scan_baseline_callback(
|
|
680
|
+
MessageObject(topic=MessageEndpoints.scan_baseline().endpoint, value=baseline)
|
|
681
|
+
)
|
|
682
|
+
for name, file_msg in files.items():
|
|
683
|
+
manager._file_reference_callback(
|
|
684
|
+
MessageObject(
|
|
685
|
+
topic=MessageEndpoints.public_file("scan_id", name).endpoint, value=file_msg
|
|
686
|
+
)
|
|
687
|
+
)
|
|
688
|
+
manager._pending_baseline_writes["other_scan"] = {"samz": {}}
|
|
689
|
+
manager._pending_file_references["other_scan"] = {"eiger": files["eiger"]}
|
|
690
|
+
assert manager.scan_storage == {}
|
|
691
|
+
|
|
692
|
+
with mock.patch.object(manager, "write_file") as write:
|
|
693
|
+
if first_update == "status":
|
|
694
|
+
manager.update_scan_storage_with_status(
|
|
695
|
+
messages.ScanStatusMessage(
|
|
696
|
+
scan_id="scan_id",
|
|
697
|
+
status="paused",
|
|
698
|
+
info={"scan_number": 1},
|
|
699
|
+
readout_priority={"baseline": ["samz"]},
|
|
700
|
+
)
|
|
701
|
+
)
|
|
702
|
+
else:
|
|
703
|
+
manager.insert_to_scan_storage(
|
|
704
|
+
messages.ScanMessage(
|
|
705
|
+
scan_id="scan_id", point_id=0, data={}, metadata={"scan_number": 1}
|
|
706
|
+
)
|
|
707
|
+
)
|
|
708
|
+
write.assert_not_called()
|
|
709
|
+
|
|
710
|
+
storage = manager.scan_storage["scan_id"]
|
|
711
|
+
assert storage.baseline == baseline.data
|
|
712
|
+
assert storage.file_references == files
|
|
713
|
+
assert "scan_id" not in manager._pending_baseline_writes
|
|
714
|
+
assert "scan_id" not in manager._pending_file_references
|
|
715
|
+
assert manager._pending_baseline_writes == {"other_scan": {"samz": {}}}
|
|
716
|
+
assert manager._pending_file_references == {"other_scan": {"eiger": files["eiger"]}}
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
def test_baseline_before_first_status_allows_closed_scan_to_finalize(file_writer_manager_mock):
|
|
720
|
+
manager = file_writer_manager_mock
|
|
721
|
+
baseline = messages.ScanBaselineMessage(scan_id="scan_id", data={"samz": {}})
|
|
722
|
+
manager._scan_baseline_callback(
|
|
723
|
+
MessageObject(topic=MessageEndpoints.scan_baseline().endpoint, value=baseline)
|
|
724
|
+
)
|
|
725
|
+
with mock.patch.object(manager, "write_file") as write:
|
|
726
|
+
manager.update_scan_storage_with_status(
|
|
727
|
+
messages.ScanStatusMessage(
|
|
728
|
+
scan_id="scan_id",
|
|
729
|
+
status="closed",
|
|
730
|
+
scan_type="software_triggered",
|
|
731
|
+
num_monitored_readouts=0,
|
|
732
|
+
readout_priority={"monitored": [], "baseline": ["samz"]},
|
|
733
|
+
info={"scan_number": 1, "monitor_sync": "bec", "baseline_readout_requested": True},
|
|
734
|
+
)
|
|
735
|
+
)
|
|
736
|
+
write.assert_called_once_with("scan_id")
|
|
737
|
+
assert manager.scan_storage["scan_id"].baseline == baseline.data
|
|
738
|
+
assert manager._pending_baseline_writes == {}
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
def test_file_reference_updates_active_storage(file_writer_manager_mock):
|
|
742
|
+
manager = file_writer_manager_mock
|
|
743
|
+
manager.scan_storage["scan_id"] = ScanStorage(1, "scan_id")
|
|
744
|
+
first = messages.FileMessage(file_path="/eiger.h5", done=False, successful=False)
|
|
745
|
+
finished = messages.FileMessage(file_path="/eiger.h5", done=True, successful=True)
|
|
746
|
+
for file_msg in [first, finished]:
|
|
747
|
+
manager._file_reference_callback(
|
|
748
|
+
MessageObject(
|
|
749
|
+
topic=MessageEndpoints.public_file("scan_id", "eiger").endpoint, value=file_msg
|
|
750
|
+
)
|
|
751
|
+
)
|
|
752
|
+
assert manager.scan_storage["scan_id"].file_references == {"eiger": finished}
|
|
753
|
+
assert manager._pending_file_references == {}
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
@pytest.mark.parametrize(
|
|
757
|
+
"name,is_master_file", [("master", False), ("master", True), ("other", True)]
|
|
758
|
+
)
|
|
759
|
+
def test_master_file_notifications_are_not_buffered(file_writer_manager_mock, name, is_master_file):
|
|
760
|
+
manager = file_writer_manager_mock
|
|
761
|
+
file_msg = messages.FileMessage(
|
|
762
|
+
file_path="/master.h5", done=True, successful=True, is_master_file=is_master_file
|
|
763
|
+
)
|
|
764
|
+
manager._file_reference_callback(
|
|
765
|
+
MessageObject(topic=MessageEndpoints.public_file("scan_id", name).endpoint, value=file_msg)
|
|
766
|
+
)
|
|
767
|
+
assert manager.scan_storage == {}
|
|
768
|
+
assert manager._pending_file_references == {}
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
def test_finished_scan_does_not_buffer_late_notifications(
|
|
772
|
+
file_writer_manager_mock, scan_storage_mock
|
|
773
|
+
):
|
|
774
|
+
manager = file_writer_manager_mock
|
|
775
|
+
manager.scan_storage["scan_id"] = scan_storage_mock
|
|
776
|
+
with (
|
|
777
|
+
mock.patch(
|
|
778
|
+
"bec_server.file_writer.file_writer_manager.get_full_path", return_value="scan.h5"
|
|
779
|
+
),
|
|
780
|
+
mock.patch.object(manager.file_writer, "write"),
|
|
781
|
+
):
|
|
782
|
+
manager.write_file("scan_id")
|
|
783
|
+
|
|
784
|
+
baseline = messages.ScanBaselineMessage(scan_id="scan_id", data={"samz": {}})
|
|
785
|
+
file_msg = messages.FileMessage(file_path="/eiger.h5", done=True, successful=True)
|
|
786
|
+
manager._scan_baseline_callback(
|
|
787
|
+
MessageObject(topic=MessageEndpoints.scan_baseline().endpoint, value=baseline)
|
|
788
|
+
)
|
|
789
|
+
manager._file_reference_callback(
|
|
790
|
+
MessageObject(
|
|
791
|
+
topic=MessageEndpoints.public_file("scan_id", "eiger").endpoint, value=file_msg
|
|
792
|
+
)
|
|
793
|
+
)
|
|
794
|
+
assert manager.scan_storage == {}
|
|
795
|
+
assert manager._pending_baseline_writes == {}
|
|
796
|
+
assert manager._pending_file_references == {}
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
def test_subscriptions_buffer_early_notifications(file_writer_manager_mock, connected_connector):
|
|
800
|
+
manager = file_writer_manager_mock
|
|
801
|
+
received = threading.Event()
|
|
802
|
+
baseline = messages.ScanBaselineMessage(scan_id="scan_id", data={"samz": {}})
|
|
803
|
+
file_msg = messages.FileMessage(file_path="/eiger.h5", done=True, successful=True)
|
|
804
|
+
|
|
805
|
+
def file_reference_callback(msg):
|
|
806
|
+
manager._file_reference_callback(msg)
|
|
807
|
+
received.set()
|
|
808
|
+
|
|
809
|
+
connected_connector.register(
|
|
810
|
+
MessageEndpoints.scan_baseline(), cb=manager._scan_baseline_callback
|
|
811
|
+
)
|
|
812
|
+
connected_connector.register(
|
|
813
|
+
patterns=MessageEndpoints.public_file("*", "*"), cb=file_reference_callback
|
|
814
|
+
)
|
|
815
|
+
pipe = connected_connector.pipeline()
|
|
816
|
+
connected_connector.set_and_publish(MessageEndpoints.scan_baseline(), baseline, pipe=pipe)
|
|
817
|
+
connected_connector.set_and_publish(
|
|
818
|
+
MessageEndpoints.public_file("scan_id", "eiger"), file_msg, pipe=pipe
|
|
819
|
+
)
|
|
820
|
+
pipe.execute()
|
|
821
|
+
assert received.wait(5), "File reference subscription did not deliver its notification"
|
|
822
|
+
assert manager.scan_storage == {}
|
|
823
|
+
assert manager._pending_baseline_writes == {"scan_id": baseline.data}
|
|
824
|
+
assert manager._pending_file_references == {"scan_id": {"eiger": file_msg}}
|
|
825
|
+
|
|
826
|
+
with mock.patch.object(manager, "write_file") as write:
|
|
827
|
+
manager.update_scan_storage_with_status(
|
|
828
|
+
messages.ScanStatusMessage(
|
|
829
|
+
scan_id="scan_id",
|
|
830
|
+
status="closed",
|
|
831
|
+
scan_type="software_triggered",
|
|
832
|
+
num_monitored_readouts=0,
|
|
833
|
+
readout_priority={"monitored": [], "baseline": ["samz"]},
|
|
834
|
+
info={"scan_number": 1, "monitor_sync": "bec", "baseline_readout_requested": True},
|
|
835
|
+
)
|
|
836
|
+
)
|
|
837
|
+
write.assert_called_once_with("scan_id")
|
|
838
|
+
assert manager.scan_storage["scan_id"].baseline == baseline.data
|
|
839
|
+
assert manager.scan_storage["scan_id"].file_references == {"eiger": file_msg}
|
|
840
|
+
assert manager._pending_baseline_writes == {}
|
|
841
|
+
assert manager._pending_file_references == {}
|
|
@@ -13,6 +13,7 @@ from bec_lib.messaging_services import NotificationMessageObject
|
|
|
13
13
|
from bec_lib.tests.fixtures import dm_with_devices # noqa: F401
|
|
14
14
|
from bec_lib.tests.utils import ConnectorMock
|
|
15
15
|
from bec_lib.utils.scan_utils import compose_cli_input_from_scan_info
|
|
16
|
+
from bec_server.file_writer.file_writer_manager import ScanStorage
|
|
16
17
|
from bec_server.scan_server.instruction_handler import InstructionHandler
|
|
17
18
|
from bec_server.scan_server.scans.scan_base import ScanBase, ScanInfo, ScanType
|
|
18
19
|
from bec_server.scan_server.scans.scan_status import ScanStatus
|
|
@@ -388,6 +389,7 @@ def test_build_scan_status_message(action_context):
|
|
|
388
389
|
assert msg.dataset_number == 2
|
|
389
390
|
assert msg.num_points == 3
|
|
390
391
|
assert msg.num_monitored_readouts == 0
|
|
392
|
+
assert msg.info["baseline_readout_requested"] is False
|
|
391
393
|
assert msg.scan_type == "software_triggered"
|
|
392
394
|
assert msg.scan_parameters == {
|
|
393
395
|
"exp_time": 0.1,
|
|
@@ -576,6 +578,27 @@ def test_read_actions_emit_expected_messages_and_point_ids(action_context):
|
|
|
576
578
|
assert monitored_msg_2.metadata["device_instr_id"] == monitored_status_2._device_instr_id
|
|
577
579
|
|
|
578
580
|
|
|
581
|
+
@pytest.mark.parametrize("read_baseline", [False, True])
|
|
582
|
+
def test_baseline_request_in_scan_status_controls_file_readiness(action_context, read_baseline):
|
|
583
|
+
ctx = action_context()
|
|
584
|
+
_set_readout_priority(ctx, baseline=["samz"], monitored=["samx"])
|
|
585
|
+
ctx.actions.read_monitored_devices(wait=False)
|
|
586
|
+
assert ctx.scan.scan_info.baseline_readout_requested is False
|
|
587
|
+
if read_baseline:
|
|
588
|
+
ctx.actions.read_baseline_devices(wait=False)
|
|
589
|
+
|
|
590
|
+
closed_msg = ctx.actions._build_scan_status_message("closed")
|
|
591
|
+
assert closed_msg.info["baseline_readout_requested"] is read_baseline
|
|
592
|
+
storage = ScanStorage(1, closed_msg.scan_id)
|
|
593
|
+
storage.status_msg = closed_msg
|
|
594
|
+
storage.scan_finished = True
|
|
595
|
+
storage.num_monitored_readouts = 1
|
|
596
|
+
storage.append(0, {"samx": {}})
|
|
597
|
+
assert storage.ready_to_write() is (not read_baseline)
|
|
598
|
+
storage.baseline = {"samz": {}}
|
|
599
|
+
assert storage.ready_to_write() is True
|
|
600
|
+
|
|
601
|
+
|
|
579
602
|
def test_empty_read_and_trigger_actions_return_done_status(action_context):
|
|
580
603
|
ctx = action_context()
|
|
581
604
|
ctx.scan.scan_info.metadata["RID"] = "rid-123"
|
|
@@ -589,6 +612,7 @@ def test_empty_read_and_trigger_actions_return_done_status(action_context):
|
|
|
589
612
|
trigger_status = ctx.actions.trigger_all_devices(wait=False)
|
|
590
613
|
|
|
591
614
|
assert baseline_status.done
|
|
615
|
+
assert ctx.scan.scan_info.baseline_readout_requested is False
|
|
592
616
|
assert monitored_status.done
|
|
593
617
|
assert trigger_status.done
|
|
594
618
|
assert not _sent_device_instructions(ctx, "read")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|