intersect-sdk-common 0.0.0a0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. intersect_sdk_common/__init__.py +72 -0
  2. intersect_sdk_common/config.py +168 -0
  3. intersect_sdk_common/constants.py +28 -0
  4. intersect_sdk_common/control_plane/__init__.py +1 -0
  5. intersect_sdk_common/control_plane/brokers/__init__.py +4 -0
  6. intersect_sdk_common/control_plane/brokers/amqp_client.py +620 -0
  7. intersect_sdk_common/control_plane/brokers/broker_client.py +81 -0
  8. intersect_sdk_common/control_plane/brokers/mqtt_client.py +300 -0
  9. intersect_sdk_common/control_plane/control_plane_manager.py +172 -0
  10. intersect_sdk_common/control_plane/definitions.py +12 -0
  11. intersect_sdk_common/control_plane/messages/__init__.py +6 -0
  12. intersect_sdk_common/control_plane/messages/event.py +145 -0
  13. intersect_sdk_common/control_plane/messages/lifecycle.py +112 -0
  14. intersect_sdk_common/control_plane/messages/userspace.py +183 -0
  15. intersect_sdk_common/control_plane/topic_handler.py +31 -0
  16. intersect_sdk_common/core_definitions.py +47 -0
  17. intersect_sdk_common/data_plane/__init__.py +1 -0
  18. intersect_sdk_common/data_plane/data_plane_manager.py +109 -0
  19. intersect_sdk_common/data_plane/minio_utils.py +169 -0
  20. intersect_sdk_common/exceptions.py +19 -0
  21. intersect_sdk_common/logger.py +5 -0
  22. intersect_sdk_common/py.typed +0 -0
  23. intersect_sdk_common/utils/__init__.py +1 -0
  24. intersect_sdk_common/utils/multi_flag_thread_event.py +82 -0
  25. intersect_sdk_common/version.py +38 -0
  26. intersect_sdk_common-0.0.0a0.dist-info/METADATA +52 -0
  27. intersect_sdk_common-0.0.0a0.dist-info/RECORD +28 -0
  28. intersect_sdk_common-0.0.0a0.dist-info/WHEEL +4 -0
@@ -0,0 +1,72 @@
1
+ """Common definitions shared across ALL INTERSECT services, be they domain science microservices or INTERSECT core services.
2
+
3
+ You can use the base package as the public API, no need to import from submodules.
4
+
5
+ IMPORTANT: If you are building a scientific microservice, do NOT import from this package. Instead, import from intersect-sdk, which is the public-facing library intended for you. Relevant definitions from this package will be re-exported there.
6
+ """
7
+
8
+ from importlib import import_module
9
+ from typing import TYPE_CHECKING
10
+
11
+ # import everything eagerly for IDEs/LSPs
12
+ if TYPE_CHECKING:
13
+ from .config import (
14
+ ControlPlaneConfig,
15
+ ControlProvider,
16
+ DataStoreConfig,
17
+ DataStoreConfigMap,
18
+ HierarchyConfig,
19
+ )
20
+ from .control_plane.control_plane_manager import ControlPlaneManager
21
+ from .control_plane.definitions import MessageCallback
22
+ from .core_definitions import IntersectDataHandler, IntersectMimeType
23
+ from .data_plane.data_plane_manager import DataPlaneManager
24
+ from .version import __version__, intersect_sdk_version_info, intersect_sdk_version_string
25
+
26
+ __all__ = (
27
+ 'ControlPlaneConfig',
28
+ 'ControlPlaneManager',
29
+ 'ControlProvider',
30
+ 'DataPlaneManager',
31
+ 'DataStoreConfig',
32
+ 'DataStoreConfigMap',
33
+ 'HierarchyConfig',
34
+ 'IntersectDataHandler',
35
+ 'IntersectMimeType',
36
+ 'MessageCallback',
37
+ '__version__',
38
+ 'intersect_sdk_version_info',
39
+ 'intersect_sdk_version_string',
40
+ )
41
+
42
+ # PEP 562 stuff: do lazy imports for people who just want to import from the top-level module
43
+
44
+ __lazy_imports = {
45
+ 'ControlPlaneConfig': '.config',
46
+ 'ControlProvider': '.config',
47
+ 'DataStoreConfig': '.config',
48
+ 'DataStoreConfigMap': '.config',
49
+ 'HierarchyConfig': '.config',
50
+ 'ControlPlaneManager': '.control_plane.control_plane_manager',
51
+ 'MessageCallback': '.control_plane.definitions',
52
+ 'IntersectDataHandler': '.core_definitions',
53
+ 'IntersectMimeType': '.core_definitions',
54
+ 'DataPlaneManager': '.data_plane.data_plane_manager',
55
+ '__version__': '.version',
56
+ 'intersect_sdk_version_info': '.version',
57
+ 'intersect_sdk_version_string': '.version',
58
+ }
59
+
60
+
61
+ def __getattr__(attr_name: str) -> object:
62
+ attr_module = __lazy_imports.get(attr_name)
63
+ if attr_module:
64
+ module = import_module(attr_module, package=__spec__.parent)
65
+ return getattr(module, attr_name)
66
+
67
+ msg = f'module {__name__!r} has no attribute {attr_name!r}'
68
+ raise AttributeError(msg)
69
+
70
+
71
+ def __dir__() -> list[str]:
72
+ return list(__all__)
@@ -0,0 +1,168 @@
1
+ """Configuration types shared across both Clients and Services."""
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Annotated, Literal
5
+
6
+ from pydantic import BaseModel, ConfigDict, Field, PositiveInt
7
+
8
+ from .core_definitions import IntersectDataHandler
9
+
10
+ HIERARCHY_REGEX = r'^[a-z]((?!--)[a-z0-9-]){2,62}$'
11
+ """
12
+ The hierarchy regex needs to be fairly restricted due to the number of different
13
+ systems we want to be compatible with. The rules:
14
+
15
+ - Only allow unreserved characters (alphanumeric and .-~_): https://datatracker.ietf.org/doc/html/rfc3986#section-2.3
16
+ - Require lowercase letters to avoid incompatibilities with case-insensitive systems.
17
+ - MinIO has been found to forbid _ and ~ characters
18
+ - MinIO requires an alphanumeric character at the start of the string
19
+ - No adjacent non-alphanumeric characters allowed
20
+ - Range should be from 3-63 characters
21
+
22
+ The following commit tracks several issues with MINIO: https://code.ornl.gov/intersect/additive-manufacturing/ros-intersect-adapter/-/commit/fa71b791be0ccf1a5884910b5be3b5239cf9896f
23
+ """
24
+
25
+ ControlProvider = Literal['mqtt5.0', 'amqp0.9.1']
26
+ """The type of broker we connect to."""
27
+
28
+
29
+ class HierarchyConfig(BaseModel):
30
+ """Configuration for registering this service in a system-of-system architecture."""
31
+
32
+ service: Annotated[str, Field(pattern=HIERARCHY_REGEX)]
33
+ """
34
+ The name of this application - should be unique within an INTERSECT system
35
+ """
36
+
37
+ subsystem: str | None = Field(default=None, pattern=HIERARCHY_REGEX)
38
+ """
39
+ An associated subsystem / service-grouping of the system (should be unique within an INTERSECT system)
40
+ """
41
+
42
+ system: Annotated[str, Field(pattern=HIERARCHY_REGEX)]
43
+ """
44
+ Name of the "system", could also be thought of as a "device" (should be unique within a facility)
45
+ """
46
+
47
+ facility: Annotated[str, Field(pattern=HIERARCHY_REGEX)]
48
+ """
49
+ Name of the facility (an ORNL institutional designation, i.e. 'neutrons') (NOT abbreviated, should be unique within an organization)
50
+ """
51
+
52
+ organization: Annotated[str, Field(pattern=HIERARCHY_REGEX)]
53
+ """
54
+ Name of the organization (i.e. 'ornl') (NOT abbreviated) (should be unique in an INTERSECT cluster)
55
+ """
56
+
57
+ def hierarchy_string(self, join_str: str = '') -> str:
58
+ """Get the full hierarchy string. This is mostly used internally, but if you're developing a client, it could potentially be helpful.
59
+
60
+ Params
61
+ join_str: String used to separate different hierarchy parts in the full string (default: empty string).
62
+
63
+ Returns:
64
+ Single string, which will contain all system-of-system parts. For optional parts not configured (i.e. - no subsystem), they will be represented by a "-" character.
65
+ """
66
+ if not self.subsystem:
67
+ return join_str.join([self.organization, self.facility, self.system, '-', self.service])
68
+ return join_str.join(
69
+ [
70
+ self.organization,
71
+ self.facility,
72
+ self.system,
73
+ self.subsystem,
74
+ self.service,
75
+ ]
76
+ )
77
+
78
+ # we need to use the Python regex engine instead of the Rust regex engine here, because Rust's does not support lookaheads
79
+ model_config = ConfigDict(regex_engine='python-re')
80
+
81
+
82
+ @dataclass
83
+ class ControlPlaneConfig:
84
+ """Configuration for interacting with a broker."""
85
+
86
+ protocol: ControlProvider
87
+ """
88
+ The protocol of the broker you'd like to use (i.e. AMQP, MQTT...)
89
+ """
90
+ # TODO - support more protocols and protocol versions as needed - see https://www.asyncapi.com/docs/reference/specification/v2.6.0#serverObject
91
+
92
+ username: Annotated[str, Field(min_length=1)]
93
+ """
94
+ Username credentials for broker connection.
95
+ """
96
+
97
+ password: Annotated[str, Field(min_length=1)]
98
+ """
99
+ Password credentials for broker connection.
100
+ """
101
+
102
+ host: Annotated[str, Field(min_length=1)] = '127.0.0.1'
103
+ """
104
+ Broker hostname (default: 127.0.0.1)
105
+ """
106
+
107
+ port: PositiveInt | None = None
108
+ """
109
+ Broker port. List of common ports:
110
+
111
+ - 1883 (MQTT)
112
+ - 4222 (NATS default port)
113
+ - 5222 (XMPP)
114
+ - 5223 (XMPP over TLS)
115
+ - 5671 (AMQP over TLS)
116
+ - 5672 (AMQP)
117
+ - 7400 (DDS Discovery)
118
+ - 7401 (DDS User traffic)
119
+ - 8883 (MQTT over TLS)
120
+ - 61613 (RabbitMQ STOMP - WARNING: ephemeral port)
121
+
122
+ NOTE: INTERSECT currently only supports AMQP and MQTT.
123
+ """
124
+
125
+
126
+ @dataclass
127
+ class DataStoreConfig:
128
+ """Configuration for interacting with a data store."""
129
+
130
+ username: Annotated[str, Field(min_length=1)]
131
+ """
132
+ Username credentials for data store connection.
133
+ """
134
+
135
+ password: Annotated[str, Field(min_length=1)]
136
+ """
137
+ Password credentials for data store connection.
138
+ """
139
+
140
+ host: Annotated[str, Field(min_length=1)] = '127.0.0.1'
141
+ """
142
+ Data store hostname (default: 127.0.0.1)
143
+ """
144
+
145
+ port: PositiveInt | None = None
146
+ """
147
+ Data store port
148
+ """
149
+
150
+
151
+ @dataclass
152
+ class DataStoreConfigMap:
153
+ """Configurations for any data stores the application should talk to."""
154
+
155
+ minio: list[DataStoreConfig] = field(default_factory=list)
156
+ """
157
+ minio configurations
158
+ """
159
+
160
+ def get_missing_data_store_types(self) -> set[IntersectDataHandler]:
161
+ """Return a set of IntersectDataHandlers which will not be permitted, due to a configuration type missing.
162
+
163
+ If all data configurations exist, returns an empty set
164
+ """
165
+ missing = set()
166
+ if not self.minio:
167
+ missing.add(IntersectDataHandler.MINIO)
168
+ return missing
@@ -0,0 +1,28 @@
1
+ """These are miscellaneous constants used in INTERSECT which SDK users may obtain value from knowing about."""
2
+
3
+ SYSTEM_OF_SYSTEM_REGEX = r'^[a-z0-9][-a-z0-9.]*[-a-z0-9]$'
4
+ """
5
+ This is the regex used as a representation of a source/destination.
6
+ This is only needed externally if you are building a client, services can ignore this.
7
+
8
+ NOTE: for future compatibility reasons, we are NOT specifying the number of "parts" (separated by a '.') in this regex. All that matters is that you don't start or end with a period, or start with a hyphen.
9
+ """
10
+
11
+ # see the internal schema file for full validation details
12
+ CAPABILITY_REGEX = r'^[a-zA-Z0-9]\w*$'
13
+ """
14
+ This is the regex used for representing capabilities and event keys. Capabilities should start with an alphanumeric character, and not be longer than 255 characters.
15
+
16
+ This regex applies to namespacing local to a Service, so does not have to be unique across the ecosystem.
17
+ """
18
+
19
+ MIME_TYPE_REGEX = r'\w+/[-+.\w]+'
20
+ """
21
+ Regex used for validating Content Types.
22
+
23
+ References can be found at:
24
+
25
+ - https://www.iana.org/assignments/media-types/media-types.xhtml
26
+
27
+ - https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
28
+ """
@@ -0,0 +1 @@
1
+ """Logic associated with the control plane."""
@@ -0,0 +1,4 @@
1
+ """Common logic for interacting with brokers on the protocol level.
2
+
3
+ These classes should be kept internal to the common library.
4
+ """