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.
Files changed (90) hide show
  1. unitelabs_cdk-0.2.4/LICENSE +21 -0
  2. unitelabs_cdk-0.2.4/PKG-INFO +86 -0
  3. unitelabs_cdk-0.2.4/README.md +54 -0
  4. unitelabs_cdk-0.2.4/pyproject.toml +78 -0
  5. unitelabs_cdk-0.2.4/src/unitelabs/cdk/__init__.py +9 -0
  6. unitelabs_cdk-0.2.4/src/unitelabs/cdk/cli/__init__.py +4 -0
  7. unitelabs_cdk-0.2.4/src/unitelabs/cdk/cli/certificate.py +50 -0
  8. unitelabs_cdk-0.2.4/src/unitelabs/cdk/cli/connector.py +59 -0
  9. unitelabs_cdk-0.2.4/src/unitelabs/cdk/compose_app.py +147 -0
  10. unitelabs_cdk-0.2.4/src/unitelabs/cdk/config.py +12 -0
  11. unitelabs_cdk-0.2.4/src/unitelabs/cdk/connector.py +126 -0
  12. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/core/lock_controller/LockController-v2_0.proto +67 -0
  13. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/core/lock_controller/LockController-v2_0.sila.xml +134 -0
  14. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/core/lock_controller/__init__.py +15 -0
  15. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/core/lock_controller/lock_controller.py +111 -0
  16. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/core/lock_controller/lock_controller_base.py +83 -0
  17. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/core/sila_service/SiLAService-v1_0.proto +130 -0
  18. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/core/sila_service/SiLAService-v1_0.sila.xml +225 -0
  19. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/core/sila_service/__init__.py +3 -0
  20. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/core/sila_service/sila_service.py +146 -0
  21. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/core/simulation_controller/SimulationController-v1_0.sila.xml +76 -0
  22. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/core/simulation_controller/__init__.py +3 -0
  23. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/core/simulation_controller/simulation_controller_base.py +89 -0
  24. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/examples/greeting_provider/__init__.py +3 -0
  25. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/examples/greeting_provider/greeting_provider_base.py +34 -0
  26. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/examples/timer_provider/__init__.py +3 -0
  27. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/examples/timer_provider/timer_provider_base.py +60 -0
  28. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/robot/grip_controller/GripController-v1_0.proto +27 -0
  29. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/robot/grip_controller/GripController-v1_0.sila.xml +21 -0
  30. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/robot/grip_controller/__init__.py +3 -0
  31. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/robot/grip_controller/grip_controller_base.py +27 -0
  32. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/robot/labware_transfer_manipulator_controller/LabwareTransferManipulatorController-v1_0.proto +122 -0
  33. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/robot/labware_transfer_manipulator_controller/LabwareTransferManipulatorController-v1_0.sila.xml +333 -0
  34. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/robot/labware_transfer_manipulator_controller/__init__.py +17 -0
  35. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/robot/labware_transfer_manipulator_controller/labware_transfer_manipulator_controller_base.py +226 -0
  36. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/robot/movement_controller/MovementController-v1_0.proto +62 -0
  37. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/robot/movement_controller/MovementController-v1_0.sila.xml +94 -0
  38. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/robot/movement_controller/__init__.py +3 -0
  39. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/robot/movement_controller/movement_controller_base.py +69 -0
  40. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/basic_data_types_test/BasicDataTypesTest-v1_0.proto +170 -0
  41. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/basic_data_types_test/BasicDataTypesTest-v1_0.sila.xml +242 -0
  42. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/basic_data_types_test/__init__.py +3 -0
  43. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/basic_data_types_test/basic_data_types_test.py +158 -0
  44. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/list_data_type_test/ListDataTypeTest-v1_0.sila.xml +262 -0
  45. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/list_data_type_test/__init__.py +3 -0
  46. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/list_data_type_test/list_data_type_test.py +208 -0
  47. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/observable_command_test/ObservableCommandTest-v1_0.proto +49 -0
  48. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/observable_command_test/ObservableCommandTest-v1_0.sila.xml +74 -0
  49. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/observable_command_test/__init__.py +3 -0
  50. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/observable_command_test/observable_command_test.py +66 -0
  51. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/observable_property_test/ObservablePropertyTest-v1_0.proto +53 -0
  52. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/observable_property_test/ObservablePropertyTest-v1_0.sila.xml +52 -0
  53. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/observable_property_test/__init__.py +3 -0
  54. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/observable_property_test/observable_property_test.py +56 -0
  55. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/structure_data_type_test/StructureDataTypeTest-v1_0.sila.xml +256 -0
  56. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/structure_data_type_test/__init__.py +3 -0
  57. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/structure_data_type_test/structure_data_type_test.py +181 -0
  58. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/unobservable_command_test/UnobservableCommandTest-v1_0.proto +57 -0
  59. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/unobservable_command_test/UnobservableCommandTest-v1_0.sila.xml +110 -0
  60. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/unobservable_command_test/__init__.py +3 -0
  61. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/unobservable_command_test/unobservable_command_test.py +54 -0
  62. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/unobservable_property_test/UnobservablePropertyTest-v1_0.proto +31 -0
  63. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/unobservable_property_test/UnobservablePropertyTest-v1_0.sila.xml +27 -0
  64. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/unobservable_property_test/__init__.py +3 -0
  65. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/test/unobservable_property_test/unobservable_property_test.py +24 -0
  66. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/weighing/weighing_service/WeighingService-v1_0.proto +105 -0
  67. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/weighing/weighing_service/WeighingService-v1_0.sila.xml +249 -0
  68. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/weighing/weighing_service/__init__.py +3 -0
  69. unitelabs_cdk-0.2.4/src/unitelabs/cdk/features/weighing/weighing_service/weighing_service_base.py +97 -0
  70. unitelabs_cdk-0.2.4/src/unitelabs/cdk/logging.py +23 -0
  71. unitelabs_cdk-0.2.4/src/unitelabs/cdk/py.typed +0 -0
  72. unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/__init__.py +40 -0
  73. unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/commands/__init__.py +0 -0
  74. unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/commands/intermediate.py +16 -0
  75. unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/commands/intermediate_response.py +36 -0
  76. unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/commands/response.py +44 -0
  77. unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/commands/status.py +24 -0
  78. unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/data_types/__init__.py +44 -0
  79. unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/data_types/custom_data_type.py +22 -0
  80. unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/data_types/data_type_definition.py +38 -0
  81. unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/data_types/parser.py +95 -0
  82. unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/defined_execution_error.py +18 -0
  83. unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/feature.py +32 -0
  84. unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/metadata.py +19 -0
  85. unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/observable_command.py +120 -0
  86. unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/observable_property.py +48 -0
  87. unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/unobservable_command.py +96 -0
  88. unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/unobservable_property.py +43 -0
  89. unitelabs_cdk-0.2.4/src/unitelabs/cdk/sila/utils.py +74 -0
  90. 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&amp;labelColor=090422">](LINK)
44
+ [<img src="https://img.shields.io/badge/poetry-≥1.3.1-0052FF.svg?logo=LOGO&amp;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&amp;labelColor=090422">](LINK)
13
+ [<img src="https://img.shields.io/badge/poetry-≥1.3.1-0052FF.svg?logo=LOGO&amp;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,4 @@
1
+ from .certificate import certificate
2
+ from .connector import connector
3
+
4
+ __all__ = ["certificate", "connector"]
@@ -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