enapter 0.25.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. enapter-0.25.0/LICENSE +21 -0
  2. enapter-0.25.0/PKG-INFO +70 -0
  3. enapter-0.25.0/README.md +54 -0
  4. enapter-0.25.0/pyproject.toml +53 -0
  5. enapter-0.25.0/setup.cfg +4 -0
  6. enapter-0.25.0/src/enapter/__init__.py +13 -0
  7. enapter-0.25.0/src/enapter/__main__.py +9 -0
  8. enapter-0.25.0/src/enapter/async_/__init__.py +4 -0
  9. enapter-0.25.0/src/enapter/async_/generator.py +18 -0
  10. enapter-0.25.0/src/enapter/async_/routine.py +37 -0
  11. enapter-0.25.0/src/enapter/cli/__init__.py +6 -0
  12. enapter-0.25.0/src/enapter/cli/app.py +37 -0
  13. enapter-0.25.0/src/enapter/cli/command.py +16 -0
  14. enapter-0.25.0/src/enapter/cli/http/__init__.py +3 -0
  15. enapter-0.25.0/src/enapter/cli/http/api/__init__.py +3 -0
  16. enapter-0.25.0/src/enapter/cli/http/api/blueprint_command.py +43 -0
  17. enapter-0.25.0/src/enapter/cli/http/api/blueprint_download_command.py +41 -0
  18. enapter-0.25.0/src/enapter/cli/http/api/blueprint_get_command.py +23 -0
  19. enapter-0.25.0/src/enapter/cli/http/api/blueprint_profiles_command.py +29 -0
  20. enapter-0.25.0/src/enapter/cli/http/api/blueprint_profiles_download_command.py +31 -0
  21. enapter-0.25.0/src/enapter/cli/http/api/blueprint_upload_command.py +29 -0
  22. enapter-0.25.0/src/enapter/cli/http/api/blueprint_validate_command.py +27 -0
  23. enapter-0.25.0/src/enapter/cli/http/api/command.py +47 -0
  24. enapter-0.25.0/src/enapter/cli/http/api/command_arguments.py +11 -0
  25. enapter-0.25.0/src/enapter/cli/http/api/command_command.py +39 -0
  26. enapter-0.25.0/src/enapter/cli/http/api/command_create_execution_command.py +39 -0
  27. enapter-0.25.0/src/enapter/cli/http/api/command_execute_command.py +48 -0
  28. enapter-0.25.0/src/enapter/cli/http/api/command_get_execution_command.py +41 -0
  29. enapter-0.25.0/src/enapter/cli/http/api/command_list_executions_command.py +99 -0
  30. enapter-0.25.0/src/enapter/cli/http/api/device_assign_blueprint_command.py +28 -0
  31. enapter-0.25.0/src/enapter/cli/http/api/device_command.py +61 -0
  32. enapter-0.25.0/src/enapter/cli/http/api/device_create_lua_command.py +32 -0
  33. enapter-0.25.0/src/enapter/cli/http/api/device_create_standalone_command.py +26 -0
  34. enapter-0.25.0/src/enapter/cli/http/api/device_create_vucm_command.py +24 -0
  35. enapter-0.25.0/src/enapter/cli/http/api/device_delete_command.py +27 -0
  36. enapter-0.25.0/src/enapter/cli/http/api/device_generate_communication_config_command.py +33 -0
  37. enapter-0.25.0/src/enapter/cli/http/api/device_get_command.py +59 -0
  38. enapter-0.25.0/src/enapter/cli/http/api/device_list_command.py +72 -0
  39. enapter-0.25.0/src/enapter/cli/http/api/device_update_command.py +28 -0
  40. enapter-0.25.0/src/enapter/cli/http/api/rule_command.py +60 -0
  41. enapter-0.25.0/src/enapter/cli/http/api/rule_create_command.py +61 -0
  42. enapter-0.25.0/src/enapter/cli/http/api/rule_delete_command.py +24 -0
  43. enapter-0.25.0/src/enapter/cli/http/api/rule_disable_command.py +28 -0
  44. enapter-0.25.0/src/enapter/cli/http/api/rule_enable_command.py +28 -0
  45. enapter-0.25.0/src/enapter/cli/http/api/rule_engine_command.py +44 -0
  46. enapter-0.25.0/src/enapter/cli/http/api/rule_engine_get_command.py +25 -0
  47. enapter-0.25.0/src/enapter/cli/http/api/rule_engine_resume_command.py +25 -0
  48. enapter-0.25.0/src/enapter/cli/http/api/rule_engine_suspend_command.py +25 -0
  49. enapter-0.25.0/src/enapter/cli/http/api/rule_get_command.py +28 -0
  50. enapter-0.25.0/src/enapter/cli/http/api/rule_list_command.py +40 -0
  51. enapter-0.25.0/src/enapter/cli/http/api/rule_update_command.py +29 -0
  52. enapter-0.25.0/src/enapter/cli/http/api/rule_update_script_command.py +55 -0
  53. enapter-0.25.0/src/enapter/cli/http/api/site_command.py +43 -0
  54. enapter-0.25.0/src/enapter/cli/http/api/site_create_command.py +43 -0
  55. enapter-0.25.0/src/enapter/cli/http/api/site_delete_command.py +18 -0
  56. enapter-0.25.0/src/enapter/cli/http/api/site_get_command.py +22 -0
  57. enapter-0.25.0/src/enapter/cli/http/api/site_list_command.py +34 -0
  58. enapter-0.25.0/src/enapter/cli/http/api/site_location.py +11 -0
  59. enapter-0.25.0/src/enapter/cli/http/api/site_update_command.py +45 -0
  60. enapter-0.25.0/src/enapter/cli/http/api/telemetry_command.py +31 -0
  61. enapter-0.25.0/src/enapter/cli/http/api/telemetry_latest_command.py +36 -0
  62. enapter-0.25.0/src/enapter/cli/http/api/telemetry_timeseries_command.py +130 -0
  63. enapter-0.25.0/src/enapter/cli/http/command.py +27 -0
  64. enapter-0.25.0/src/enapter/cli/subparsers.py +8 -0
  65. enapter-0.25.0/src/enapter/cli/version_command.py +14 -0
  66. enapter-0.25.0/src/enapter/http/__init__.py +3 -0
  67. enapter-0.25.0/src/enapter/http/api/__init__.py +24 -0
  68. enapter-0.25.0/src/enapter/http/api/authentication.py +19 -0
  69. enapter-0.25.0/src/enapter/http/api/authorization.py +13 -0
  70. enapter-0.25.0/src/enapter/http/api/blueprints/__init__.py +8 -0
  71. enapter-0.25.0/src/enapter/http/api/blueprints/blueprint.py +30 -0
  72. enapter-0.25.0/src/enapter/http/api/blueprints/client.py +76 -0
  73. enapter-0.25.0/src/enapter/http/api/client.py +79 -0
  74. enapter-0.25.0/src/enapter/http/api/commands/__init__.py +14 -0
  75. enapter-0.25.0/src/enapter/http/api/commands/client.py +129 -0
  76. enapter-0.25.0/src/enapter/http/api/commands/execution.py +68 -0
  77. enapter-0.25.0/src/enapter/http/api/commands/request.py +25 -0
  78. enapter-0.25.0/src/enapter/http/api/commands/response.py +35 -0
  79. enapter-0.25.0/src/enapter/http/api/config.py +27 -0
  80. enapter-0.25.0/src/enapter/http/api/devices/__init__.py +25 -0
  81. enapter-0.25.0/src/enapter/http/api/devices/client.py +188 -0
  82. enapter-0.25.0/src/enapter/http/api/devices/communication_config.py +58 -0
  83. enapter-0.25.0/src/enapter/http/api/devices/device.py +74 -0
  84. enapter-0.25.0/src/enapter/http/api/devices/device_communication.py +39 -0
  85. enapter-0.25.0/src/enapter/http/api/devices/device_connectivity.py +22 -0
  86. enapter-0.25.0/src/enapter/http/api/devices/device_type.py +15 -0
  87. enapter-0.25.0/src/enapter/http/api/devices/mqtt_credentials.py +16 -0
  88. enapter-0.25.0/src/enapter/http/api/devices/mqtt_protocol.py +7 -0
  89. enapter-0.25.0/src/enapter/http/api/devices/mqtts_credentials.py +25 -0
  90. enapter-0.25.0/src/enapter/http/api/devices/time_sync_protocol.py +7 -0
  91. enapter-0.25.0/src/enapter/http/api/errors.py +50 -0
  92. enapter-0.25.0/src/enapter/http/api/pagination.py +54 -0
  93. enapter-0.25.0/src/enapter/http/api/rule_engine/__init__.py +19 -0
  94. enapter-0.25.0/src/enapter/http/api/rule_engine/client.py +195 -0
  95. enapter-0.25.0/src/enapter/http/api/rule_engine/engine.py +32 -0
  96. enapter-0.25.0/src/enapter/http/api/rule_engine/engine_state.py +10 -0
  97. enapter-0.25.0/src/enapter/http/api/rule_engine/rule.py +39 -0
  98. enapter-0.25.0/src/enapter/http/api/rule_engine/rule_script.py +37 -0
  99. enapter-0.25.0/src/enapter/http/api/rule_engine/rule_state.py +10 -0
  100. enapter-0.25.0/src/enapter/http/api/rule_engine/runtime_version.py +10 -0
  101. enapter-0.25.0/src/enapter/http/api/sites/__init__.py +9 -0
  102. enapter-0.25.0/src/enapter/http/api/sites/client.py +76 -0
  103. enapter-0.25.0/src/enapter/http/api/sites/location.py +25 -0
  104. enapter-0.25.0/src/enapter/http/api/sites/site.py +42 -0
  105. enapter-0.25.0/src/enapter/http/api/telemetry/__init__.py +33 -0
  106. enapter-0.25.0/src/enapter/http/api/telemetry/aggregation.py +11 -0
  107. enapter-0.25.0/src/enapter/http/api/telemetry/client.py +105 -0
  108. enapter-0.25.0/src/enapter/http/api/telemetry/data_type.py +31 -0
  109. enapter-0.25.0/src/enapter/http/api/telemetry/gap_filling.py +18 -0
  110. enapter-0.25.0/src/enapter/http/api/telemetry/gap_filling_method.py +7 -0
  111. enapter-0.25.0/src/enapter/http/api/telemetry/labels.py +17 -0
  112. enapter-0.25.0/src/enapter/http/api/telemetry/latest_datapoint.py +25 -0
  113. enapter-0.25.0/src/enapter/http/api/telemetry/long_timeseries_row.py +18 -0
  114. enapter-0.25.0/src/enapter/http/api/telemetry/long_timeseries_stream.py +34 -0
  115. enapter-0.25.0/src/enapter/http/api/telemetry/raw_timeseries_row.py +10 -0
  116. enapter-0.25.0/src/enapter/http/api/telemetry/raw_timeseries_stream.py +56 -0
  117. enapter-0.25.0/src/enapter/http/api/telemetry/selector.py +34 -0
  118. enapter-0.25.0/src/enapter/http/api/telemetry/wide_timeseries.py +33 -0
  119. enapter-0.25.0/src/enapter/http/api/telemetry/wide_timeseries_column.py +23 -0
  120. enapter-0.25.0/src/enapter/http/api/transport.py +5 -0
  121. enapter-0.25.0/src/enapter/log/__init__.py +34 -0
  122. enapter-0.25.0/src/enapter/log/json_formatter.py +36 -0
  123. enapter-0.25.0/src/enapter/mdns/__init__.py +3 -0
  124. enapter-0.25.0/src/enapter/mdns/resolver.py +43 -0
  125. enapter-0.25.0/src/enapter/mqtt/__init__.py +7 -0
  126. enapter-0.25.0/src/enapter/mqtt/api/__init__.py +6 -0
  127. enapter-0.25.0/src/enapter/mqtt/api/client.py +64 -0
  128. enapter-0.25.0/src/enapter/mqtt/api/config.py +67 -0
  129. enapter-0.25.0/src/enapter/mqtt/api/device/__init__.py +21 -0
  130. enapter-0.25.0/src/enapter/mqtt/api/device/channel.py +55 -0
  131. enapter-0.25.0/src/enapter/mqtt/api/device/command_request.py +38 -0
  132. enapter-0.25.0/src/enapter/mqtt/api/device/command_response.py +28 -0
  133. enapter-0.25.0/src/enapter/mqtt/api/device/command_state.py +8 -0
  134. enapter-0.25.0/src/enapter/mqtt/api/device/log.py +31 -0
  135. enapter-0.25.0/src/enapter/mqtt/api/device/log_severity.py +9 -0
  136. enapter-0.25.0/src/enapter/mqtt/api/device/message.py +24 -0
  137. enapter-0.25.0/src/enapter/mqtt/api/device/properties.py +24 -0
  138. enapter-0.25.0/src/enapter/mqtt/api/device/telemetry.py +28 -0
  139. enapter-0.25.0/src/enapter/mqtt/client.py +127 -0
  140. enapter-0.25.0/src/enapter/mqtt/errors.py +3 -0
  141. enapter-0.25.0/src/enapter/mqtt/message.py +3 -0
  142. enapter-0.25.0/src/enapter/py.typed +0 -0
  143. enapter-0.25.0/src/enapter/standalone/__init__.py +35 -0
  144. enapter-0.25.0/src/enapter/standalone/config.py +218 -0
  145. enapter-0.25.0/src/enapter/standalone/device.py +59 -0
  146. enapter-0.25.0/src/enapter/standalone/device_protocol.py +33 -0
  147. enapter-0.25.0/src/enapter/standalone/logger.py +21 -0
  148. enapter-0.25.0/src/enapter/standalone/mqtt_adapter.py +142 -0
  149. enapter-0.25.0/src/enapter/standalone/run.py +39 -0
  150. enapter-0.25.0/src/enapter/standalone/ucm.py +28 -0
  151. enapter-0.25.0/src/enapter.egg-info/PKG-INFO +70 -0
  152. enapter-0.25.0/src/enapter.egg-info/SOURCES.txt +153 -0
  153. enapter-0.25.0/src/enapter.egg-info/dependency_links.txt +1 -0
  154. enapter-0.25.0/src/enapter.egg-info/requires.txt +4 -0
  155. enapter-0.25.0/src/enapter.egg-info/top_level.txt +1 -0
enapter-0.25.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022-2026 Enapter
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,70 @@
1
+ Metadata-Version: 2.4
2
+ Name: enapter
3
+ Version: 0.25.0
4
+ Summary: Enapter Python SDK
5
+ Author-email: Roman Novatorov <rnovatorov@enapter.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Enapter/python-sdk
8
+ Requires-Python: >=3.11
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: aiomqtt==2.*
12
+ Requires-Dist: dnspython==2.*
13
+ Requires-Dist: json-log-formatter==1.*
14
+ Requires-Dist: httpx==0.28.*
15
+ Dynamic: license-file
16
+
17
+ # Enapter Python SDK
18
+
19
+ [![CI](https://github.com/Enapter/python-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/Enapter/python-sdk/actions/workflows/ci.yml)
20
+ [![PyPI version](https://img.shields.io/pypi/v/enapter.svg)](https://pypi.org/project/enapter)
21
+ [![Code Style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/python/black)
22
+
23
+ A software development kit (SDK) for building applications and integrations
24
+ with Enapter using Python.
25
+
26
+ ## Features
27
+
28
+ - [Standalone
29
+ Devices](https://v3.developers.enapter.com/docs/standalone/introduction)
30
+ framework
31
+ - [MQTT
32
+ API](https://v3.developers.enapter.com/reference/device_integration/mqtt_api/)
33
+ client
34
+ - [HTTP API](https://v3.developers.enapter.com/reference/http/intro) client
35
+
36
+ ## Installation
37
+
38
+ > [!IMPORTANT]
39
+ > Requires **Python 3.11+**.
40
+
41
+ > [!WARNING]
42
+ > The API is still under development and may change at any time. It is
43
+ > recommended to pin the package version when installing.
44
+
45
+ Install from PyPI:
46
+
47
+ ```bash
48
+ pip install enapter==0.25.0
49
+ ```
50
+
51
+ ## Usage
52
+
53
+ Explore the examples:
54
+
55
+ - [Standalone Devices](examples/standalone)
56
+ - [MQTT API](examples/mqtt/api)
57
+ - [HTTP API](examples/http/api)
58
+
59
+ These provide a good overview of the available features and should give you
60
+ enough to get started.
61
+
62
+ > [!TIP]
63
+ > Don't hesitate to peek into the source code - it's meant to be easy to
64
+ > follow.
65
+
66
+ ## Help
67
+
68
+ If you feel lost or confused, reach us on
69
+ [Discord](https://discord.com/invite/TCaEZs3qpe) or just [file a
70
+ bug](https://github.com/Enapter/python-sdk/issues/new). We'd be glad to help.
@@ -0,0 +1,54 @@
1
+ # Enapter Python SDK
2
+
3
+ [![CI](https://github.com/Enapter/python-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/Enapter/python-sdk/actions/workflows/ci.yml)
4
+ [![PyPI version](https://img.shields.io/pypi/v/enapter.svg)](https://pypi.org/project/enapter)
5
+ [![Code Style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/python/black)
6
+
7
+ A software development kit (SDK) for building applications and integrations
8
+ with Enapter using Python.
9
+
10
+ ## Features
11
+
12
+ - [Standalone
13
+ Devices](https://v3.developers.enapter.com/docs/standalone/introduction)
14
+ framework
15
+ - [MQTT
16
+ API](https://v3.developers.enapter.com/reference/device_integration/mqtt_api/)
17
+ client
18
+ - [HTTP API](https://v3.developers.enapter.com/reference/http/intro) client
19
+
20
+ ## Installation
21
+
22
+ > [!IMPORTANT]
23
+ > Requires **Python 3.11+**.
24
+
25
+ > [!WARNING]
26
+ > The API is still under development and may change at any time. It is
27
+ > recommended to pin the package version when installing.
28
+
29
+ Install from PyPI:
30
+
31
+ ```bash
32
+ pip install enapter==0.25.0
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ Explore the examples:
38
+
39
+ - [Standalone Devices](examples/standalone)
40
+ - [MQTT API](examples/mqtt/api)
41
+ - [HTTP API](examples/http/api)
42
+
43
+ These provide a good overview of the available features and should give you
44
+ enough to get started.
45
+
46
+ > [!TIP]
47
+ > Don't hesitate to peek into the source code - it's meant to be easy to
48
+ > follow.
49
+
50
+ ## Help
51
+
52
+ If you feel lost or confused, reach us on
53
+ [Discord](https://discord.com/invite/TCaEZs3qpe) or just [file a
54
+ bug](https://github.com/Enapter/python-sdk/issues/new). We'd be glad to help.
@@ -0,0 +1,53 @@
1
+ [project]
2
+ name = "enapter"
3
+ description = "Enapter Python SDK"
4
+ readme = "README.md"
5
+ license = "MIT"
6
+ license-files = ["LICENSE"]
7
+ requires-python = ">=3.11"
8
+ authors = [
9
+ { name = "Roman Novatorov", email = "rnovatorov@enapter.com" },
10
+ ]
11
+ dependencies = [
12
+ "aiomqtt==2.*",
13
+ "dnspython==2.*",
14
+ "json-log-formatter==1.*",
15
+ "httpx==0.28.*",
16
+ ]
17
+ dynamic = ["version"]
18
+
19
+ [project.urls]
20
+ Homepage = "https://github.com/Enapter/python-sdk"
21
+
22
+ [dependency-groups]
23
+ dev = [
24
+ "black",
25
+ "docker",
26
+ "isort",
27
+ "mypy",
28
+ "pyflakes",
29
+ "pytest",
30
+ "pytest-asyncio",
31
+ "pytest-cov",
32
+ "types-docker",
33
+ ]
34
+
35
+ [build-system]
36
+ requires = ["setuptools>=77"]
37
+ build-backend = "setuptools.build_meta"
38
+
39
+ [tool.setuptools]
40
+ package-dir = {"" = "src"}
41
+
42
+ [tool.setuptools.packages.find]
43
+ where = ["src"]
44
+
45
+ [tool.setuptools.dynamic]
46
+ version = { attr = "enapter.__version__" }
47
+
48
+ [tool.isort]
49
+ profile = "black"
50
+ known_first_party = ["enapter"]
51
+
52
+ [tool.pytest.ini_options]
53
+ asyncio_mode = "auto"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,13 @@
1
+ __version__ = "0.25.0"
2
+
3
+ from . import async_, log, mdns, mqtt, http, standalone # isort: skip
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "async_",
8
+ "log",
9
+ "mdns",
10
+ "mqtt",
11
+ "http",
12
+ "standalone",
13
+ ]
@@ -0,0 +1,9 @@
1
+ import asyncio
2
+
3
+ from enapter import cli
4
+
5
+ app = cli.App.new()
6
+ try:
7
+ asyncio.run(app.run())
8
+ except KeyboardInterrupt:
9
+ pass
@@ -0,0 +1,4 @@
1
+ from .generator import generator
2
+ from .routine import Routine
3
+
4
+ __all__ = ["generator", "Routine"]
@@ -0,0 +1,18 @@
1
+ import contextlib
2
+ import functools
3
+ from typing import AsyncContextManager, AsyncGenerator, Callable
4
+
5
+
6
+ def generator(
7
+ func: Callable[..., AsyncGenerator],
8
+ ) -> Callable[..., AsyncContextManager[AsyncGenerator]]:
9
+ @functools.wraps(func)
10
+ @contextlib.asynccontextmanager
11
+ async def wrapper(*args, **kwargs) -> AsyncGenerator[AsyncGenerator, None]:
12
+ gen = func(*args, **kwargs)
13
+ try:
14
+ yield gen
15
+ finally:
16
+ await gen.aclose()
17
+
18
+ return wrapper
@@ -0,0 +1,37 @@
1
+ import abc
2
+ import asyncio
3
+ import contextlib
4
+ from typing import Self
5
+
6
+
7
+ class Routine(abc.ABC):
8
+
9
+ def __init__(self, task_group: asyncio.TaskGroup | None = None) -> None:
10
+ self._task_group = task_group
11
+ self._task: asyncio.Task | None = None
12
+
13
+ @abc.abstractmethod
14
+ async def _run(self) -> None:
15
+ pass # pragma: no cover
16
+
17
+ async def __aenter__(self) -> Self:
18
+ await self.start()
19
+ return self
20
+
21
+ async def __aexit__(self, *_) -> None:
22
+ await self.stop()
23
+
24
+ async def start(self) -> None:
25
+ if self._task is not None:
26
+ raise RuntimeError("already started")
27
+ if self._task_group is None:
28
+ self._task = asyncio.create_task(self._run())
29
+ else:
30
+ self._task = self._task_group.create_task(self._run())
31
+
32
+ async def stop(self) -> None:
33
+ if self._task is None:
34
+ raise RuntimeError("not started yet")
35
+ self._task.cancel()
36
+ with contextlib.suppress(asyncio.CancelledError):
37
+ await self._task
@@ -0,0 +1,6 @@
1
+ from .command import Command
2
+ from .subparsers import Subparsers
3
+
4
+ from .app import App # isort: skip
5
+
6
+ __all__ = ["App", "Command", "Subparsers"]
@@ -0,0 +1,37 @@
1
+ import argparse
2
+ import logging
3
+
4
+ from enapter import log
5
+
6
+ from . import http
7
+ from .version_command import VersionCommand
8
+
9
+
10
+ class App:
11
+
12
+ def __init__(self, args: argparse.Namespace) -> None:
13
+ self.args = args
14
+
15
+ @classmethod
16
+ def new(cls) -> "App":
17
+ parser = argparse.ArgumentParser(
18
+ prog="enapter", formatter_class=argparse.ArgumentDefaultsHelpFormatter
19
+ )
20
+ parser.add_argument("-v", "--verbose", action="store_true")
21
+ subparsers = parser.add_subparsers(dest="command", required=True)
22
+ for command in [
23
+ http.Command,
24
+ VersionCommand,
25
+ ]:
26
+ command.register(subparsers)
27
+ return cls(args=parser.parse_args())
28
+
29
+ async def run(self) -> None:
30
+ log.configure(level=logging.DEBUG if self.args.verbose else logging.INFO)
31
+ match self.args.command:
32
+ case "http":
33
+ await http.Command.run(self.args)
34
+ case "version":
35
+ await VersionCommand.run(self.args)
36
+ case _:
37
+ raise NotImplementedError(self.args.command)
@@ -0,0 +1,16 @@
1
+ import abc
2
+ import argparse
3
+
4
+ from .subparsers import Subparsers
5
+
6
+
7
+ class Command(abc.ABC):
8
+
9
+ @staticmethod
10
+ def register(subparsers: Subparsers) -> None:
11
+ pass
12
+
13
+ @staticmethod
14
+ @abc.abstractmethod
15
+ async def run(args: argparse.Namespace) -> None:
16
+ pass
@@ -0,0 +1,3 @@
1
+ from .command import Command
2
+
3
+ __all__ = ["Command"]
@@ -0,0 +1,3 @@
1
+ from .command import Command
2
+
3
+ __all__ = ["Command"]
@@ -0,0 +1,43 @@
1
+ import argparse
2
+
3
+ from enapter import cli
4
+
5
+ from .blueprint_download_command import BlueprintDownloadCommand
6
+ from .blueprint_get_command import BlueprintGetCommand
7
+ from .blueprint_profiles_command import BlueprintProfilesCommand
8
+ from .blueprint_upload_command import BlueprintUploadCommand
9
+ from .blueprint_validate_command import BlueprintValidateCommand
10
+
11
+
12
+ class BlueprintCommand(cli.Command):
13
+
14
+ @staticmethod
15
+ def register(parent: cli.Subparsers) -> None:
16
+ parser = parent.add_parser(
17
+ "blueprint", formatter_class=argparse.ArgumentDefaultsHelpFormatter
18
+ )
19
+ subparsers = parser.add_subparsers(dest="blueprint_command", required=True)
20
+ for command in [
21
+ BlueprintDownloadCommand,
22
+ BlueprintGetCommand,
23
+ BlueprintProfilesCommand,
24
+ BlueprintUploadCommand,
25
+ BlueprintValidateCommand,
26
+ ]:
27
+ command.register(subparsers)
28
+
29
+ @staticmethod
30
+ async def run(args: argparse.Namespace) -> None:
31
+ match args.blueprint_command:
32
+ case "download":
33
+ await BlueprintDownloadCommand.run(args)
34
+ case "get":
35
+ await BlueprintGetCommand.run(args)
36
+ case "profiles":
37
+ await BlueprintProfilesCommand.run(args)
38
+ case "upload":
39
+ await BlueprintUploadCommand.run(args)
40
+ case "validate":
41
+ await BlueprintValidateCommand.run(args)
42
+ case _:
43
+ raise NotImplementedError(args.blueprint_command)
@@ -0,0 +1,41 @@
1
+ import argparse
2
+ import logging
3
+ import pathlib
4
+
5
+ from enapter import cli, http
6
+
7
+ LOGGER = logging.getLogger(__name__)
8
+
9
+
10
+ class BlueprintDownloadCommand(cli.Command):
11
+
12
+ @staticmethod
13
+ def register(parent: cli.Subparsers) -> None:
14
+ parser = parent.add_parser(
15
+ "download", formatter_class=argparse.ArgumentDefaultsHelpFormatter
16
+ )
17
+ parser.add_argument("id", help="ID of the blueprint to download")
18
+ parser.add_argument(
19
+ "-o",
20
+ "--output",
21
+ type=pathlib.Path,
22
+ help="Output directory path",
23
+ default=pathlib.Path.cwd(),
24
+ )
25
+ parser.add_argument(
26
+ "-v",
27
+ "--view",
28
+ choices=["original", "compiled"],
29
+ default="original",
30
+ help="Blueprint view type",
31
+ )
32
+
33
+ @staticmethod
34
+ async def run(args: argparse.Namespace) -> None:
35
+ async with http.api.Client(http.api.Config.from_env()) as client:
36
+ content = await client.blueprints.download(
37
+ args.id, view=http.api.blueprints.BlueprintView(args.view.upper())
38
+ )
39
+ output_path = args.output / f"{args.id}.zip"
40
+ with output_path.open("wb") as f:
41
+ f.write(content)
@@ -0,0 +1,23 @@
1
+ import argparse
2
+ import json
3
+ import logging
4
+
5
+ from enapter import cli, http
6
+
7
+ LOGGER = logging.getLogger(__name__)
8
+
9
+
10
+ class BlueprintGetCommand(cli.Command):
11
+
12
+ @staticmethod
13
+ def register(parent: cli.Subparsers) -> None:
14
+ parser = parent.add_parser(
15
+ "get", formatter_class=argparse.ArgumentDefaultsHelpFormatter
16
+ )
17
+ parser.add_argument("id", help="ID of the blueprint to get")
18
+
19
+ @staticmethod
20
+ async def run(args: argparse.Namespace) -> None:
21
+ async with http.api.Client(http.api.Config.from_env()) as client:
22
+ blueprint = await client.blueprints.get(blueprint_id=args.id)
23
+ print(json.dumps(blueprint.to_dto()))
@@ -0,0 +1,29 @@
1
+ import argparse
2
+
3
+ from enapter import cli
4
+
5
+ from .blueprint_profiles_download_command import BlueprintProfilesDownloadCommand
6
+
7
+
8
+ class BlueprintProfilesCommand(cli.Command):
9
+
10
+ @staticmethod
11
+ def register(parent: cli.Subparsers) -> None:
12
+ parser = parent.add_parser(
13
+ "profiles", formatter_class=argparse.ArgumentDefaultsHelpFormatter
14
+ )
15
+ subparsers = parser.add_subparsers(
16
+ dest="blueprint_profiles_command", required=True
17
+ )
18
+ for command in [
19
+ BlueprintProfilesDownloadCommand,
20
+ ]:
21
+ command.register(subparsers)
22
+
23
+ @staticmethod
24
+ async def run(args: argparse.Namespace) -> None:
25
+ match args.blueprint_profiles_command:
26
+ case "download":
27
+ await BlueprintProfilesDownloadCommand.run(args)
28
+ case _:
29
+ raise NotImplementedError(args.blueprint_profiles_command)
@@ -0,0 +1,31 @@
1
+ import argparse
2
+ import logging
3
+ import pathlib
4
+
5
+ from enapter import cli, http
6
+
7
+ LOGGER = logging.getLogger(__name__)
8
+
9
+
10
+ class BlueprintProfilesDownloadCommand(cli.Command):
11
+
12
+ @staticmethod
13
+ def register(parent: cli.Subparsers) -> None:
14
+ parser = parent.add_parser(
15
+ "download", formatter_class=argparse.ArgumentDefaultsHelpFormatter
16
+ )
17
+ parser.add_argument(
18
+ "-o",
19
+ "--output",
20
+ type=pathlib.Path,
21
+ help="Output directory path",
22
+ default=pathlib.Path.cwd(),
23
+ )
24
+
25
+ @staticmethod
26
+ async def run(args: argparse.Namespace) -> None:
27
+ async with http.api.Client(http.api.Config.from_env()) as client:
28
+ content = await client.blueprints.download_device_profiles()
29
+ output_path = args.output / "device_profiles.zip"
30
+ with output_path.open("wb") as f:
31
+ f.write(content)
@@ -0,0 +1,29 @@
1
+ import argparse
2
+ import json
3
+ import logging
4
+ import pathlib
5
+
6
+ from enapter import cli, http
7
+
8
+ LOGGER = logging.getLogger(__name__)
9
+
10
+
11
+ class BlueprintUploadCommand(cli.Command):
12
+
13
+ @staticmethod
14
+ def register(parent: cli.Subparsers) -> None:
15
+ parser = parent.add_parser(
16
+ "upload", formatter_class=argparse.ArgumentDefaultsHelpFormatter
17
+ )
18
+ parser.add_argument(
19
+ "path", type=pathlib.Path, help="Path to a directory or a zip file"
20
+ )
21
+
22
+ @staticmethod
23
+ async def run(args: argparse.Namespace) -> None:
24
+ async with http.api.Client(http.api.Config.from_env()) as client:
25
+ if args.path.is_dir():
26
+ blueprint = await client.blueprints.upload_directory(args.path)
27
+ else:
28
+ blueprint = await client.blueprints.upload_file(args.path)
29
+ print(json.dumps(blueprint.to_dto()))
@@ -0,0 +1,27 @@
1
+ import argparse
2
+ import logging
3
+ import pathlib
4
+
5
+ from enapter import cli, http
6
+
7
+ LOGGER = logging.getLogger(__name__)
8
+
9
+
10
+ class BlueprintValidateCommand(cli.Command):
11
+
12
+ @staticmethod
13
+ def register(parent: cli.Subparsers) -> None:
14
+ parser = parent.add_parser(
15
+ "validate", formatter_class=argparse.ArgumentDefaultsHelpFormatter
16
+ )
17
+ parser.add_argument(
18
+ "path", type=pathlib.Path, help="Path to a directory or a zip file"
19
+ )
20
+
21
+ @staticmethod
22
+ async def run(args: argparse.Namespace) -> None:
23
+ async with http.api.Client(http.api.Config.from_env()) as client:
24
+ if args.path.is_dir():
25
+ await client.blueprints.validate_directory(args.path)
26
+ else:
27
+ await client.blueprints.validate_file(args.path)
@@ -0,0 +1,47 @@
1
+ import argparse
2
+
3
+ from enapter import cli
4
+
5
+ from .blueprint_command import BlueprintCommand
6
+ from .command_command import CommandCommand
7
+ from .device_command import DeviceCommand
8
+ from .rule_engine_command import RuleEngineCommand
9
+ from .site_command import SiteCommand
10
+ from .telemetry_command import TelemetryCommand
11
+
12
+
13
+ class Command(cli.Command):
14
+
15
+ @staticmethod
16
+ def register(parent: cli.Subparsers) -> None:
17
+ parser = parent.add_parser(
18
+ "api", formatter_class=argparse.ArgumentDefaultsHelpFormatter
19
+ )
20
+ subparsers = parser.add_subparsers(dest="api_command", required=True)
21
+ for command in [
22
+ BlueprintCommand,
23
+ CommandCommand,
24
+ DeviceCommand,
25
+ RuleEngineCommand,
26
+ SiteCommand,
27
+ TelemetryCommand,
28
+ ]:
29
+ command.register(subparsers)
30
+
31
+ @staticmethod
32
+ async def run(args: argparse.Namespace) -> None:
33
+ match args.api_command:
34
+ case "blueprint":
35
+ await BlueprintCommand.run(args)
36
+ case "command":
37
+ await CommandCommand.run(args)
38
+ case "device":
39
+ await DeviceCommand.run(args)
40
+ case "rule-engine":
41
+ await RuleEngineCommand.run(args)
42
+ case "site":
43
+ await SiteCommand.run(args)
44
+ case "telemetry":
45
+ await TelemetryCommand.run(args)
46
+ case _:
47
+ raise NotImplementedError(args.api_command)
@@ -0,0 +1,11 @@
1
+ import argparse
2
+ import json
3
+
4
+
5
+ def parse_command_arguments(arguments_string: str | None) -> dict:
6
+ if arguments_string is None:
7
+ return {}
8
+ try:
9
+ return json.loads(arguments_string)
10
+ except json.JSONDecodeError as e:
11
+ raise argparse.ArgumentTypeError(f"Decode JSON: {e.msg}")