bec-lib 3.151.3__tar.gz → 3.153.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.
- {bec_lib-3.151.3 → bec_lib-3.153.0}/PKG-INFO +3 -3
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/acl_login.py +74 -26
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/bec_service.py +14 -15
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/devicemanager.py +3 -1
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/endpoints.py +17 -28
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/messages.py +4 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/queue_items.py +40 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/scan_input_validator.py +1 -1
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/scans.py +17 -3
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/service_config.py +1 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/utils/user_acls_test.py +44 -4
- {bec_lib-3.151.3 → bec_lib-3.153.0}/pyproject.toml +3 -3
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_acl_login.py +110 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_bec_logger.py +1 -1
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_bec_service.py +22 -1
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_queue_items.py +67 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_scan_object.py +10 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/.gitignore +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/README.md +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/__init__.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/_print_versions.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/actors.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/alarm_handler.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/atlas_models.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/bec_errors.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/bec_yaml_loader.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/bl_state_manager.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/bl_states.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/builtin_actor_hli.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/callback_handler.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/channel_monitor.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/client.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/codecs.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/config_helper.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/config_values.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/configs/__init__.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/configs/demo_config.yaml +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/configs/openapi_schema.json +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/connector.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/dap_plugin_objects.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/dap_plugins.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/device.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/file_utils.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/live_scan_data.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/lmfit_serializer.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/logger.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/macro_update_handler.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/messaging_hooks.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/messaging_services.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/metadata_schema.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/numpy_encoder.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/observer.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/pdf_writer.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/plugin_helper.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/procedures/__init__.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/procedures/helper.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/procedures/hli.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/procedures/status.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/redis_connector/__init__.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/redis_connector/constants.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/redis_connector/hli.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/redis_connector/managed_redis_connection.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/redis_connector/streams.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/redis_connector/validation.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/request_context.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/request_items.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/scan_args.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/scan_data_container.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/scan_history.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/scan_items.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/scan_manager.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/scan_number_container.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/scan_repeat.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/scan_report.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/scibec_validator.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/script_executor.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/serialization.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/serialization_registry.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/signature_serializer.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/tests/__init__.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/tests/fixtures.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/tests/test_config.yaml +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/tests/test_service_config.yaml +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/tests/utils.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/user_macros.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/utils/__init__.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/utils/copier_jinja_filters.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/utils/import_utils.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/utils/json_extended.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/utils/plugin_manager/__init__.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/utils/plugin_manager/_constants.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/utils/plugin_manager/_util.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/utils/plugin_manager/create/__init__.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/utils/plugin_manager/create/device.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/utils/plugin_manager/create/scan.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/utils/plugin_manager/create/templates/v4_scan.py.jinja +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/utils/plugin_manager/main.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/utils/proxy.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/utils/pydantic_pretty_print.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/utils/rpc_utils.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/utils/scan_utils.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/bec_lib/utils/threading_utils.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/__init__.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/_additional_for_signature_serializer_test.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/conftest.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_alarm_handler.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_atlas_models.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_beamline_states.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_bec_messages.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_callback_handler.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_channel_monitor.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_client.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_config_helper.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_core_utils.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_dap_plugins.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_data_container.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_device_hashing.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_device_manager.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_devices.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_file_utils.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_hli_connector_fakeredis.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_hli_redis_connector.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_import_utils.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_jinja_plugins.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_lmfit_serializer.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_managed_config_values.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_managed_redis_connection.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_messaging_hooks.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_messaging_service.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_metadata_schema.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_observer.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_pdf_writer.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_plugin_helper.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_plugin_manager.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_plugin_system.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_pydantic_pretty_print.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_redis_connector_fakeredis.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_scan_context.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_scan_data.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_scan_history.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_scan_items.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_scan_manager.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_scan_repeat.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_scan_report.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_scibec_validator.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_script_executor.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_serializer.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_signature_serializer.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_signature_serializer_with_future_import.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_stream_subs.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_user_macros.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_utils.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/tests/test_yaml_loader.py +0 -0
- {bec_lib-3.151.3 → bec_lib-3.153.0}/util_scripts/init_config.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bec_lib
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.153.0
|
|
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
|
|
@@ -13,7 +13,7 @@ Requires-Dist: beautifulsoup4~=4.14
|
|
|
13
13
|
Requires-Dist: fastjsonschema~=2.19
|
|
14
14
|
Requires-Dist: fpdf2>=2.7.7,~=2.7
|
|
15
15
|
Requires-Dist: h5py~=3.10
|
|
16
|
-
Requires-Dist: hdf5plugin<
|
|
16
|
+
Requires-Dist: hdf5plugin<8.0,>=4.3
|
|
17
17
|
Requires-Dist: hiredis~=3.0
|
|
18
18
|
Requires-Dist: lmfit~=1.3
|
|
19
19
|
Requires-Dist: loguru~=0.7
|
|
@@ -45,7 +45,7 @@ Requires-Dist: coverage~=7.0; extra == 'dev'
|
|
|
45
45
|
Requires-Dist: fakeredis>=2.23.2,~=2.23; extra == 'dev'
|
|
46
46
|
Requires-Dist: isort>=5.13.2,~=5.13; extra == 'dev'
|
|
47
47
|
Requires-Dist: jinja2~=3.1; extra == 'dev'
|
|
48
|
-
Requires-Dist: pandas
|
|
48
|
+
Requires-Dist: pandas<4,>=2; extra == 'dev'
|
|
49
49
|
Requires-Dist: pylint<5,>=3; extra == 'dev'
|
|
50
50
|
Requires-Dist: pytest-random-order~=1.1; extra == 'dev'
|
|
51
51
|
Requires-Dist: pytest-redis<5,>=3; extra == 'dev'
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
+
from collections.abc import Iterator
|
|
5
|
+
from contextlib import contextmanager
|
|
4
6
|
from functools import wraps
|
|
5
7
|
from getpass import getpass
|
|
6
8
|
from typing import TYPE_CHECKING, cast
|
|
@@ -13,6 +15,7 @@ from rich.table import Table
|
|
|
13
15
|
from bec_lib.endpoints import MessageEndpoints
|
|
14
16
|
from bec_lib.logger import bec_logger
|
|
15
17
|
from bec_lib.redis_connector import RedisConnector
|
|
18
|
+
from bec_lib.service_config import ACLConfig
|
|
16
19
|
from bec_lib.utils.import_utils import lazy_import_from
|
|
17
20
|
|
|
18
21
|
if TYPE_CHECKING: # pragma: no cover
|
|
@@ -87,8 +90,35 @@ class BECAccess:
|
|
|
87
90
|
"""
|
|
88
91
|
self.connector.authenticate(username=username, password=token)
|
|
89
92
|
|
|
93
|
+
@contextmanager
|
|
94
|
+
def temporary_user(self, *, username: str, token: str | None) -> Iterator[None]:
|
|
95
|
+
"""
|
|
96
|
+
Temporarily authenticate as another ACL user and restore the current user on exit.
|
|
97
|
+
"""
|
|
98
|
+
credentials = self.current_credentials()
|
|
99
|
+
self.login_with_token(username=username, token=token)
|
|
100
|
+
try:
|
|
101
|
+
yield
|
|
102
|
+
finally:
|
|
103
|
+
self.login_with_token(
|
|
104
|
+
username=credentials["username"] or "default", token=credentials.get("token")
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def current_credentials(self) -> dict[str, str | None]:
|
|
108
|
+
"""
|
|
109
|
+
Return the current Redis ACL credentials for forwarding to linked clients.
|
|
110
|
+
"""
|
|
111
|
+
# pylint: disable=protected-access
|
|
112
|
+
conn_kwargs = (
|
|
113
|
+
self.connector._managed_connection._redis_conn.connection_pool.connection_kwargs
|
|
114
|
+
)
|
|
115
|
+
token = conn_kwargs.get("password")
|
|
116
|
+
if token == "null":
|
|
117
|
+
token = None
|
|
118
|
+
return {"username": self.connector.username, "token": token}
|
|
119
|
+
|
|
90
120
|
def _bec_service_login(
|
|
91
|
-
self, prompt_for_acl: bool = False, acl_config: dict | str | None = None
|
|
121
|
+
self, prompt_for_acl: bool = False, acl_config: ACLConfig | dict | str | None = None
|
|
92
122
|
) -> None:
|
|
93
123
|
"""
|
|
94
124
|
Login to Redis using the ACL system. This is the main entry point for the login process, started
|
|
@@ -97,9 +127,8 @@ class BECAccess:
|
|
|
97
127
|
Args:
|
|
98
128
|
prompt_for_acl (bool): If True, prompt the user to login using ACL. This is typically only used
|
|
99
129
|
for user-facing services. Default is False.
|
|
100
|
-
acl_config (dict
|
|
101
|
-
|
|
102
|
-
password for the account.
|
|
130
|
+
acl_config (ACLConfig, dict, str, optional): The ACL configuration. If a string is provided,
|
|
131
|
+
it will be treated as a path to the configuration file.
|
|
103
132
|
"""
|
|
104
133
|
|
|
105
134
|
if not self.connector.redis_server_is_running():
|
|
@@ -241,40 +270,58 @@ class BECAccess:
|
|
|
241
270
|
password = getpass(f"Enter the token for {selected_account} (hidden): ")
|
|
242
271
|
return password
|
|
243
272
|
|
|
244
|
-
def _config_login_successful(
|
|
273
|
+
def _config_login_successful(
|
|
274
|
+
self, prompt_for_acl: bool, acl_config: ACLConfig | dict | str | None
|
|
275
|
+
) -> bool:
|
|
245
276
|
"""
|
|
246
277
|
Login to Redis using the configuration file.
|
|
247
278
|
|
|
248
279
|
Args:
|
|
249
280
|
prompt_for_acl (bool): If True, prompt the user to login using ACL. Default is False.
|
|
250
|
-
acl_config (dict
|
|
281
|
+
acl_config (ACLConfig, dict, str, optional): The ACL configuration. If a string is provided,
|
|
282
|
+
it will be treated as a path to the configuration file.
|
|
251
283
|
|
|
252
284
|
Returns:
|
|
253
285
|
bool: True if the login was successful, False otherwise.
|
|
254
286
|
"""
|
|
255
287
|
|
|
288
|
+
if acl_config is None:
|
|
289
|
+
return False
|
|
290
|
+
|
|
256
291
|
if isinstance(acl_config, str):
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
# but are auto-deployed.
|
|
261
|
-
account = dotenv_values(acl_config)
|
|
262
|
-
user = account.get("REDIS_USER")
|
|
263
|
-
password = account.get("REDIS_PASSWORD")
|
|
264
|
-
if self._check_redis_auth(user, password):
|
|
265
|
-
return True
|
|
292
|
+
env_file = acl_config
|
|
293
|
+
username = None
|
|
294
|
+
password = None
|
|
266
295
|
elif isinstance(acl_config, dict):
|
|
267
|
-
|
|
296
|
+
env_file = acl_config.get("env_file")
|
|
297
|
+
username = acl_config.get("username") or acl_config.get("user")
|
|
268
298
|
password = acl_config.get("password")
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
return True
|
|
299
|
+
elif isinstance(acl_config, ACLConfig):
|
|
300
|
+
env_file = acl_config.env_file
|
|
301
|
+
username = acl_config.user
|
|
302
|
+
password = acl_config.password
|
|
274
303
|
else:
|
|
275
304
|
raise ValueError(
|
|
276
|
-
"Invalid value for 'acl' in the service config. Must be
|
|
305
|
+
"Invalid value for 'acl' in the service config. Must be an ACLConfig, dict, "
|
|
306
|
+
"or a path to a .env file."
|
|
277
307
|
)
|
|
308
|
+
|
|
309
|
+
if env_file and not prompt_for_acl and os.path.exists(env_file):
|
|
310
|
+
# Load the account information from the .env file. This is relevant for BEC services
|
|
311
|
+
# that are not launched by the user but are auto-deployed.
|
|
312
|
+
account = dotenv_values(env_file)
|
|
313
|
+
user = account.get("REDIS_USER")
|
|
314
|
+
env_password = account.get("REDIS_PASSWORD")
|
|
315
|
+
if self._check_redis_auth(user, env_password):
|
|
316
|
+
return True
|
|
317
|
+
|
|
318
|
+
if (username or password) and self._check_redis_auth(username, password):
|
|
319
|
+
return True
|
|
320
|
+
|
|
321
|
+
if prompt_for_acl and username:
|
|
322
|
+
self._user_service_login(username=username)
|
|
323
|
+
return True
|
|
324
|
+
|
|
278
325
|
return False
|
|
279
326
|
|
|
280
327
|
def _default_user_login_successful(self, full_access: bool) -> bool:
|
|
@@ -282,7 +329,8 @@ class BECAccess:
|
|
|
282
329
|
Login using the default user account.
|
|
283
330
|
|
|
284
331
|
Args:
|
|
285
|
-
full_access (bool): If True, the user has
|
|
332
|
+
full_access (bool): If True, the user has sufficient access to perform all user
|
|
333
|
+
operations. If False, the user has limited access or no access to perform user operations.
|
|
286
334
|
|
|
287
335
|
Returns:
|
|
288
336
|
bool: True if the login was successful, False otherwise.
|
|
@@ -292,8 +340,8 @@ class BECAccess:
|
|
|
292
340
|
return True
|
|
293
341
|
|
|
294
342
|
try:
|
|
295
|
-
|
|
296
|
-
|
|
343
|
+
# We check an endpoint on level INFO to verify that the user has sufficient access to perform all user operations.
|
|
344
|
+
self.connector.get(MessageEndpoints.device_config())
|
|
297
345
|
return True
|
|
298
346
|
# pylint: disable=broad-except
|
|
299
347
|
except Exception:
|
|
@@ -319,7 +367,7 @@ class BECAccess:
|
|
|
319
367
|
raise BECAuthenticationError("Login information not available. Unable to login.")
|
|
320
368
|
self._atlas_login = self._info.atlas_login
|
|
321
369
|
if not username and not self._info.atlas_login:
|
|
322
|
-
return self.login_with_token(username="
|
|
370
|
+
return self.login_with_token(username="default", token=None)
|
|
323
371
|
return self.login(username)
|
|
324
372
|
|
|
325
373
|
def _check_redis_auth(self, user: str | None, password: str | None) -> bool:
|
|
@@ -6,19 +6,15 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
import argparse
|
|
8
8
|
import getpass
|
|
9
|
-
import os
|
|
10
9
|
import socket
|
|
11
10
|
import sys
|
|
12
11
|
import threading
|
|
13
12
|
import time
|
|
14
13
|
import uuid
|
|
15
14
|
from dataclasses import asdict, dataclass
|
|
16
|
-
from importlib.metadata import PackageNotFoundError
|
|
17
|
-
from importlib.metadata import version as importlib_version
|
|
18
15
|
from typing import TYPE_CHECKING, Any, Literal
|
|
19
16
|
|
|
20
17
|
import psutil
|
|
21
|
-
import tomli
|
|
22
18
|
from rich.console import Console
|
|
23
19
|
from rich.table import Table
|
|
24
20
|
|
|
@@ -111,7 +107,7 @@ def parse_cmdline_args(parser=None, config_name: Literal["client", "server"] | s
|
|
|
111
107
|
config_file = args.config
|
|
112
108
|
cli_args = vars(args)
|
|
113
109
|
user = cli_args.pop("user")
|
|
114
|
-
acl_config = {"
|
|
110
|
+
acl_config = {"user": user} if user else None
|
|
115
111
|
redis_url = args.bec_server
|
|
116
112
|
|
|
117
113
|
if redis_url and config_file:
|
|
@@ -134,18 +130,21 @@ def parse_cmdline_args(parser=None, config_name: Literal["client", "server"] | s
|
|
|
134
130
|
except ValueError:
|
|
135
131
|
raise ValueError(f"Invalid port number in Redis URL: {comps[1]}")
|
|
136
132
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
133
|
+
kwargs = {"redis": redis_data, "cmdline_args": cli_args, "config_name": config_name}
|
|
134
|
+
if acl_config:
|
|
135
|
+
kwargs["acl"] = acl_config
|
|
136
|
+
service_config = ServiceConfig(**kwargs)
|
|
140
137
|
elif config_file:
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
138
|
+
kwargs = {"cmdline_args": cli_args, "config_name": config_name}
|
|
139
|
+
if acl_config:
|
|
140
|
+
kwargs["acl"] = acl_config
|
|
141
|
+
service_config = ServiceConfig(config_file, **kwargs)
|
|
144
142
|
else:
|
|
145
143
|
# If no config file or Redis URL is provided, use the default ServiceConfig
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
144
|
+
kwargs = {"cmdline_args": cli_args, "config_name": config_name}
|
|
145
|
+
if acl_config:
|
|
146
|
+
kwargs["acl"] = acl_config
|
|
147
|
+
service_config = ServiceConfig(**kwargs)
|
|
149
148
|
|
|
150
149
|
return args, extra_args, service_config
|
|
151
150
|
|
|
@@ -173,7 +172,7 @@ class BECService:
|
|
|
173
172
|
else connector
|
|
174
173
|
)
|
|
175
174
|
self.acl = BECAccess(self.connector)
|
|
176
|
-
self.acl._bec_service_login(prompt_for_acl, self._service_config.
|
|
175
|
+
self.acl._bec_service_login(prompt_for_acl, self._service_config.model.acl)
|
|
177
176
|
|
|
178
177
|
self._unique_service = unique_service
|
|
179
178
|
self.wait_for_server = wait_for_server
|
|
@@ -315,7 +315,9 @@ class DeviceContainer(dict):
|
|
|
315
315
|
return list(tags)
|
|
316
316
|
|
|
317
317
|
def get_software_triggered_devices(self) -> list:
|
|
318
|
-
"""
|
|
318
|
+
"""
|
|
319
|
+
Get a list of all enabled devices that should receive a software trigger during a scan.
|
|
320
|
+
"""
|
|
319
321
|
# pylint: disable=protected-access
|
|
320
322
|
devices = [
|
|
321
323
|
dev for _, dev in self.items() if dev._config.get("softwareTrigger", False) is True
|
|
@@ -234,7 +234,7 @@ class MessageEndpoints:
|
|
|
234
234
|
Returns:
|
|
235
235
|
EndpointInfo: Endpoint for device config request.
|
|
236
236
|
"""
|
|
237
|
-
endpoint = f"{EndpointType.
|
|
237
|
+
endpoint = f"{EndpointType.USER.value}/devices/config_request"
|
|
238
238
|
return EndpointInfo(
|
|
239
239
|
endpoint=endpoint, message_type=messages.DeviceConfigMessage, message_op=MessageOp.SEND
|
|
240
240
|
)
|
|
@@ -703,7 +703,7 @@ class MessageEndpoints:
|
|
|
703
703
|
Returns:
|
|
704
704
|
EndpointInfo: Endpoint for stopping devices.
|
|
705
705
|
"""
|
|
706
|
-
endpoint = f"{EndpointType.
|
|
706
|
+
endpoint = f"{EndpointType.USER.value}/queue/stop_devices"
|
|
707
707
|
return EndpointInfo(
|
|
708
708
|
endpoint=endpoint, message_type=messages.VariableMessage, message_op=MessageOp.SEND
|
|
709
709
|
)
|
|
@@ -849,17 +849,6 @@ class MessageEndpoints:
|
|
|
849
849
|
message_op=MessageOp.SET_PUBLISH,
|
|
850
850
|
)
|
|
851
851
|
|
|
852
|
-
@staticmethod
|
|
853
|
-
def bluesky_events() -> str:
|
|
854
|
-
"""
|
|
855
|
-
Endpoint for bluesky events. This endpoint is used by the scan bundler to
|
|
856
|
-
publish the bluesky events using a direct msgpack dump of the bluesky event.
|
|
857
|
-
|
|
858
|
-
Returns:
|
|
859
|
-
str: Endpoint for bluesky events.
|
|
860
|
-
"""
|
|
861
|
-
return f"{EndpointType.INFO.value}/scans/bluesky-events"
|
|
862
|
-
|
|
863
852
|
@staticmethod
|
|
864
853
|
def scan_segment():
|
|
865
854
|
"""
|
|
@@ -1084,7 +1073,7 @@ class MessageEndpoints:
|
|
|
1084
1073
|
Returns:
|
|
1085
1074
|
EndpointInfo: Endpoint for log.
|
|
1086
1075
|
"""
|
|
1087
|
-
endpoint = f"{EndpointType.
|
|
1076
|
+
endpoint = f"{EndpointType.USER.value}/log"
|
|
1088
1077
|
return EndpointInfo(
|
|
1089
1078
|
endpoint=endpoint, message_type=messages.LogMessage, message_op=MessageOp.STREAM
|
|
1090
1079
|
)
|
|
@@ -1626,7 +1615,7 @@ class MessageEndpoints:
|
|
|
1626
1615
|
Returns:
|
|
1627
1616
|
EndpointInfo: Endpoint for procedure queue updates for given queue.
|
|
1628
1617
|
"""
|
|
1629
|
-
endpoint = f"{EndpointType.
|
|
1618
|
+
endpoint = f"{EndpointType.USER.value}/procedures/logs/{queue}"
|
|
1630
1619
|
return EndpointInfo(
|
|
1631
1620
|
endpoint=endpoint, message_type=messages.RawMessage, message_op=MessageOp.STREAM
|
|
1632
1621
|
)
|
|
@@ -1682,7 +1671,7 @@ class MessageEndpoints:
|
|
|
1682
1671
|
|
|
1683
1672
|
@staticmethod
|
|
1684
1673
|
def modify_interlock_table():
|
|
1685
|
-
endpoint = f"{EndpointType.
|
|
1674
|
+
endpoint = f"{EndpointType.USER.value}/actor/builtin/scan_interlock/table_mod"
|
|
1686
1675
|
return EndpointInfo(
|
|
1687
1676
|
endpoint=endpoint,
|
|
1688
1677
|
message_type=messages.ScanInterlockModifyStateTableMessage,
|
|
@@ -1700,7 +1689,7 @@ class MessageEndpoints:
|
|
|
1700
1689
|
|
|
1701
1690
|
@staticmethod
|
|
1702
1691
|
def scan_interlock_enabled():
|
|
1703
|
-
endpoint = f"{EndpointType.
|
|
1692
|
+
endpoint = f"{EndpointType.USER.value}/actor/builtin/scan_interlock/config/enabled"
|
|
1704
1693
|
return EndpointInfo(
|
|
1705
1694
|
endpoint=endpoint,
|
|
1706
1695
|
message_type=messages.BoolConfigDefaultFalse,
|
|
@@ -1710,7 +1699,7 @@ class MessageEndpoints:
|
|
|
1710
1699
|
@staticmethod
|
|
1711
1700
|
def scan_interlock_trigger_setting():
|
|
1712
1701
|
"""Whether to restart the scan when the interlock is triggered."""
|
|
1713
|
-
endpoint = f"{EndpointType.
|
|
1702
|
+
endpoint = f"{EndpointType.USER.value}/actor/builtin/scan_interlock/config/trigger_setting"
|
|
1714
1703
|
return EndpointInfo(
|
|
1715
1704
|
endpoint=endpoint,
|
|
1716
1705
|
message_type=messages.ScanInterlockTriggerSettingMessage,
|
|
@@ -1738,18 +1727,18 @@ class MessageEndpoints:
|
|
|
1738
1727
|
)
|
|
1739
1728
|
|
|
1740
1729
|
@staticmethod
|
|
1741
|
-
def
|
|
1730
|
+
def acl_session(session_id: str):
|
|
1742
1731
|
"""
|
|
1743
|
-
Endpoint for exchanging
|
|
1732
|
+
Endpoint for exchanging ACL session information. This endpoint is used by the CLI or GUI to exchange
|
|
1744
1733
|
updates on the required ACL user. It uses a messages.CredentialsMessage message.
|
|
1745
1734
|
|
|
1746
1735
|
Args:
|
|
1747
|
-
|
|
1736
|
+
session_id (str): Session ID.
|
|
1748
1737
|
Returns:
|
|
1749
|
-
EndpointInfo: Endpoint for
|
|
1738
|
+
EndpointInfo: Endpoint for ACL session.
|
|
1750
1739
|
"""
|
|
1751
1740
|
|
|
1752
|
-
endpoint = f"{EndpointType.USER.value}/
|
|
1741
|
+
endpoint = f"{EndpointType.USER.value}/acl/{session_id}"
|
|
1753
1742
|
return EndpointInfo(
|
|
1754
1743
|
endpoint=endpoint, message_type=messages.CredentialsMessage, message_op=MessageOp.SEND
|
|
1755
1744
|
)
|
|
@@ -1812,7 +1801,7 @@ class MessageEndpoints:
|
|
|
1812
1801
|
Returns:
|
|
1813
1802
|
EndpointInfo: Endpoint for script execution info.
|
|
1814
1803
|
"""
|
|
1815
|
-
endpoint = f"{EndpointType.
|
|
1804
|
+
endpoint = f"{EndpointType.USER.value}/scripts/execution_info/{script_id}"
|
|
1816
1805
|
return EndpointInfo(
|
|
1817
1806
|
endpoint=endpoint,
|
|
1818
1807
|
message_type=messages.ScriptExecutionInfoMessage,
|
|
@@ -1828,7 +1817,7 @@ class MessageEndpoints:
|
|
|
1828
1817
|
Returns:
|
|
1829
1818
|
EndpointInfo: Endpoint for script content.
|
|
1830
1819
|
"""
|
|
1831
|
-
endpoint = f"{EndpointType.
|
|
1820
|
+
endpoint = f"{EndpointType.USER.value}/scripts/content/{script_id}"
|
|
1832
1821
|
return EndpointInfo(
|
|
1833
1822
|
endpoint=endpoint, message_type=messages.VariableMessage, message_op=MessageOp.KEY_VALUE
|
|
1834
1823
|
)
|
|
@@ -1859,7 +1848,7 @@ class MessageEndpoints:
|
|
|
1859
1848
|
Returns:
|
|
1860
1849
|
EndpointInfo: Endpoint for beamline state.
|
|
1861
1850
|
"""
|
|
1862
|
-
endpoint = f"{EndpointType.
|
|
1851
|
+
endpoint = f"{EndpointType.USER.value}/beamline_state/{state_name}"
|
|
1863
1852
|
return EndpointInfo(
|
|
1864
1853
|
endpoint=endpoint,
|
|
1865
1854
|
message_type=messages.BeamlineStateMessage,
|
|
@@ -1875,7 +1864,7 @@ class MessageEndpoints:
|
|
|
1875
1864
|
Returns:
|
|
1876
1865
|
EndpointInfo: Endpoint for beamline state updates.
|
|
1877
1866
|
"""
|
|
1878
|
-
endpoint = f"{EndpointType.
|
|
1867
|
+
endpoint = f"{EndpointType.USER.value}/available_beamline_states"
|
|
1879
1868
|
return EndpointInfo(
|
|
1880
1869
|
endpoint=endpoint,
|
|
1881
1870
|
message_type=messages.AvailableBeamlineStatesMessage,
|
|
@@ -2073,7 +2062,7 @@ class MessageEndpoints:
|
|
|
2073
2062
|
Returns:
|
|
2074
2063
|
EndpointInfo: Endpoint for dynamic metric publishing.
|
|
2075
2064
|
"""
|
|
2076
|
-
endpoint = f"{EndpointType.
|
|
2065
|
+
endpoint = f"{EndpointType.USER.value}/dynamic_metrics/{group_name}"
|
|
2077
2066
|
return EndpointInfo(
|
|
2078
2067
|
endpoint=endpoint,
|
|
2079
2068
|
message_type=messages.DynamicMetricMessage,
|
|
@@ -415,6 +415,8 @@ class RequestBlock(BaseModel):
|
|
|
415
415
|
scan_number (int | None): Scan number if applicable
|
|
416
416
|
scan_id (str | None): Scan ID if applicable
|
|
417
417
|
report_instructions (list[dict] | None): List of report instructions for the scan, if any
|
|
418
|
+
owned_device_locks (list[str]): List of device locks owned by the request
|
|
419
|
+
pending_device_locks (list[str]): List of device locks pending for the request
|
|
418
420
|
|
|
419
421
|
"""
|
|
420
422
|
|
|
@@ -427,6 +429,8 @@ class RequestBlock(BaseModel):
|
|
|
427
429
|
scan_number: int | None
|
|
428
430
|
scan_id: str | None
|
|
429
431
|
report_instructions: list[dict] | None = None
|
|
432
|
+
owned_device_locks: list[str] = Field(default_factory=list)
|
|
433
|
+
pending_device_locks: list[str] = Field(default_factory=list)
|
|
430
434
|
|
|
431
435
|
|
|
432
436
|
class QueueInfoEntry(BaseModel):
|
|
@@ -54,6 +54,7 @@ class QueueItem:
|
|
|
54
54
|
self.active_request_block = active_request_block
|
|
55
55
|
self.scan_ids = scan_id
|
|
56
56
|
self.reason = reason
|
|
57
|
+
self._lock = threading.RLock()
|
|
57
58
|
if client_messages is None:
|
|
58
59
|
client_messages = []
|
|
59
60
|
self.client_messages = client_messages
|
|
@@ -85,6 +86,44 @@ class QueueItem:
|
|
|
85
86
|
def status(self):
|
|
86
87
|
return self._status
|
|
87
88
|
|
|
89
|
+
@threadlocked
|
|
90
|
+
def _get_device_lock_state(self) -> tuple[list[str], list[str]]:
|
|
91
|
+
"""
|
|
92
|
+
Return a snapshot of owned and pending device locks.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
tuple[list[str], list[str]]: A tuple containing two lists:
|
|
96
|
+
- The first list contains the names of devices whose locks are currently owned.
|
|
97
|
+
- The second list contains the names of devices whose locks are pending.
|
|
98
|
+
"""
|
|
99
|
+
owned_device_locks = sorted(
|
|
100
|
+
{
|
|
101
|
+
device
|
|
102
|
+
for request_block in self.request_blocks
|
|
103
|
+
for device in request_block.owned_device_locks
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
pending_device_locks = sorted(
|
|
107
|
+
{
|
|
108
|
+
device
|
|
109
|
+
for request_block in self.request_blocks
|
|
110
|
+
for device in request_block.pending_device_locks
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
return owned_device_locks, pending_device_locks
|
|
114
|
+
|
|
115
|
+
def get_device_lock_state(self) -> tuple[list[str], list[str]]:
|
|
116
|
+
"""
|
|
117
|
+
Refresh and return a consistent snapshot of device-lock state.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
tuple[list[str], list[str]]: A tuple containing two lists:
|
|
121
|
+
- The first list contains the names of devices whose locks are currently owned.
|
|
122
|
+
- The second list contains the names of devices whose locks are pending.
|
|
123
|
+
"""
|
|
124
|
+
self._update_with_buffer()
|
|
125
|
+
return self._get_device_lock_state()
|
|
126
|
+
|
|
88
127
|
def _update_with_buffer(self):
|
|
89
128
|
current_queue = self.scan_manager.queue_storage.current_scan_queue
|
|
90
129
|
if not current_queue:
|
|
@@ -103,6 +142,7 @@ class QueueItem:
|
|
|
103
142
|
self.update_queue_item(queue_item.info)
|
|
104
143
|
return
|
|
105
144
|
|
|
145
|
+
@threadlocked
|
|
106
146
|
def update_queue_item(self, queue_item: messages.QueueInfoEntry):
|
|
107
147
|
"""update the queue item"""
|
|
108
148
|
self.request_blocks = queue_item.request_blocks
|
|
@@ -13,7 +13,7 @@ from bec_lib.scan_args import ScanArgument
|
|
|
13
13
|
from bec_lib.signature_serializer import deserialize_dtype, dict_to_signature
|
|
14
14
|
|
|
15
15
|
# Internal scan kwargs that are automatically added to the scan request
|
|
16
|
-
INTERNAL_SCAN_KWARGS = {"user_metadata", "system_config", "scan_queue"}
|
|
16
|
+
INTERNAL_SCAN_KWARGS = {"user_metadata", "system_config", "scan_queue", "monitored", "on_request"}
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class ScanInputValidator:
|
|
@@ -58,6 +58,7 @@ class ScanObject:
|
|
|
58
58
|
hide_report: bool = False,
|
|
59
59
|
metadata: dict | None = None,
|
|
60
60
|
monitored: list[str | DeviceBase] | None = None,
|
|
61
|
+
on_request: list[str | DeviceBase] | None = None,
|
|
61
62
|
file_suffix: str | None = None,
|
|
62
63
|
file_directory: str | None = None,
|
|
63
64
|
scan_queue: str | None = None,
|
|
@@ -72,7 +73,8 @@ class ScanObject:
|
|
|
72
73
|
async_callback: Asynchronous callback function
|
|
73
74
|
hide_report: Hide the report
|
|
74
75
|
metadata: Metadata dictionary
|
|
75
|
-
monitored: List of monitored
|
|
76
|
+
monitored: List of devices to be put on the readout priority `monitored`
|
|
77
|
+
on_request: List of devices to be put on the readout priority `on_request`
|
|
76
78
|
file_suffix: File suffix for the scan data
|
|
77
79
|
file_directory: File directory for the scan data
|
|
78
80
|
scan_queue: Scan queue name. If None, the default queue will be used.
|
|
@@ -110,13 +112,25 @@ class ScanObject:
|
|
|
110
112
|
monitored = [monitored]
|
|
111
113
|
for mon_device in monitored:
|
|
112
114
|
if isinstance(mon_device, str):
|
|
113
|
-
|
|
114
|
-
if not
|
|
115
|
+
mon_device_obj = self.client.device_manager.devices.get(mon_device)
|
|
116
|
+
if not mon_device_obj:
|
|
115
117
|
raise RuntimeError(
|
|
116
118
|
f"Specified monitored device {mon_device} does not exist in the current device configuration."
|
|
117
119
|
)
|
|
118
120
|
kwargs["monitored"] = monitored
|
|
119
121
|
|
|
122
|
+
if on_request is not None:
|
|
123
|
+
if not isinstance(on_request, list):
|
|
124
|
+
on_request = [on_request]
|
|
125
|
+
for or_device in on_request:
|
|
126
|
+
if isinstance(or_device, str):
|
|
127
|
+
or_device_obj = self.client.device_manager.devices.get(or_device)
|
|
128
|
+
if not or_device_obj:
|
|
129
|
+
raise RuntimeError(
|
|
130
|
+
f"Specified on_request device {or_device} does not exist in the current device configuration."
|
|
131
|
+
)
|
|
132
|
+
kwargs["on_request"] = on_request
|
|
133
|
+
|
|
120
134
|
sys_config = sys_config.model_dump()
|
|
121
135
|
# pylint: disable=protected-access
|
|
122
136
|
if scans._scan_group:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
from typing import Literal
|
|
3
3
|
|
|
4
|
-
from bec_lib.endpoints import MessageEndpoints
|
|
4
|
+
from bec_lib.endpoints import EndpointType, MessageEndpoints
|
|
5
5
|
from bec_lib.redis_connector import RedisConnector
|
|
6
6
|
|
|
7
7
|
|
|
@@ -12,12 +12,20 @@ class BECAccessDemo: # pragma: no cover
|
|
|
12
12
|
self.connector = connector
|
|
13
13
|
else:
|
|
14
14
|
self.connector = RedisConnector("localhost:6379")
|
|
15
|
-
self.
|
|
15
|
+
admin_account = self._find_admin_account()
|
|
16
|
+
if admin_account:
|
|
17
|
+
self.connector.authenticate(**admin_account)
|
|
16
18
|
self.username = "user"
|
|
17
19
|
self.admin_username = "admin"
|
|
18
20
|
self.deployment_id = "test_deployment"
|
|
19
21
|
|
|
20
|
-
def _find_admin_account(self) -> dict[str, str]:
|
|
22
|
+
def _find_admin_account(self) -> dict[str, str] | None:
|
|
23
|
+
try:
|
|
24
|
+
self.connector.acl_list()
|
|
25
|
+
return None
|
|
26
|
+
except Exception:
|
|
27
|
+
pass
|
|
28
|
+
|
|
21
29
|
for user, token in [("default", "null"), ("admin", "admin")]:
|
|
22
30
|
try:
|
|
23
31
|
self.connector.authenticate(username=user, password=token)
|
|
@@ -149,6 +157,30 @@ class BECAccessDemo: # pragma: no cover
|
|
|
149
157
|
reset_keys=True,
|
|
150
158
|
)
|
|
151
159
|
|
|
160
|
+
def set_default_non_admin(self):
|
|
161
|
+
"""Grant the default user access to all non-admin endpoint namespaces."""
|
|
162
|
+
read_write_namespaces = [
|
|
163
|
+
EndpointType.PUBLIC.value,
|
|
164
|
+
EndpointType.PERSONAL.value,
|
|
165
|
+
EndpointType.USER.value,
|
|
166
|
+
]
|
|
167
|
+
read_only_namespaces = [EndpointType.INFO.value]
|
|
168
|
+
self.connector._managed_connection._redis_conn.acl_setuser(
|
|
169
|
+
"default",
|
|
170
|
+
enabled=True,
|
|
171
|
+
nopass=True,
|
|
172
|
+
categories=["+@all", "-@dangerous"],
|
|
173
|
+
keys=[f"%RW~{namespace}/*" for namespace in read_write_namespaces]
|
|
174
|
+
+ [f"%R~{namespace}/*" for namespace in read_only_namespaces],
|
|
175
|
+
channels=[
|
|
176
|
+
f"{namespace}/*" for namespace in read_write_namespaces + read_only_namespaces
|
|
177
|
+
]
|
|
178
|
+
+ [MessageEndpoints.public_file("*", "*").endpoint],
|
|
179
|
+
commands=["+keys"],
|
|
180
|
+
reset_channels=True,
|
|
181
|
+
reset_keys=True,
|
|
182
|
+
)
|
|
183
|
+
|
|
152
184
|
|
|
153
185
|
def _main(
|
|
154
186
|
mode: str, connector: RedisConnector | None = None, shutdown: bool = True
|
|
@@ -167,6 +199,14 @@ def _main(
|
|
|
167
199
|
print(
|
|
168
200
|
"Enabled mode admin. Please make sure to place the .bec_acl.env file in the root directory and restart the bec server."
|
|
169
201
|
)
|
|
202
|
+
case "non_admin":
|
|
203
|
+
demo.reset()
|
|
204
|
+
demo.add_user()
|
|
205
|
+
demo.add_admin()
|
|
206
|
+
demo.set_default_non_admin()
|
|
207
|
+
print(
|
|
208
|
+
"Enabled mode non_admin. The default user can access all non-admin namespaces. Please restart the bec server."
|
|
209
|
+
)
|
|
170
210
|
case _:
|
|
171
211
|
raise ValueError(f"Invalid mode: {mode}")
|
|
172
212
|
|
|
@@ -185,6 +225,6 @@ if __name__ == "__main__": # pragma: no cover
|
|
|
185
225
|
args = parser.parse_args()
|
|
186
226
|
|
|
187
227
|
if args.mode is None:
|
|
188
|
-
args.mode = "
|
|
228
|
+
args.mode = "non_admin"
|
|
189
229
|
|
|
190
230
|
_main("default" if args.reset else args.mode)
|