unitelabs-cdk 0.2.4__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.
- unitelabs_cdk-0.2.4/LICENSE +21 -0
- unitelabs_cdk-0.2.4/PKG-INFO +86 -0
- unitelabs_cdk-0.2.4/README.md +54 -0
- unitelabs_cdk-0.2.4/pyproject.toml +78 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/__init__.py +9 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/cli/__init__.py +4 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/cli/certificate.py +50 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/cli/connector.py +59 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/compose_app.py +147 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/config.py +12 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/connector.py +126 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/core/lock_controller/LockController-v2_0.proto +67 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/core/lock_controller/LockController-v2_0.sila.xml +134 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/core/lock_controller/__init__.py +15 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/core/lock_controller/lock_controller.py +111 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/core/lock_controller/lock_controller_base.py +83 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/core/sila_service/SiLAService-v1_0.proto +130 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/core/sila_service/SiLAService-v1_0.sila.xml +225 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/core/sila_service/__init__.py +3 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/core/sila_service/sila_service.py +146 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/core/simulation_controller/SimulationController-v1_0.sila.xml +76 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/core/simulation_controller/__init__.py +3 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/core/simulation_controller/simulation_controller_base.py +89 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/examples/greeting_provider/__init__.py +3 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/examples/greeting_provider/greeting_provider_base.py +34 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/examples/timer_provider/__init__.py +3 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/examples/timer_provider/timer_provider_base.py +60 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/robot/grip_controller/GripController-v1_0.proto +27 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/robot/grip_controller/GripController-v1_0.sila.xml +21 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/robot/grip_controller/__init__.py +3 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/robot/grip_controller/grip_controller_base.py +27 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/robot/labware_transfer_manipulator_controller/LabwareTransferManipulatorController-v1_0.proto +122 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/robot/labware_transfer_manipulator_controller/LabwareTransferManipulatorController-v1_0.sila.xml +333 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/robot/labware_transfer_manipulator_controller/__init__.py +17 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/robot/labware_transfer_manipulator_controller/labware_transfer_manipulator_controller_base.py +226 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/robot/movement_controller/MovementController-v1_0.proto +62 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/robot/movement_controller/MovementController-v1_0.sila.xml +94 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/robot/movement_controller/__init__.py +3 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/robot/movement_controller/movement_controller_base.py +69 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/basic_data_types_test/BasicDataTypesTest-v1_0.proto +170 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/basic_data_types_test/BasicDataTypesTest-v1_0.sila.xml +242 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/basic_data_types_test/__init__.py +3 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/basic_data_types_test/basic_data_types_test.py +158 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/list_data_type_test/ListDataTypeTest-v1_0.sila.xml +262 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/list_data_type_test/__init__.py +3 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/list_data_type_test/list_data_type_test.py +208 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/observable_command_test/ObservableCommandTest-v1_0.proto +49 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/observable_command_test/ObservableCommandTest-v1_0.sila.xml +74 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/observable_command_test/__init__.py +3 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/observable_command_test/observable_command_test.py +66 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/observable_property_test/ObservablePropertyTest-v1_0.proto +53 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/observable_property_test/ObservablePropertyTest-v1_0.sila.xml +52 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/observable_property_test/__init__.py +3 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/observable_property_test/observable_property_test.py +56 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/structure_data_type_test/StructureDataTypeTest-v1_0.sila.xml +256 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/structure_data_type_test/__init__.py +3 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/structure_data_type_test/structure_data_type_test.py +181 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/unobservable_command_test/UnobservableCommandTest-v1_0.proto +57 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/unobservable_command_test/UnobservableCommandTest-v1_0.sila.xml +110 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/unobservable_command_test/__init__.py +3 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/unobservable_command_test/unobservable_command_test.py +54 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/unobservable_property_test/UnobservablePropertyTest-v1_0.proto +31 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/unobservable_property_test/UnobservablePropertyTest-v1_0.sila.xml +27 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/unobservable_property_test/__init__.py +3 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/unobservable_property_test/unobservable_property_test.py +24 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/weighing/weighing_service/WeighingService-v1_0.proto +105 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/weighing/weighing_service/WeighingService-v1_0.sila.xml +249 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/weighing/weighing_service/__init__.py +3 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/weighing/weighing_service/weighing_service_base.py +97 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/logging.py +23 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/py.typed +0 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/__init__.py +40 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/commands/__init__.py +0 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/commands/intermediate.py +16 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/commands/intermediate_response.py +36 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/commands/response.py +44 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/commands/status.py +24 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/data_types/__init__.py +44 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/data_types/custom_data_type.py +22 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/data_types/data_type_definition.py +38 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/data_types/parser.py +95 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/defined_execution_error.py +18 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/feature.py +32 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/metadata.py +19 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/observable_command.py +120 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/observable_property.py +48 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/unobservable_command.py +96 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/unobservable_property.py +43 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/utils.py +74 -0
- unitelabs_cdk-0.2.4/src/unitelabs/cdk/utils.py +117 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 UniteLabs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: unitelabs-cdk
|
|
3
|
+
Version: 0.2.4
|
|
4
|
+
Summary: A framework to develop UniteLabs connectors based on the SiLA 2 standard specification.
|
|
5
|
+
Home-page: https://unitelabs.io
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: UniteLabs,SiLA 2,laboratory,automation,connectivity
|
|
8
|
+
Author: UniteLabs
|
|
9
|
+
Author-email: developers+cdk@unitelabs.io
|
|
10
|
+
Requires-Python: >=3.9,<4.0
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Healthcare Industry
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
24
|
+
Requires-Dist: click (==8.1.7)
|
|
25
|
+
Requires-Dist: pydantic_settings (==2.1.0)
|
|
26
|
+
Requires-Dist: unitelabs-sila (==0.1.5)
|
|
27
|
+
Project-URL: Bug Tracker, https://gitlab.com/unitelabs/dev-tools/python-cdk/-/issues
|
|
28
|
+
Project-URL: Documentation, https://gitlab.com/unitelabs/dev-tools/python-cdk/-/tree/main/docs/
|
|
29
|
+
Project-URL: Repository, https://gitlab.com/unitelabs/dev-tools/python-cdk
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# UniteLabs Connector Framework
|
|
33
|
+
|
|
34
|
+
The UniteLabs Connector Framework is a free and open-source framework that enables you to build connectors for
|
|
35
|
+
laboratory hard- and software systems with interfaces that are based on industry standards like
|
|
36
|
+
[SiLA 2](https://sila-standard.com). If you plan on implementing an interface natively for your device or as wrapper
|
|
37
|
+
around an existing proprietary interface, you can use this framework to get it built quickly without deep-diving into
|
|
38
|
+
the standard specifications with our intuitive, code-first approach. It provides configurable modules you can use to
|
|
39
|
+
quickly integrate the hardware or software you want to connect.
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
[<img src="https://img.shields.io/badge/python-≥3.9.2-0052FF.svg?logo=LOGO&labelColor=090422">](LINK)
|
|
44
|
+
[<img src="https://img.shields.io/badge/poetry-≥1.3.1-0052FF.svg?logo=LOGO&labelColor=090422">](LINK)
|
|
45
|
+
|
|
46
|
+
The UniteLabs Connector Framework requires Python 3.9 or later. To get started quickly, we recommend to clone one of the
|
|
47
|
+
starter projects with:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
$ git clone --depth 1 https://gitlab.com/unitelabs/connector-starter.git <my-connector-name>
|
|
51
|
+
$ cd <my-connector-name>
|
|
52
|
+
$ poetry install
|
|
53
|
+
$ poetry run connector start
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
You can also manually create a new project from scratch and [install the framework](docs/1.get-started/2.installation.md)
|
|
57
|
+
with pip. In this case, of course, you'll be responsible for creating the project boilerplate files yourself.
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
$ pip install unitelabs-connector-framework
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Documentation
|
|
64
|
+
|
|
65
|
+
Explor the UniteLabs [Connector Development Kit documentation](https://docs-unitelabs-web-815d69b03b66df4c3a4817af38d7e7b09a17f7c4b50b.gitlab.io/connectivity/overview)
|
|
66
|
+
on our docs page. From there you can find your way to the tutorials and guides.
|
|
67
|
+
|
|
68
|
+
## Contribute
|
|
69
|
+
|
|
70
|
+
There are many ways to contribute to this project and our vision of freely and readily available interfaces for laboratory systems.
|
|
71
|
+
|
|
72
|
+
- Check out our [contribution guidelines](docs/6.community/1.contributing.md) to help us improve this project
|
|
73
|
+
- Join the over 400 developers in the [SiLA Slack community](https://sila-standard.org/slack)
|
|
74
|
+
- Give back to the community and add your connectors to the [UniteHub](https://hub.unitelabs.io) by sending us an
|
|
75
|
+
[email](mailto:connectors@unitelabs.io)!
|
|
76
|
+
- Get in touch with our developers regarding feedback and feature requests at [developers@unitelabs.io](mailto:developers@unitelabs.io)
|
|
77
|
+
- Give us a ⭐️ on [GitLab](https://gitlab.com/unitelabs/dev-tools/python-cdk)
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
We, UniteLabs, provide and maintain this free and open-source framework with the aim to enable the community to overcome
|
|
82
|
+
any barriers in digitalizing their laboratory environment. We highly appreciate, if the users of this framework value
|
|
83
|
+
the same principles. Therefore, if you want to make your connectors available for others, we encourage you to share them
|
|
84
|
+
on our sharing platform, the [UniteHub](https://hub.unitelabs.io). As we do not want to enforce disclosure of your work,
|
|
85
|
+
we distribute this framework under the [MIT license](LICENSE).
|
|
86
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# UniteLabs Connector Framework
|
|
2
|
+
|
|
3
|
+
The UniteLabs Connector Framework is a free and open-source framework that enables you to build connectors for
|
|
4
|
+
laboratory hard- and software systems with interfaces that are based on industry standards like
|
|
5
|
+
[SiLA 2](https://sila-standard.com). If you plan on implementing an interface natively for your device or as wrapper
|
|
6
|
+
around an existing proprietary interface, you can use this framework to get it built quickly without deep-diving into
|
|
7
|
+
the standard specifications with our intuitive, code-first approach. It provides configurable modules you can use to
|
|
8
|
+
quickly integrate the hardware or software you want to connect.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
[<img src="https://img.shields.io/badge/python-≥3.9.2-0052FF.svg?logo=LOGO&labelColor=090422">](LINK)
|
|
13
|
+
[<img src="https://img.shields.io/badge/poetry-≥1.3.1-0052FF.svg?logo=LOGO&labelColor=090422">](LINK)
|
|
14
|
+
|
|
15
|
+
The UniteLabs Connector Framework requires Python 3.9 or later. To get started quickly, we recommend to clone one of the
|
|
16
|
+
starter projects with:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
$ git clone --depth 1 https://gitlab.com/unitelabs/connector-starter.git <my-connector-name>
|
|
20
|
+
$ cd <my-connector-name>
|
|
21
|
+
$ poetry install
|
|
22
|
+
$ poetry run connector start
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
You can also manually create a new project from scratch and [install the framework](docs/1.get-started/2.installation.md)
|
|
26
|
+
with pip. In this case, of course, you'll be responsible for creating the project boilerplate files yourself.
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
$ pip install unitelabs-connector-framework
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Documentation
|
|
33
|
+
|
|
34
|
+
Explor the UniteLabs [Connector Development Kit documentation](https://docs-unitelabs-web-815d69b03b66df4c3a4817af38d7e7b09a17f7c4b50b.gitlab.io/connectivity/overview)
|
|
35
|
+
on our docs page. From there you can find your way to the tutorials and guides.
|
|
36
|
+
|
|
37
|
+
## Contribute
|
|
38
|
+
|
|
39
|
+
There are many ways to contribute to this project and our vision of freely and readily available interfaces for laboratory systems.
|
|
40
|
+
|
|
41
|
+
- Check out our [contribution guidelines](docs/6.community/1.contributing.md) to help us improve this project
|
|
42
|
+
- Join the over 400 developers in the [SiLA Slack community](https://sila-standard.org/slack)
|
|
43
|
+
- Give back to the community and add your connectors to the [UniteHub](https://hub.unitelabs.io) by sending us an
|
|
44
|
+
[email](mailto:connectors@unitelabs.io)!
|
|
45
|
+
- Get in touch with our developers regarding feedback and feature requests at [developers@unitelabs.io](mailto:developers@unitelabs.io)
|
|
46
|
+
- Give us a ⭐️ on [GitLab](https://gitlab.com/unitelabs/dev-tools/python-cdk)
|
|
47
|
+
|
|
48
|
+
## License
|
|
49
|
+
|
|
50
|
+
We, UniteLabs, provide and maintain this free and open-source framework with the aim to enable the community to overcome
|
|
51
|
+
any barriers in digitalizing their laboratory environment. We highly appreciate, if the users of this framework value
|
|
52
|
+
the same principles. Therefore, if you want to make your connectors available for others, we encourage you to share them
|
|
53
|
+
on our sharing platform, the [UniteHub](https://hub.unitelabs.io). As we do not want to enforce disclosure of your work,
|
|
54
|
+
we distribute this framework under the [MIT license](LICENSE).
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "unitelabs-cdk"
|
|
3
|
+
version = "0.2.4"
|
|
4
|
+
description = "A framework to develop UniteLabs connectors based on the SiLA 2 standard specification."
|
|
5
|
+
license = "MIT"
|
|
6
|
+
authors = ["UniteLabs <developers+cdk@unitelabs.io>"]
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
homepage = "https://unitelabs.io"
|
|
9
|
+
repository = "https://gitlab.com/unitelabs/dev-tools/python-cdk"
|
|
10
|
+
documentation = "https://gitlab.com/unitelabs/dev-tools/python-cdk/-/tree/main/docs/"
|
|
11
|
+
keywords = ["UniteLabs", "SiLA 2", "laboratory", "automation", "connectivity"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"Intended Audience :: Healthcare Industry",
|
|
16
|
+
"Intended Audience :: Science/Research",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
20
|
+
"Programming Language :: Python :: 3.9",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Topic :: Software Development :: Libraries",
|
|
25
|
+
]
|
|
26
|
+
packages = [{ include = "unitelabs", from = "src" }]
|
|
27
|
+
|
|
28
|
+
[tool.poetry.urls]
|
|
29
|
+
"Bug Tracker" = "https://gitlab.com/unitelabs/dev-tools/python-cdk/-/issues"
|
|
30
|
+
|
|
31
|
+
[tool.poetry.scripts]
|
|
32
|
+
certificate = 'unitelabs.cdk.cli:certificate'
|
|
33
|
+
connector = 'unitelabs.cdk.cli:connector'
|
|
34
|
+
|
|
35
|
+
[tool.poetry.dependencies]
|
|
36
|
+
python = "^3.9"
|
|
37
|
+
click = "8.1.7"
|
|
38
|
+
pydantic_settings = "2.1.0"
|
|
39
|
+
unitelabs-sila = {version = "0.1.5", source = "unitelabs"}
|
|
40
|
+
|
|
41
|
+
[tool.poetry.group.dev.dependencies]
|
|
42
|
+
ruff = "^0.5.1"
|
|
43
|
+
pytest = "7.4.4"
|
|
44
|
+
pytest-asyncio = "0.23.3"
|
|
45
|
+
pytest-cov = "4.1.0"
|
|
46
|
+
unitelabs-jsondocs = { version="0.1.1", source="unitelabs" }
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
[[tool.poetry.source]]
|
|
50
|
+
name = "unitelabs"
|
|
51
|
+
url = "https://gitlab.com/api/v4/groups/1009252/-/packages/pypi/simple"
|
|
52
|
+
priority = "supplemental"
|
|
53
|
+
|
|
54
|
+
[tool.ruff]
|
|
55
|
+
src = ["src", "tests"]
|
|
56
|
+
line-length = 120
|
|
57
|
+
target-version = "py39"
|
|
58
|
+
|
|
59
|
+
[tool.ruff.format]
|
|
60
|
+
docstring-code-format = true
|
|
61
|
+
|
|
62
|
+
[tool.ruff.lint.pydocstyle]
|
|
63
|
+
convention = "google"
|
|
64
|
+
|
|
65
|
+
[tool.pytest.ini_options]
|
|
66
|
+
pythonpath = ["src"]
|
|
67
|
+
testpaths = ["test"]
|
|
68
|
+
asyncio_mode = "auto"
|
|
69
|
+
|
|
70
|
+
[tool.coverage.report]
|
|
71
|
+
exclude_lines = [
|
|
72
|
+
"pragma: no cover",
|
|
73
|
+
"@typing.overload",
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
[build-system]
|
|
77
|
+
requires = ["poetry-core"]
|
|
78
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -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
|
|
@@ -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
|