unitelabs-cdk 0.2.7__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.7/LICENSE +21 -0
- unitelabs_cdk-0.2.7/PKG-INFO +88 -0
- unitelabs_cdk-0.2.7/README.md +54 -0
- unitelabs_cdk-0.2.7/pyproject.toml +79 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/__init__.py +9 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/cli/__init__.py +4 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/cli/certificate.py +53 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/cli/connector.py +113 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/compose_app.py +147 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/config.py +13 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/connector.py +126 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/core/lock_controller/LockController-v2_0.proto +67 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/core/lock_controller/LockController-v2_0.sila.xml +134 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/core/lock_controller/__init__.py +15 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/core/lock_controller/lock_controller.py +111 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/core/lock_controller/lock_controller_base.py +83 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/core/sila_service/SiLAService-v1_0.proto +130 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/core/sila_service/SiLAService-v1_0.sila.xml +225 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/core/sila_service/__init__.py +3 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/core/sila_service/sila_service.py +146 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/core/simulation_controller/SimulationController-v1_0.sila.xml +76 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/core/simulation_controller/__init__.py +3 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/core/simulation_controller/simulation_controller_base.py +89 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/examples/greeting_provider/__init__.py +3 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/examples/greeting_provider/greeting_provider_base.py +34 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/examples/timer_provider/__init__.py +3 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/examples/timer_provider/timer_provider_base.py +59 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/robot/grip_controller/GripController-v1_0.proto +27 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/robot/grip_controller/GripController-v1_0.sila.xml +21 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/robot/grip_controller/__init__.py +3 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/robot/grip_controller/grip_controller_base.py +27 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/robot/labware_transfer_manipulator_controller/LabwareTransferManipulatorController-v1_0.proto +122 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/robot/labware_transfer_manipulator_controller/LabwareTransferManipulatorController-v1_0.sila.xml +333 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/robot/labware_transfer_manipulator_controller/__init__.py +17 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/robot/labware_transfer_manipulator_controller/labware_transfer_manipulator_controller_base.py +226 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/robot/movement_controller/MovementController-v1_0.proto +62 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/robot/movement_controller/MovementController-v1_0.sila.xml +94 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/robot/movement_controller/__init__.py +3 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/robot/movement_controller/movement_controller_base.py +69 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/basic_data_types_test/BasicDataTypesTest-v1_0.proto +170 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/basic_data_types_test/BasicDataTypesTest-v1_0.sila.xml +242 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/basic_data_types_test/__init__.py +3 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/basic_data_types_test/basic_data_types_test.py +158 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/error_handling_test/ErrorHandlingTest-v1_0.proto +123 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/error_handling_test/ErrorHandlingTest-v1_0.sila.xml +108 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/error_handling_test/__init__.py +3 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/error_handling_test/error_handling_test.py +102 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/list_data_type_test/ListDataTypeTest-v1_0.sila.xml +262 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/list_data_type_test/__init__.py +3 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/list_data_type_test/list_data_type_test.py +208 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/observable_command_test/ObservableCommandTest-v1_0.proto +49 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/observable_command_test/ObservableCommandTest-v1_0.sila.xml +74 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/observable_command_test/__init__.py +3 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/observable_command_test/observable_command_test.py +66 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/observable_property_test/ObservablePropertyTest-v1_0.proto +53 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/observable_property_test/ObservablePropertyTest-v1_0.sila.xml +52 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/observable_property_test/__init__.py +3 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/observable_property_test/observable_property_test.py +56 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/structure_data_type_test/StructureDataTypeTest-v1_0.sila.xml +256 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/structure_data_type_test/__init__.py +3 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/structure_data_type_test/structure_data_type_test.py +181 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/unobservable_command_test/UnobservableCommandTest-v1_0.proto +57 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/unobservable_command_test/UnobservableCommandTest-v1_0.sila.xml +110 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/unobservable_command_test/__init__.py +3 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/unobservable_command_test/unobservable_command_test.py +54 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/unobservable_property_test/UnobservablePropertyTest-v1_0.proto +31 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/unobservable_property_test/UnobservablePropertyTest-v1_0.sila.xml +27 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/unobservable_property_test/__init__.py +3 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/test/unobservable_property_test/unobservable_property_test.py +24 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/weighing/weighing_service/WeighingService-v1_0.proto +105 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/weighing/weighing_service/WeighingService-v1_0.sila.xml +249 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/weighing/weighing_service/__init__.py +3 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/features/weighing/weighing_service/weighing_service_base.py +97 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/logging.py +23 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/py.typed +0 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/sila/__init__.py +40 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/sila/commands/__init__.py +14 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/sila/commands/decorator.py +107 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/sila/commands/intermediate.py +16 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/sila/commands/intermediate_responses.py +101 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/sila/commands/parameters.py +132 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/sila/commands/responses.py +95 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/sila/commands/status.py +24 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/sila/data_types/__init__.py +44 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/sila/data_types/custom_data_type.py +22 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/sila/data_types/data_type_definition.py +38 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/sila/data_types/parser.py +98 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/sila/defined_execution_error.py +38 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/sila/feature.py +32 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/sila/metadata.py +19 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/sila/observable_command.py +111 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/sila/observable_property.py +83 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/sila/unobservable_command.py +100 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/sila/unobservable_property.py +71 -0
- unitelabs_cdk-0.2.7/src/unitelabs/cdk/sila/utils.py +75 -0
- unitelabs_cdk-0.2.7/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,88 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: unitelabs-cdk
|
|
3
|
+
Version: 0.2.7
|
|
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.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
25
|
+
Requires-Dist: click (==8.1.7)
|
|
26
|
+
Requires-Dist: pydantic_settings (==2.1.0)
|
|
27
|
+
Requires-Dist: python-dotenv (==1.0.1)
|
|
28
|
+
Requires-Dist: unitelabs-sila (==0.1.5)
|
|
29
|
+
Project-URL: Bug Tracker, https://gitlab.com/unitelabs/dev-tools/python-cdk/-/issues
|
|
30
|
+
Project-URL: Documentation, https://gitlab.com/unitelabs/dev-tools/python-cdk/-/tree/main/docs/
|
|
31
|
+
Project-URL: Repository, https://gitlab.com/unitelabs/dev-tools/python-cdk
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# UniteLabs Connector Framework
|
|
35
|
+
|
|
36
|
+
The UniteLabs Connector Framework is a free and open-source framework that enables you to build connectors for
|
|
37
|
+
laboratory hard- and software systems with interfaces that are based on industry standards like
|
|
38
|
+
[SiLA 2](https://sila-standard.com). If you plan on implementing an interface natively for your device or as wrapper
|
|
39
|
+
around an existing proprietary interface, you can use this framework to get it built quickly without deep-diving into
|
|
40
|
+
the standard specifications with our intuitive, code-first approach. It provides configurable modules you can use to
|
|
41
|
+
quickly integrate the hardware or software you want to connect.
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
[<img src="https://img.shields.io/badge/python-≥3.9.2-0052FF.svg?logo=LOGO&labelColor=090422">](LINK)
|
|
46
|
+
[<img src="https://img.shields.io/badge/poetry-≥1.3.1-0052FF.svg?logo=LOGO&labelColor=090422">](LINK)
|
|
47
|
+
|
|
48
|
+
The UniteLabs Connector Framework requires Python 3.9 or later. To get started quickly, we recommend to clone one of the
|
|
49
|
+
starter projects with:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
$ git clone --depth 1 https://gitlab.com/unitelabs/connector-starter.git <my-connector-name>
|
|
53
|
+
$ cd <my-connector-name>
|
|
54
|
+
$ poetry install
|
|
55
|
+
$ poetry run connector start
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
You can also manually create a new project from scratch and [install the framework](docs/1.get-started/2.installation.md)
|
|
59
|
+
with pip. In this case, of course, you'll be responsible for creating the project boilerplate files yourself.
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
$ pip install unitelabs-connector-framework
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Documentation
|
|
66
|
+
|
|
67
|
+
Explor the UniteLabs [Connector Development Kit documentation](https://docs-unitelabs-web-815d69b03b66df4c3a4817af38d7e7b09a17f7c4b50b.gitlab.io/connectivity/overview)
|
|
68
|
+
on our docs page. From there you can find your way to the tutorials and guides.
|
|
69
|
+
|
|
70
|
+
## Contribute
|
|
71
|
+
|
|
72
|
+
There are many ways to contribute to this project and our vision of freely and readily available interfaces for laboratory systems.
|
|
73
|
+
|
|
74
|
+
- Check out our [contribution guidelines](docs/6.community/1.contributing.md) to help us improve this project
|
|
75
|
+
- Join the over 400 developers in the [SiLA Slack community](https://sila-standard.org/slack)
|
|
76
|
+
- Give back to the community and add your connectors to the [UniteHub](https://hub.unitelabs.io) by sending us an
|
|
77
|
+
[email](mailto:connectors@unitelabs.io)!
|
|
78
|
+
- Get in touch with our developers regarding feedback and feature requests at [developers@unitelabs.io](mailto:developers@unitelabs.io)
|
|
79
|
+
- Give us a ⭐️ on [GitLab](https://gitlab.com/unitelabs/dev-tools/python-cdk)
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
We, UniteLabs, provide and maintain this free and open-source framework with the aim to enable the community to overcome
|
|
84
|
+
any barriers in digitalizing their laboratory environment. We highly appreciate, if the users of this framework value
|
|
85
|
+
the same principles. Therefore, if you want to make your connectors available for others, we encourage you to share them
|
|
86
|
+
on our sharing platform, the [UniteHub](https://hub.unitelabs.io). As we do not want to enforce disclosure of your work,
|
|
87
|
+
we distribute this framework under the [MIT license](LICENSE).
|
|
88
|
+
|
|
@@ -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,79 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "unitelabs-cdk"
|
|
3
|
+
version = "0.2.7"
|
|
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
|
+
python-dotenv = "1.0.1"
|
|
40
|
+
unitelabs-sila = {version = "0.1.5", source = "unitelabs"}
|
|
41
|
+
|
|
42
|
+
[tool.poetry.group.dev.dependencies]
|
|
43
|
+
ruff = "^0.5.1"
|
|
44
|
+
pytest = "7.4.4"
|
|
45
|
+
pytest-asyncio = "0.23.3"
|
|
46
|
+
pytest-cov = "4.1.0"
|
|
47
|
+
unitelabs-jsondocs = { version="0.1.1", source="unitelabs" }
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
[[tool.poetry.source]]
|
|
51
|
+
name = "unitelabs"
|
|
52
|
+
url = "https://gitlab.com/api/v4/groups/1009252/-/packages/pypi/simple"
|
|
53
|
+
priority = "supplemental"
|
|
54
|
+
|
|
55
|
+
[tool.ruff]
|
|
56
|
+
src = ["src", "tests"]
|
|
57
|
+
line-length = 120
|
|
58
|
+
target-version = "py39"
|
|
59
|
+
|
|
60
|
+
[tool.ruff.format]
|
|
61
|
+
docstring-code-format = true
|
|
62
|
+
|
|
63
|
+
[tool.ruff.lint.pydocstyle]
|
|
64
|
+
convention = "google"
|
|
65
|
+
|
|
66
|
+
[tool.pytest.ini_options]
|
|
67
|
+
pythonpath = ["src"]
|
|
68
|
+
testpaths = ["test"]
|
|
69
|
+
asyncio_mode = "auto"
|
|
70
|
+
|
|
71
|
+
[tool.coverage.report]
|
|
72
|
+
exclude_lines = [
|
|
73
|
+
"pragma: no cover",
|
|
74
|
+
"@typing.overload",
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
[build-system]
|
|
78
|
+
requires = ["poetry-core"]
|
|
79
|
+
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,53 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pathlib
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
import dotenv
|
|
7
|
+
|
|
8
|
+
from sila import server
|
|
9
|
+
from unitelabs.cdk import Config
|
|
10
|
+
|
|
11
|
+
config = Config()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.group()
|
|
15
|
+
def certificate() -> None:
|
|
16
|
+
"""Utility tool to handle certificates for TLS encryption."""
|
|
17
|
+
|
|
18
|
+
dotenv.load_dotenv()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@certificate.command()
|
|
22
|
+
@click.option(
|
|
23
|
+
"--uuid",
|
|
24
|
+
type=str,
|
|
25
|
+
default=config.sila_server.get("uuid", None),
|
|
26
|
+
required=True,
|
|
27
|
+
help="The SiLA server's uuid.",
|
|
28
|
+
)
|
|
29
|
+
@click.option(
|
|
30
|
+
"--host",
|
|
31
|
+
type=str,
|
|
32
|
+
default=lambda: config.sila_server.get("host", "0.0.0.0"),
|
|
33
|
+
help="The SiLA server's host address.",
|
|
34
|
+
)
|
|
35
|
+
@click.option(
|
|
36
|
+
"--target",
|
|
37
|
+
"-t",
|
|
38
|
+
type=str,
|
|
39
|
+
default=".",
|
|
40
|
+
help="The output directory in which to store the certificate files.",
|
|
41
|
+
)
|
|
42
|
+
def generate(uuid: str, host: str, target: str):
|
|
43
|
+
"""Generates a new self-signed certificate according to the SiLA 2 specification."""
|
|
44
|
+
key, cert = server.generate_certificate(uuid, host)
|
|
45
|
+
|
|
46
|
+
directory = pathlib.Path(target)
|
|
47
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
(directory / "cert.pem").write_bytes(cert)
|
|
49
|
+
(directory / "key.pem").write_bytes(key)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if __name__ == "__main__":
|
|
53
|
+
certificate()
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import importlib.machinery
|
|
3
|
+
import importlib.metadata
|
|
4
|
+
import importlib.util
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import pathlib
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
import dotenv
|
|
11
|
+
|
|
12
|
+
from unitelabs.cdk import AppFactory, compose_app, utils
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TLSConfigurationError(Exception):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@click.group()
|
|
20
|
+
def connector() -> None:
|
|
21
|
+
"""Base cli"""
|
|
22
|
+
|
|
23
|
+
dotenv.load_dotenv()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@connector.command()
|
|
27
|
+
@click.option(
|
|
28
|
+
"--app",
|
|
29
|
+
type=str,
|
|
30
|
+
metavar="IMPORT",
|
|
31
|
+
default=utils.find_factory,
|
|
32
|
+
help="The application factory function to load, in the form 'module:name'.",
|
|
33
|
+
)
|
|
34
|
+
@click.option(
|
|
35
|
+
"--tls/--no-tls",
|
|
36
|
+
required=False,
|
|
37
|
+
default=None,
|
|
38
|
+
envvar="SILA_SERVER__TLS",
|
|
39
|
+
help="Use TLS encryption. Alone, this will assume cert and key exist in `certificate.generate` default location.",
|
|
40
|
+
)
|
|
41
|
+
@click.option(
|
|
42
|
+
"-C",
|
|
43
|
+
"--cert",
|
|
44
|
+
required=False,
|
|
45
|
+
envvar="SILA_SERVER__CERT",
|
|
46
|
+
default=None,
|
|
47
|
+
help="The path to the certificate chain file, overriding .env setting of `SILA_SERVER__CERT`, default: ./cert.pem",
|
|
48
|
+
)
|
|
49
|
+
@click.option(
|
|
50
|
+
"-K",
|
|
51
|
+
"--key",
|
|
52
|
+
required=False,
|
|
53
|
+
envvar="SILA_SERVER__KEY",
|
|
54
|
+
default=None,
|
|
55
|
+
help="The path to the private key file, overriding .env setting of `SILA_SERVER__KEY`, default: ./key.pem",
|
|
56
|
+
)
|
|
57
|
+
@click.option(
|
|
58
|
+
"-v",
|
|
59
|
+
"--verbose",
|
|
60
|
+
count=True,
|
|
61
|
+
help="Increase the verbosity of to debug.",
|
|
62
|
+
)
|
|
63
|
+
@utils.coroutine
|
|
64
|
+
async def start(app, tls, cert, key, verbose: int):
|
|
65
|
+
"""Application Entrypoint."""
|
|
66
|
+
if tls is not None:
|
|
67
|
+
os.environ["SILA_SERVER__TLS"] = str(tls)
|
|
68
|
+
|
|
69
|
+
if tls:
|
|
70
|
+
cert = pathlib.Path(cert) if cert else pathlib.Path("./cert.pem")
|
|
71
|
+
key = pathlib.Path(key) if key else pathlib.Path("./key.pem")
|
|
72
|
+
cert = cert.resolve()
|
|
73
|
+
key = key.resolve()
|
|
74
|
+
|
|
75
|
+
msg = ""
|
|
76
|
+
if not cert.exists():
|
|
77
|
+
msg += f"Certificate file at {cert} does not exist. Specify path with --cert. "
|
|
78
|
+
|
|
79
|
+
if not key.exists():
|
|
80
|
+
msg += f"Private key file at {key} does not exist. Specify path with --key."
|
|
81
|
+
|
|
82
|
+
if msg:
|
|
83
|
+
raise TLSConfigurationError(msg)
|
|
84
|
+
|
|
85
|
+
os.environb[b"SILA_SERVER__CERT"] = cert.read_bytes()
|
|
86
|
+
os.environb[b"SILA_SERVER__KEY"] = key.read_bytes()
|
|
87
|
+
|
|
88
|
+
log_level = logging.DEBUG if verbose > 0 else logging.INFO
|
|
89
|
+
|
|
90
|
+
create_app = await load_create_app(app)
|
|
91
|
+
app = await compose_app(create_app, log_level=log_level)
|
|
92
|
+
|
|
93
|
+
await app.start()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
async def load_create_app(location: str) -> AppFactory:
|
|
97
|
+
"""
|
|
98
|
+
Dynamically import the application factory from the given location.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
location: Where to find the app factory formatted as "module:name".
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
module_name, _, factory_name = location.partition(":")
|
|
105
|
+
|
|
106
|
+
module = importlib.import_module(module_name)
|
|
107
|
+
create_app = getattr(module, factory_name)
|
|
108
|
+
|
|
109
|
+
return create_app
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
if __name__ == "__main__":
|
|
113
|
+
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,13 @@
|
|
|
1
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
2
|
+
|
|
3
|
+
from sila import cloud_connector, server
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Config(BaseSettings):
|
|
7
|
+
model_config = SettingsConfigDict(
|
|
8
|
+
env_file=".env", env_file_encoding="utf-8", env_nested_delimiter="__", extra="ignore"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
environment: str = "development"
|
|
12
|
+
sila_server: server.ServerConfig = server.ServerConfig()
|
|
13
|
+
cloud_server_endpoint: cloud_connector.CloudServerEndpointConfig = cloud_connector.CloudServerEndpointConfig()
|