unitelabs-cdk 0.2.4__py3-none-any.whl
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.
- unitelabs/cdk/__init__.py +9 -0
- unitelabs/cdk/cli/__init__.py +4 -0
- unitelabs/cdk/cli/certificate.py +50 -0
- unitelabs/cdk/cli/connector.py +59 -0
- unitelabs/cdk/compose_app.py +147 -0
- unitelabs/cdk/config.py +12 -0
- unitelabs/cdk/connector.py +126 -0
- unitelabs/cdk/features/core/lock_controller/LockController-v2_0.proto +67 -0
- unitelabs/cdk/features/core/lock_controller/LockController-v2_0.sila.xml +134 -0
- unitelabs/cdk/features/core/lock_controller/__init__.py +15 -0
- unitelabs/cdk/features/core/lock_controller/lock_controller.py +111 -0
- unitelabs/cdk/features/core/lock_controller/lock_controller_base.py +83 -0
- unitelabs/cdk/features/core/sila_service/SiLAService-v1_0.proto +130 -0
- unitelabs/cdk/features/core/sila_service/SiLAService-v1_0.sila.xml +225 -0
- unitelabs/cdk/features/core/sila_service/__init__.py +3 -0
- unitelabs/cdk/features/core/sila_service/sila_service.py +146 -0
- unitelabs/cdk/features/core/simulation_controller/SimulationController-v1_0.sila.xml +76 -0
- unitelabs/cdk/features/core/simulation_controller/__init__.py +3 -0
- unitelabs/cdk/features/core/simulation_controller/simulation_controller_base.py +89 -0
- unitelabs/cdk/features/examples/greeting_provider/__init__.py +3 -0
- unitelabs/cdk/features/examples/greeting_provider/greeting_provider_base.py +34 -0
- unitelabs/cdk/features/examples/timer_provider/__init__.py +3 -0
- unitelabs/cdk/features/examples/timer_provider/timer_provider_base.py +60 -0
- unitelabs/cdk/features/robot/grip_controller/GripController-v1_0.proto +27 -0
- unitelabs/cdk/features/robot/grip_controller/GripController-v1_0.sila.xml +21 -0
- unitelabs/cdk/features/robot/grip_controller/__init__.py +3 -0
- unitelabs/cdk/features/robot/grip_controller/grip_controller_base.py +27 -0
- unitelabs/cdk/features/robot/labware_transfer_manipulator_controller/LabwareTransferManipulatorController-v1_0.proto +122 -0
- unitelabs/cdk/features/robot/labware_transfer_manipulator_controller/LabwareTransferManipulatorController-v1_0.sila.xml +333 -0
- unitelabs/cdk/features/robot/labware_transfer_manipulator_controller/__init__.py +17 -0
- unitelabs/cdk/features/robot/labware_transfer_manipulator_controller/labware_transfer_manipulator_controller_base.py +226 -0
- unitelabs/cdk/features/robot/movement_controller/MovementController-v1_0.proto +62 -0
- unitelabs/cdk/features/robot/movement_controller/MovementController-v1_0.sila.xml +94 -0
- unitelabs/cdk/features/robot/movement_controller/__init__.py +3 -0
- unitelabs/cdk/features/robot/movement_controller/movement_controller_base.py +69 -0
- unitelabs/cdk/features/test/basic_data_types_test/BasicDataTypesTest-v1_0.proto +170 -0
- unitelabs/cdk/features/test/basic_data_types_test/BasicDataTypesTest-v1_0.sila.xml +242 -0
- unitelabs/cdk/features/test/basic_data_types_test/__init__.py +3 -0
- unitelabs/cdk/features/test/basic_data_types_test/basic_data_types_test.py +158 -0
- unitelabs/cdk/features/test/list_data_type_test/ListDataTypeTest-v1_0.sila.xml +262 -0
- unitelabs/cdk/features/test/list_data_type_test/__init__.py +3 -0
- unitelabs/cdk/features/test/list_data_type_test/list_data_type_test.py +208 -0
- unitelabs/cdk/features/test/observable_command_test/ObservableCommandTest-v1_0.proto +49 -0
- unitelabs/cdk/features/test/observable_command_test/ObservableCommandTest-v1_0.sila.xml +74 -0
- unitelabs/cdk/features/test/observable_command_test/__init__.py +3 -0
- unitelabs/cdk/features/test/observable_command_test/observable_command_test.py +66 -0
- unitelabs/cdk/features/test/observable_property_test/ObservablePropertyTest-v1_0.proto +53 -0
- unitelabs/cdk/features/test/observable_property_test/ObservablePropertyTest-v1_0.sila.xml +52 -0
- unitelabs/cdk/features/test/observable_property_test/__init__.py +3 -0
- unitelabs/cdk/features/test/observable_property_test/observable_property_test.py +56 -0
- unitelabs/cdk/features/test/structure_data_type_test/StructureDataTypeTest-v1_0.sila.xml +256 -0
- unitelabs/cdk/features/test/structure_data_type_test/__init__.py +3 -0
- unitelabs/cdk/features/test/structure_data_type_test/structure_data_type_test.py +181 -0
- unitelabs/cdk/features/test/unobservable_command_test/UnobservableCommandTest-v1_0.proto +57 -0
- unitelabs/cdk/features/test/unobservable_command_test/UnobservableCommandTest-v1_0.sila.xml +110 -0
- unitelabs/cdk/features/test/unobservable_command_test/__init__.py +3 -0
- unitelabs/cdk/features/test/unobservable_command_test/unobservable_command_test.py +54 -0
- unitelabs/cdk/features/test/unobservable_property_test/UnobservablePropertyTest-v1_0.proto +31 -0
- unitelabs/cdk/features/test/unobservable_property_test/UnobservablePropertyTest-v1_0.sila.xml +27 -0
- unitelabs/cdk/features/test/unobservable_property_test/__init__.py +3 -0
- unitelabs/cdk/features/test/unobservable_property_test/unobservable_property_test.py +24 -0
- unitelabs/cdk/features/weighing/weighing_service/WeighingService-v1_0.proto +105 -0
- unitelabs/cdk/features/weighing/weighing_service/WeighingService-v1_0.sila.xml +249 -0
- unitelabs/cdk/features/weighing/weighing_service/__init__.py +3 -0
- unitelabs/cdk/features/weighing/weighing_service/weighing_service_base.py +97 -0
- unitelabs/cdk/logging.py +23 -0
- unitelabs/cdk/py.typed +0 -0
- unitelabs/cdk/sila/__init__.py +40 -0
- unitelabs/cdk/sila/commands/__init__.py +0 -0
- unitelabs/cdk/sila/commands/intermediate.py +16 -0
- unitelabs/cdk/sila/commands/intermediate_response.py +36 -0
- unitelabs/cdk/sila/commands/response.py +44 -0
- unitelabs/cdk/sila/commands/status.py +24 -0
- unitelabs/cdk/sila/data_types/__init__.py +44 -0
- unitelabs/cdk/sila/data_types/custom_data_type.py +22 -0
- unitelabs/cdk/sila/data_types/data_type_definition.py +38 -0
- unitelabs/cdk/sila/data_types/parser.py +95 -0
- unitelabs/cdk/sila/defined_execution_error.py +18 -0
- unitelabs/cdk/sila/feature.py +32 -0
- unitelabs/cdk/sila/metadata.py +19 -0
- unitelabs/cdk/sila/observable_command.py +120 -0
- unitelabs/cdk/sila/observable_property.py +48 -0
- unitelabs/cdk/sila/unobservable_command.py +96 -0
- unitelabs/cdk/sila/unobservable_property.py +43 -0
- unitelabs/cdk/sila/utils.py +74 -0
- unitelabs/cdk/utils.py +117 -0
- unitelabs_cdk-0.2.4.dist-info/LICENSE +21 -0
- unitelabs_cdk-0.2.4.dist-info/METADATA +86 -0
- unitelabs_cdk-0.2.4.dist-info/RECORD +91 -0
- unitelabs_cdk-0.2.4.dist-info/WHEEL +4 -0
- unitelabs_cdk-0.2.4.dist-info/entry_points.txt +4 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from importlib.metadata import version
|
|
2
|
+
|
|
3
|
+
from .compose_app import AppFactory, compose_app
|
|
4
|
+
from .config import Config
|
|
5
|
+
from .connector import Connector
|
|
6
|
+
from .logging import create_logger
|
|
7
|
+
|
|
8
|
+
__version__ = version("unitelabs_cdk")
|
|
9
|
+
__all__ = ["__version__", "Connector", "Config", "create_logger", "compose_app", "AppFactory"]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pathlib
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from sila import server
|
|
7
|
+
|
|
8
|
+
from unitelabs.cdk import Config
|
|
9
|
+
|
|
10
|
+
config = Config()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.group()
|
|
14
|
+
def certificate() -> None:
|
|
15
|
+
"""Utility tool to handle certificates for TLS encryption."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@certificate.command()
|
|
19
|
+
@click.option(
|
|
20
|
+
"--uuid",
|
|
21
|
+
type=str,
|
|
22
|
+
default=config.sila_server.get("uuid", None),
|
|
23
|
+
required=True,
|
|
24
|
+
help="The SiLA server's uuid.",
|
|
25
|
+
)
|
|
26
|
+
@click.option(
|
|
27
|
+
"--host",
|
|
28
|
+
type=str,
|
|
29
|
+
default=lambda: config.sila_server.get("host", "0.0.0.0"),
|
|
30
|
+
help="The SiLA server's host address.",
|
|
31
|
+
)
|
|
32
|
+
@click.option(
|
|
33
|
+
"--target",
|
|
34
|
+
"-t",
|
|
35
|
+
type=str,
|
|
36
|
+
default=".",
|
|
37
|
+
help="The output directory in which to store the certificate files.",
|
|
38
|
+
)
|
|
39
|
+
def generate(uuid: str, host: str, target: str):
|
|
40
|
+
"""Generates a new self-signed certificate according to the SiLA 2 specification."""
|
|
41
|
+
key, cert = server.generate_certificate(uuid, host)
|
|
42
|
+
|
|
43
|
+
directory = pathlib.Path(target)
|
|
44
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
(directory / "cert.pem").write_bytes(cert)
|
|
46
|
+
(directory / "key.pem").write_bytes(key)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if __name__ == "__main__":
|
|
50
|
+
certificate()
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import importlib.machinery
|
|
3
|
+
import importlib.metadata
|
|
4
|
+
import importlib.util
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from unitelabs.cdk import AppFactory, compose_app, utils
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.group()
|
|
13
|
+
def connector() -> None:
|
|
14
|
+
"""Base cli"""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@connector.command()
|
|
18
|
+
@click.option(
|
|
19
|
+
"--app",
|
|
20
|
+
type=str,
|
|
21
|
+
metavar="IMPORT",
|
|
22
|
+
default=utils.find_factory,
|
|
23
|
+
help="The application factory function to load, in the form 'module:name'.",
|
|
24
|
+
)
|
|
25
|
+
@click.option(
|
|
26
|
+
"-v",
|
|
27
|
+
"--verbose",
|
|
28
|
+
count=True,
|
|
29
|
+
help="Increase the verbosity of to debug.",
|
|
30
|
+
)
|
|
31
|
+
@utils.coroutine
|
|
32
|
+
async def start(app, verbose: int):
|
|
33
|
+
"""Application Entrypoint"""
|
|
34
|
+
log_level = logging.DEBUG if verbose > 0 else logging.INFO
|
|
35
|
+
|
|
36
|
+
create_app = await load_create_app(app)
|
|
37
|
+
app = await compose_app(create_app, log_level=log_level)
|
|
38
|
+
|
|
39
|
+
await app.start()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
async def load_create_app(location: str) -> AppFactory:
|
|
43
|
+
"""
|
|
44
|
+
Dynamically import the application factory from the given location.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
location: Where to find the app factory formatted as "module:name".
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
module_name, _, factory_name = location.partition(":")
|
|
51
|
+
|
|
52
|
+
module = importlib.import_module(module_name)
|
|
53
|
+
create_app = getattr(module, factory_name)
|
|
54
|
+
|
|
55
|
+
return create_app
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
connector()
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import collections.abc
|
|
2
|
+
import functools
|
|
3
|
+
import inspect
|
|
4
|
+
import logging
|
|
5
|
+
import signal
|
|
6
|
+
import sys
|
|
7
|
+
import types
|
|
8
|
+
import typing
|
|
9
|
+
|
|
10
|
+
from .connector import Connector
|
|
11
|
+
from .logging import create_logger
|
|
12
|
+
|
|
13
|
+
T = typing.TypeVar("T")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
Generator = typing.Union[
|
|
17
|
+
collections.abc.Generator[T, None, None],
|
|
18
|
+
collections.abc.AsyncGenerator[T, None],
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
AppFactory = collections.abc.Callable[
|
|
23
|
+
...,
|
|
24
|
+
typing.Union[
|
|
25
|
+
Connector,
|
|
26
|
+
collections.abc.Awaitable[Connector],
|
|
27
|
+
Generator,
|
|
28
|
+
],
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def compose_app(create_app: AppFactory, log_level: int = logging.INFO) -> Connector:
|
|
33
|
+
"""
|
|
34
|
+
Creates and configures a ready to use Connector.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
create_app: The factory method used to create the Connector instance.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
The newly created Connector.
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
ValueError: If create_app does not follow the required interface.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
47
|
+
signal.signal(signal.SIGTERM, signal_handler)
|
|
48
|
+
|
|
49
|
+
create_logger("sila", log_level)
|
|
50
|
+
create_logger("unitelabs", log_level)
|
|
51
|
+
|
|
52
|
+
app = await init_app(app_factory=create_app)
|
|
53
|
+
|
|
54
|
+
return app
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# pylint: disable-next=unused-argument
|
|
58
|
+
def signal_handler(signum: int, frame: typing.Optional[types.FrameType]) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Gracefully handle received signals.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
signum: The signal number.
|
|
64
|
+
frame: The current stack frame.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
sys.exit(signum)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
async def init_app(app_factory: AppFactory) -> Connector:
|
|
71
|
+
"""
|
|
72
|
+
Uses the provided factory method to init a new Connector.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
app_factory: The factory method to call.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
The initialized Connector.
|
|
79
|
+
|
|
80
|
+
Raises:
|
|
81
|
+
ValueError: If app_factory does not follow the required interface.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
if inspect.isasyncgenfunction(app_factory) or inspect.isgeneratorfunction(app_factory):
|
|
85
|
+
generator: Generator[Connector] = app_factory()
|
|
86
|
+
generator = _sync_to_async_gen(generator)
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
app = await generator.__anext__()
|
|
90
|
+
except (StopAsyncIteration, StopIteration):
|
|
91
|
+
raise ValueError("Unable to create app: `create_app` did not yield a value.") from None
|
|
92
|
+
|
|
93
|
+
shutdown_handler = functools.partial(_shutdown_yield, generator)
|
|
94
|
+
app.on_shutdown(handler=shutdown_handler)
|
|
95
|
+
|
|
96
|
+
return app
|
|
97
|
+
|
|
98
|
+
if inspect.iscoroutinefunction(app_factory):
|
|
99
|
+
return await app_factory()
|
|
100
|
+
|
|
101
|
+
if inspect.isfunction(app_factory):
|
|
102
|
+
return app_factory()
|
|
103
|
+
|
|
104
|
+
raise ValueError(f"Invalid `create_app`: '{app_factory}'. Provide a callable function that returns a Connector.")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
async def _shutdown_yield(generator: collections.abc.AsyncGenerator[T, None]) -> None:
|
|
108
|
+
"""
|
|
109
|
+
Execute the shutdown of a factory function by advancing the iterator
|
|
110
|
+
after the yield and ensure the iteration ends (if not it means there is
|
|
111
|
+
more than one yield in the function).
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
generator: The factory function to create the app.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
await generator.__anext__()
|
|
119
|
+
except (StopAsyncIteration, StopIteration):
|
|
120
|
+
pass
|
|
121
|
+
else:
|
|
122
|
+
await _shutdown_yield(generator)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
async def _sync_to_async_gen(generator: Generator[T]) -> collections.abc.AsyncGenerator[T, None]:
|
|
126
|
+
"""
|
|
127
|
+
Wraps any generator into an async generator.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
generator: The generator to wrap as async.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
The async generator.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
if inspect.isasyncgen(generator):
|
|
137
|
+
async for item in generator:
|
|
138
|
+
yield item
|
|
139
|
+
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
if inspect.isgenerator(generator):
|
|
143
|
+
while True:
|
|
144
|
+
try:
|
|
145
|
+
yield next(generator)
|
|
146
|
+
except StopIteration:
|
|
147
|
+
return
|
unitelabs/cdk/config.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
2
|
+
from sila import cloud_connector, server
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Config(BaseSettings):
|
|
6
|
+
model_config = SettingsConfigDict(
|
|
7
|
+
env_file=".env", env_file_encoding="utf-8", env_nested_delimiter="__", extra="ignore"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
environment: str = "development"
|
|
11
|
+
sila_server: server.ServerConfig = server.ServerConfig()
|
|
12
|
+
cloud_server_endpoint: cloud_connector.CloudServerEndpointConfig = cloud_connector.CloudServerEndpointConfig()
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import contextlib
|
|
3
|
+
import logging
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
from sila import cloud_connector, discovery, server
|
|
7
|
+
from unitelabs.cdk.features.core.sila_service import SiLAService
|
|
8
|
+
|
|
9
|
+
from .config import Config
|
|
10
|
+
|
|
11
|
+
Handler = typing.Callable[..., typing.Union[typing.Any, typing.Awaitable[typing.Any]]]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Connector:
|
|
15
|
+
"""Main app"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, config: typing.Optional[dict] = None):
|
|
18
|
+
self.__config = Config.parse_obj(config or {})
|
|
19
|
+
|
|
20
|
+
self._shutdown_handlers: list[Handler] = []
|
|
21
|
+
|
|
22
|
+
self._sila_server = server.Server(config=self.config.sila_server)
|
|
23
|
+
self.logger.debug(self._sila_server)
|
|
24
|
+
self._broadcaster = discovery.Broadcaster(self._sila_server)
|
|
25
|
+
|
|
26
|
+
self.config.cloud_server_endpoint["options"] = {
|
|
27
|
+
# The period (in milliseconds) after which a keepalive ping is sent on the transport.
|
|
28
|
+
"grpc.keepalive_time_ms": 60 * 1000,
|
|
29
|
+
# The amount of time (in milliseconds) the sender of the keepalive ping waits for an
|
|
30
|
+
# acknowledgement. If it does not receive an acknowledgment within this time, it will
|
|
31
|
+
# close the connection.
|
|
32
|
+
"grpc.keepalive_timeout_ms": 3 * 60 * 1000,
|
|
33
|
+
# If set to 1 (0 : false; 1 : true), allows keepalive pings to be sent even if there
|
|
34
|
+
# are no calls in flight.
|
|
35
|
+
"grpc.keepalive_permit_without_calls": 0,
|
|
36
|
+
# How many pings can the client send before needing to send a data/header frame.
|
|
37
|
+
"grpc.http2.max_pings_without_data": 0,
|
|
38
|
+
} | self.config.cloud_server_endpoint.get("options", {})
|
|
39
|
+
|
|
40
|
+
self._cloud_server_endpoint = cloud_connector.CloudServerEndpoint(
|
|
41
|
+
self._sila_server, self.config.cloud_server_endpoint
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
self.register(SiLAService())
|
|
45
|
+
|
|
46
|
+
def register(self, feature: server.Feature):
|
|
47
|
+
"""Register a new feature to this driver"""
|
|
48
|
+
self.logger.debug("Added feature: %s", feature)
|
|
49
|
+
self._sila_server.add_feature(feature)
|
|
50
|
+
|
|
51
|
+
async def start(self):
|
|
52
|
+
"""
|
|
53
|
+
Start the connector and all related services.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
await asyncio.gather(
|
|
58
|
+
asyncio.create_task(self._sila_server.start()),
|
|
59
|
+
asyncio.create_task(self._broadcaster.start()),
|
|
60
|
+
asyncio.create_task(self._cloud_server_endpoint.start()),
|
|
61
|
+
)
|
|
62
|
+
except asyncio.CancelledError:
|
|
63
|
+
pass
|
|
64
|
+
finally:
|
|
65
|
+
await self.close()
|
|
66
|
+
|
|
67
|
+
async def close(self):
|
|
68
|
+
"""
|
|
69
|
+
Close the connector and all related services.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
for shutdown_handler in self._shutdown_handlers:
|
|
73
|
+
with contextlib.suppress(Exception):
|
|
74
|
+
if asyncio.iscoroutinefunction(shutdown_handler):
|
|
75
|
+
await shutdown_handler()
|
|
76
|
+
else:
|
|
77
|
+
shutdown_handler()
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def config(self) -> Config:
|
|
81
|
+
return self.__config
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def sila_server(self) -> server.Server:
|
|
85
|
+
return self._sila_server
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def logger(self) -> logging.Logger:
|
|
89
|
+
"""A standard Python :class:`~logging.Logger` for the app."""
|
|
90
|
+
return logging.getLogger(__package__)
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def debug(self) -> bool:
|
|
94
|
+
"""Whether debug mode is enabled."""
|
|
95
|
+
return True
|
|
96
|
+
|
|
97
|
+
def on_shutdown(self, handler: Handler) -> None:
|
|
98
|
+
"""
|
|
99
|
+
Add a shutdown hook to be called in the terminating phase (in response
|
|
100
|
+
to an explicit call to `app.close()` or upon receipt of system signals
|
|
101
|
+
such as SIGINT or SIGTERM).
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
handler: The method to be called on shutdown.
|
|
105
|
+
|
|
106
|
+
Raises:
|
|
107
|
+
TypeError: If the `handler` argument is not callable.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
if not callable(handler):
|
|
111
|
+
raise TypeError("The `handler` argument must be callable.")
|
|
112
|
+
|
|
113
|
+
self._shutdown_handlers.append(handler)
|
|
114
|
+
|
|
115
|
+
def off_shutdown(self, handler: Handler) -> None:
|
|
116
|
+
"""
|
|
117
|
+
Remove a previously added shutdown hook.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
handler: The handler to be removed from the shutdown hooks.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
self._shutdown_handlers.remove(handler)
|
|
125
|
+
except ValueError:
|
|
126
|
+
pass
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
import "SiLAFramework.proto";
|
|
4
|
+
|
|
5
|
+
package sila2.org.silastandard.core.lockcontroller.v2;
|
|
6
|
+
|
|
7
|
+
/* This Feature allows a SiLA Client to lock a SiLA Server for exclusive use, preventing other SiLA Clients from using the SiLA Server while it is locked. To lock a SiLA Server a Lock Identifier has to be set, using the 'LockServer' command. This Lock Identifier has to be sent along with every (lock protected) request to the SiLA Server in order to use its functionality. To send the lock identifier the SiLA Client Meta Data 'LockIdentifier' has to be used. When locking a SiLA Server a timeout can be specified that defines the time after which the SiLA Server will be automatically unlocked if no request with a valid lock identifier has been received meanwhile. After the timeout has expired or after explicit unlock no lock identifier has to be sent any more. */
|
|
8
|
+
service LockController {
|
|
9
|
+
/* Locks a SiLA Server for exclusive use by setting a lock identifier that has to be sent along with any following (lock protected) request as long as the SiLA Server is locked. The lock can be reset by issuing the 'Unlock Server' command. */
|
|
10
|
+
rpc LockServer (sila2.org.silastandard.core.lockcontroller.v2.LockServer_Parameters) returns (sila2.org.silastandard.core.lockcontroller.v2.LockServer_Responses) {}
|
|
11
|
+
/* Unlocks a locked SiLA Server. No lock identifier has to be sent for any following calls until the server is locked again via the 'Lock Server' command. */
|
|
12
|
+
rpc UnlockServer (sila2.org.silastandard.core.lockcontroller.v2.UnlockServer_Parameters) returns (sila2.org.silastandard.core.lockcontroller.v2.UnlockServer_Responses) {}
|
|
13
|
+
/*
|
|
14
|
+
Returns true if the SiLA Server is currently locked or false else.
|
|
15
|
+
|
|
16
|
+
This property MUST NOT be lock protected, so that any SiLA Client can query the current lock state
|
|
17
|
+
of a SiLA Server.
|
|
18
|
+
*/
|
|
19
|
+
rpc Subscribe_IsLocked (sila2.org.silastandard.core.lockcontroller.v2.Subscribe_IsLocked_Parameters) returns (stream sila2.org.silastandard.core.lockcontroller.v2.Subscribe_IsLocked_Responses) {}
|
|
20
|
+
/* Get fully qualified identifiers of all features, commands and properties affected by LockIdentifier */
|
|
21
|
+
rpc Get_FCPAffectedByMetadata_LockIdentifier (sila2.org.silastandard.core.lockcontroller.v2.Get_FCPAffectedByMetadata_LockIdentifier_Parameters) returns (sila2.org.silastandard.core.lockcontroller.v2.Get_FCPAffectedByMetadata_LockIdentifier_Responses) {}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Parameters for LockServer */
|
|
25
|
+
message LockServer_Parameters {
|
|
26
|
+
sila2.org.silastandard.String LockIdentifier = 1; /* The lock identifier that has to be sent along with every (lock protected) request to use the server's functionality. */
|
|
27
|
+
sila2.org.silastandard.Integer Timeout = 2; /* The time (in seconds) after a SiLA Server is automatically unlocked when no request with a valid lock identifier has been received meanwhile. A timeout of zero seconds specifies an infinite time (no timeout). */
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Responses of LockServer */
|
|
31
|
+
message LockServer_Responses {
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Parameters for UnlockServer */
|
|
35
|
+
message UnlockServer_Parameters {
|
|
36
|
+
sila2.org.silastandard.String LockIdentifier = 1; /* The lock identifier that has been used to lock the SiLA Server. */
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Responses of UnlockServer */
|
|
40
|
+
message UnlockServer_Responses {
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Parameters for IsLocked */
|
|
44
|
+
message Subscribe_IsLocked_Parameters {
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Responses of IsLocked */
|
|
48
|
+
message Subscribe_IsLocked_Responses {
|
|
49
|
+
sila2.org.silastandard.Boolean IsLocked = 1; /* Returns true if the SiLA Server is currently locked or false else. This property MUST NOT be lock protected, so that any SiLA Client can query the current lock state of a SiLA Server. */
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Parameters for Get_FCPAffectedByMetadata_LockIdentifier */
|
|
53
|
+
message Get_FCPAffectedByMetadata_LockIdentifier_Parameters {
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Responses of Get_FCPAffectedByMetadata_LockIdentifier */
|
|
57
|
+
message Get_FCPAffectedByMetadata_LockIdentifier_Responses {
|
|
58
|
+
repeated sila2.org.silastandard.String AffectedCalls = 1; /* Fully qualified identifiers of all features, commands and properties affected by LockIdentifier */
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/*
|
|
62
|
+
The lock identifier has to be sent with every (lock protected) call in order to use the functionality
|
|
63
|
+
of a locked SiLA Server.
|
|
64
|
+
*/
|
|
65
|
+
message Metadata_LockIdentifier {
|
|
66
|
+
sila2.org.silastandard.String LockIdentifier = 1; /* The lock identifier has to be sent with every (lock protected) call in order to use the functionality of a locked SiLA Server. */
|
|
67
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8" ?>
|
|
2
|
+
<Feature SiLA2Version="1.0" FeatureVersion="2.0" MaturityLevel="Draft" Originator="org.silastandard" Category="core"
|
|
3
|
+
xmlns="http://www.sila-standard.org"
|
|
4
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
5
|
+
xsi:schemaLocation="http://www.sila-standard.org https://gitlab.com/SiLA2/sila_base/raw/master/schema/FeatureDefinition.xsd">
|
|
6
|
+
<Identifier>LockController</Identifier>
|
|
7
|
+
<DisplayName>Lock Controller</DisplayName>
|
|
8
|
+
<Description>
|
|
9
|
+
This Feature allows a SiLA Client to lock a SiLA Server for exclusive use, preventing other SiLA Clients
|
|
10
|
+
from using the SiLA Server while it is locked. To lock a SiLA Server a Lock Identifier has to be set, using the
|
|
11
|
+
'LockServer' command. This Lock Identifier has to be sent along with every (lock protected)
|
|
12
|
+
request to the SiLA Server in order to use its functionality.
|
|
13
|
+
|
|
14
|
+
To send the lock identifier the SiLA Client Meta Data 'LockIdentifier' has to be used.
|
|
15
|
+
|
|
16
|
+
When locking a SiLA Server a timeout can be specified that defines the time after which the SiLA Server will
|
|
17
|
+
be automatically unlocked if no request with a valid lock identifier has been received meanwhile.
|
|
18
|
+
After the timeout has expired or after explicit unlock no lock identifier has to be sent any more.
|
|
19
|
+
</Description>
|
|
20
|
+
<Command>
|
|
21
|
+
<Identifier>LockServer</Identifier>
|
|
22
|
+
<DisplayName>Lock Server</DisplayName>
|
|
23
|
+
<Description>
|
|
24
|
+
Locks a SiLA Server for exclusive use by setting a lock identifier that has to be sent along with
|
|
25
|
+
any following (lock protected) request as long as the SiLA Server is locked.
|
|
26
|
+
The lock can be reset by issuing the 'Unlock Server' command.
|
|
27
|
+
</Description>
|
|
28
|
+
<Observable>No</Observable>
|
|
29
|
+
<Parameter>
|
|
30
|
+
<Identifier>LockIdentifier</Identifier>
|
|
31
|
+
<DisplayName>Lock Identifier</DisplayName>
|
|
32
|
+
<Description>
|
|
33
|
+
The lock identifier that has to be sent along with every (lock protected) request to use the server's
|
|
34
|
+
functionality.
|
|
35
|
+
</Description>
|
|
36
|
+
<DataType>
|
|
37
|
+
<Basic>String</Basic>
|
|
38
|
+
</DataType>
|
|
39
|
+
</Parameter>
|
|
40
|
+
<Parameter>
|
|
41
|
+
<Identifier>Timeout</Identifier>
|
|
42
|
+
<DisplayName>Timeout</DisplayName>
|
|
43
|
+
<Description>
|
|
44
|
+
The time (in seconds) after a SiLA Server is automatically unlocked when no request with a valid lock identifier
|
|
45
|
+
has been received meanwhile. A timeout of zero seconds specifies an infinite time (no timeout).
|
|
46
|
+
</Description>
|
|
47
|
+
<DataType>
|
|
48
|
+
<Constrained>
|
|
49
|
+
<DataType>
|
|
50
|
+
<Basic>Integer</Basic>
|
|
51
|
+
</DataType>
|
|
52
|
+
<Constraints>
|
|
53
|
+
<Unit>
|
|
54
|
+
<Label>s</Label>
|
|
55
|
+
<Factor>1</Factor>
|
|
56
|
+
<Offset>0</Offset>
|
|
57
|
+
<UnitComponent>
|
|
58
|
+
<SIUnit>Second</SIUnit>
|
|
59
|
+
<Exponent>1</Exponent>
|
|
60
|
+
</UnitComponent>
|
|
61
|
+
</Unit>
|
|
62
|
+
</Constraints>
|
|
63
|
+
</Constrained>
|
|
64
|
+
</DataType>
|
|
65
|
+
</Parameter>
|
|
66
|
+
<DefinedExecutionErrors>
|
|
67
|
+
<Identifier>ServerAlreadyLocked</Identifier>
|
|
68
|
+
</DefinedExecutionErrors>
|
|
69
|
+
</Command>
|
|
70
|
+
<Command>
|
|
71
|
+
<Identifier>UnlockServer</Identifier>
|
|
72
|
+
<DisplayName>Unlock Server</DisplayName>
|
|
73
|
+
<Description>
|
|
74
|
+
Unlocks a locked SiLA Server. No lock identifier has to be sent for any following calls until
|
|
75
|
+
the server is locked again via the 'Lock Server' command.
|
|
76
|
+
</Description>
|
|
77
|
+
<Observable>No</Observable>
|
|
78
|
+
<Parameter>
|
|
79
|
+
<Identifier>LockIdentifier</Identifier>
|
|
80
|
+
<DisplayName>Lock Identifier</DisplayName>
|
|
81
|
+
<Description>The lock identifier that has been used to lock the SiLA Server.</Description>
|
|
82
|
+
<DataType>
|
|
83
|
+
<Basic>String</Basic>
|
|
84
|
+
</DataType>
|
|
85
|
+
</Parameter>
|
|
86
|
+
<DefinedExecutionErrors>
|
|
87
|
+
<Identifier>ServerNotLocked</Identifier>
|
|
88
|
+
<Identifier>InvalidLockIdentifier</Identifier>
|
|
89
|
+
</DefinedExecutionErrors>
|
|
90
|
+
</Command>
|
|
91
|
+
<Property>
|
|
92
|
+
<Identifier>IsLocked</Identifier>
|
|
93
|
+
<DisplayName>Is Locked</DisplayName>
|
|
94
|
+
<Description>
|
|
95
|
+
Returns true if the SiLA Server is currently locked or false else.
|
|
96
|
+
|
|
97
|
+
This property MUST NOT be lock protected, so that any SiLA Client can query the current lock state
|
|
98
|
+
of a SiLA Server.
|
|
99
|
+
</Description>
|
|
100
|
+
<Observable>Yes</Observable>
|
|
101
|
+
<DataType>
|
|
102
|
+
<Basic>Boolean</Basic>
|
|
103
|
+
</DataType>
|
|
104
|
+
</Property>
|
|
105
|
+
<Metadata>
|
|
106
|
+
<Identifier>LockIdentifier</Identifier>
|
|
107
|
+
<DisplayName>Lock Identifier</DisplayName>
|
|
108
|
+
<Description>
|
|
109
|
+
The lock identifier has to be sent with every (lock protected) call in order to use the functionality
|
|
110
|
+
of a locked SiLA Server.
|
|
111
|
+
</Description>
|
|
112
|
+
<DataType>
|
|
113
|
+
<Basic>String</Basic>
|
|
114
|
+
</DataType>
|
|
115
|
+
<DefinedExecutionErrors>
|
|
116
|
+
<Identifier>InvalidLockIdentifier</Identifier>
|
|
117
|
+
</DefinedExecutionErrors>
|
|
118
|
+
</Metadata>
|
|
119
|
+
<DefinedExecutionError>
|
|
120
|
+
<Identifier>InvalidLockIdentifier</Identifier>
|
|
121
|
+
<DisplayName>Invalid Lock Identifier</DisplayName>
|
|
122
|
+
<Description>The sent lock identifier is not valid.</Description>
|
|
123
|
+
</DefinedExecutionError>
|
|
124
|
+
<DefinedExecutionError>
|
|
125
|
+
<Identifier>ServerAlreadyLocked</Identifier>
|
|
126
|
+
<DisplayName>Server Already Locked</DisplayName>
|
|
127
|
+
<Description>The SiLA Server can not be locked because it is already locked.</Description>
|
|
128
|
+
</DefinedExecutionError>
|
|
129
|
+
<DefinedExecutionError>
|
|
130
|
+
<Identifier>ServerNotLocked</Identifier>
|
|
131
|
+
<DisplayName>Server Not Locked</DisplayName>
|
|
132
|
+
<Description>The SiLA Server can not be unlocked because it is not locked.</Description>
|
|
133
|
+
</DefinedExecutionError>
|
|
134
|
+
</Feature>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .lock_controller import LockController
|
|
2
|
+
from .lock_controller_base import (
|
|
3
|
+
InvalidLockIdentifier,
|
|
4
|
+
LockControllerBase,
|
|
5
|
+
ServerAlreadyLocked,
|
|
6
|
+
ServerNotLocked,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"LockController",
|
|
11
|
+
"LockControllerBase",
|
|
12
|
+
"InvalidLockIdentifier",
|
|
13
|
+
"ServerAlreadyLocked",
|
|
14
|
+
"ServerNotLocked",
|
|
15
|
+
]
|