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.
- intersect_sdk_common/__init__.py +72 -0
- intersect_sdk_common/config.py +168 -0
- intersect_sdk_common/constants.py +28 -0
- intersect_sdk_common/control_plane/__init__.py +1 -0
- intersect_sdk_common/control_plane/brokers/__init__.py +4 -0
- intersect_sdk_common/control_plane/brokers/amqp_client.py +620 -0
- intersect_sdk_common/control_plane/brokers/broker_client.py +81 -0
- intersect_sdk_common/control_plane/brokers/mqtt_client.py +300 -0
- intersect_sdk_common/control_plane/control_plane_manager.py +172 -0
- intersect_sdk_common/control_plane/definitions.py +12 -0
- intersect_sdk_common/control_plane/messages/__init__.py +6 -0
- intersect_sdk_common/control_plane/messages/event.py +145 -0
- intersect_sdk_common/control_plane/messages/lifecycle.py +112 -0
- intersect_sdk_common/control_plane/messages/userspace.py +183 -0
- intersect_sdk_common/control_plane/topic_handler.py +31 -0
- intersect_sdk_common/core_definitions.py +47 -0
- intersect_sdk_common/data_plane/__init__.py +1 -0
- intersect_sdk_common/data_plane/data_plane_manager.py +109 -0
- intersect_sdk_common/data_plane/minio_utils.py +169 -0
- intersect_sdk_common/exceptions.py +19 -0
- intersect_sdk_common/logger.py +5 -0
- intersect_sdk_common/py.typed +0 -0
- intersect_sdk_common/utils/__init__.py +1 -0
- intersect_sdk_common/utils/multi_flag_thread_event.py +82 -0
- intersect_sdk_common/version.py +38 -0
- intersect_sdk_common-0.0.0a0.dist-info/METADATA +52 -0
- intersect_sdk_common-0.0.0a0.dist-info/RECORD +28 -0
- 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."""
|