intersect-sdk 0.8.0__tar.gz → 0.8.1__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.
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/PKG-INFO +7 -3
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/README.md +4 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/pyproject.toml +46 -39
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/brokers/amqp_client.py +1 -1
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/brokers/mqtt_client.py +1 -1
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/data_plane/data_plane_manager.py +1 -1
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/event_metadata.py +1 -1
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/interfaces.py +21 -7
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/messages/userspace.py +3 -3
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/schema.py +1 -1
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/app_lifecycle.py +1 -1
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/capability/base.py +29 -1
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/client.py +5 -5
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/config/shared.py +1 -1
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/schema.py +5 -5
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/service.py +121 -19
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/version.py +1 -1
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/e2e/test_examples.py +7 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/fixtures/example_schema.py +12 -11
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/integration/test_return_type_mismatch.py +0 -1
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/integration/test_service.py +0 -1
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/unit/test_annotations.py +3 -2
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/unit/test_base_capability_implementation.py +1 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/unit/test_config.py +2 -1
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/unit/test_invalid_schema_runtime.py +1 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/unit/test_lifecycle_message.py +2 -1
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/unit/test_schema_invalids.py +8 -7
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/unit/test_schema_valid.py +0 -1
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/unit/test_userspace_message.py +2 -1
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/unit/test_version_resolver.py +1 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/LICENSE +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/__init__.py +17 -17
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/__init__.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/constants.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/__init__.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/brokers/__init__.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/brokers/broker_client.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/control_plane_manager.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/discovery_service.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/topic_handler.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/data_plane/__init__.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/data_plane/minio_utils.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/exceptions.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/function_metadata.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/logger.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/messages/__init__.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/messages/event.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/messages/lifecycle.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/multi_flag_thread_event.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/pydantic_schema_generator.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/stoppable_thread.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/utils.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/version.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/version_resolver.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/capability/__init__.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/client_callback_definitions.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/config/__init__.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/config/client.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/config/service.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/constants.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/core_definitions.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/py.typed +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/service_callback_definitions.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/service_definitions.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/shared_callback_definitions.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/__init__.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/conftest.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/e2e/__init__.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/fixtures/__init__.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/fixtures/example_schema.json +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/fixtures/return_type_mismatch.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/integration/__init__.py +0 -0
- {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/unit/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: intersect-sdk
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: Python SDK to interact with INTERSECT
|
|
5
5
|
Keywords: intersect
|
|
6
6
|
Author-Email: Lance Drane <dranelt@ornl.gov>, Marshall McDonnell <mcdonnellmt@ornl.gov>, Seth Hitefield <hitefieldsd@ornl.gov>, Andrew Ayres <ayresaf@ornl.gov>, Gregory Cage <cagege@ornl.gov>, Jesse McGaha <mcgahajr@ornl.gov>, Robert Smith <smithrw@ornl.gov>, Gavin Wiggins <wigginsg@ornl.gov>, Michael Brim <brimmj@ornl.gov>, Rick Archibald <archibaldrk@ornl.gov>, Addi Malviya Thakur <malviyaa@ornl.gov>
|
|
@@ -21,13 +21,15 @@ Requires-Dist: paho-mqtt<2.0.0,>=1.6.1
|
|
|
21
21
|
Requires-Dist: minio>=7.2.3
|
|
22
22
|
Requires-Dist: jsonschema[format-nongpl]>=4.21.1
|
|
23
23
|
Requires-Dist: eval-type-backport>=0.1.3; python_version < "3.10"
|
|
24
|
+
Provides-Extra: amqp
|
|
24
25
|
Requires-Dist: pika<2.0.0,>=1.3.2; extra == "amqp"
|
|
26
|
+
Provides-Extra: docs
|
|
25
27
|
Requires-Dist: sphinx>=5.3.0; extra == "docs"
|
|
26
28
|
Requires-Dist: furo>=2023.3.27; extra == "docs"
|
|
27
|
-
Provides-Extra: amqp
|
|
28
|
-
Provides-Extra: docs
|
|
29
29
|
Description-Content-Type: text/markdown
|
|
30
30
|
|
|
31
|
+
[](https://doi.org/10.11578/dc.20240927.1)
|
|
32
|
+
|
|
31
33
|
# INTERSECT-SDK
|
|
32
34
|
|
|
33
35
|
The INTERSECT-SDK is a framework for microservices to integrate themselves into the wider Interconnected Science Ecosystem (INTERSECT).
|
|
@@ -37,6 +39,8 @@ Please note that this README is currently a work in progress.
|
|
|
37
39
|
## What is INTERSECT?
|
|
38
40
|
[INTERSECT](https://www.ornl.gov/intersect) was designed as a specific usecase - as an open federated hardware/software architecture for the laboratory of the future, which connects scientific instruments, robot-controlled laboratories and edge/center computing/data resources to enable autonomous experiments, self-driving laboratories, smart manufacturing, and AI-driven design, discovery and evaluation.
|
|
39
41
|
|
|
42
|
+
For a high-level overview, please see [the architecture website.](https://intersect-architecture.readthedocs.io/en/latest/)
|
|
43
|
+
|
|
40
44
|
## What are the core design philosophies of the SDK?
|
|
41
45
|
|
|
42
46
|
- Event-driven architecture
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
[](https://doi.org/10.11578/dc.20240927.1)
|
|
2
|
+
|
|
1
3
|
# INTERSECT-SDK
|
|
2
4
|
|
|
3
5
|
The INTERSECT-SDK is a framework for microservices to integrate themselves into the wider Interconnected Science Ecosystem (INTERSECT).
|
|
@@ -7,6 +9,8 @@ Please note that this README is currently a work in progress.
|
|
|
7
9
|
## What is INTERSECT?
|
|
8
10
|
[INTERSECT](https://www.ornl.gov/intersect) was designed as a specific usecase - as an open federated hardware/software architecture for the laboratory of the future, which connects scientific instruments, robot-controlled laboratories and edge/center computing/data resources to enable autonomous experiments, self-driving laboratories, smart manufacturing, and AI-driven design, discovery and evaluation.
|
|
9
11
|
|
|
12
|
+
For a high-level overview, please see [the architecture website.](https://intersect-architecture.readthedocs.io/en/latest/)
|
|
13
|
+
|
|
10
14
|
## What are the core design philosophies of the SDK?
|
|
11
15
|
|
|
12
16
|
- Event-driven architecture
|
|
@@ -35,7 +35,7 @@ dependencies = [
|
|
|
35
35
|
"jsonschema[format-nongpl]>=4.21.1",
|
|
36
36
|
"eval-type-backport>=0.1.3;python_version<'3.10'",
|
|
37
37
|
]
|
|
38
|
-
version = "0.8.
|
|
38
|
+
version = "0.8.1"
|
|
39
39
|
|
|
40
40
|
[project.license]
|
|
41
41
|
text = "BSD-3-Clause"
|
|
@@ -55,42 +55,6 @@ docs = [
|
|
|
55
55
|
"furo>=2023.3.27",
|
|
56
56
|
]
|
|
57
57
|
|
|
58
|
-
[tool.pdm.dev-dependencies]
|
|
59
|
-
lint = [
|
|
60
|
-
"pre-commit>=3.3.1",
|
|
61
|
-
"ruff==0.5.7",
|
|
62
|
-
"mypy>=1.10.0",
|
|
63
|
-
"types-paho-mqtt>=1.6.0.20240106",
|
|
64
|
-
]
|
|
65
|
-
test = [
|
|
66
|
-
"pytest>=7.3.2",
|
|
67
|
-
"pytest-cov>=4.1.0",
|
|
68
|
-
"httpretty>=1.1.4",
|
|
69
|
-
]
|
|
70
|
-
|
|
71
|
-
[tool.pdm.scripts]
|
|
72
|
-
test-all = "pytest tests/ --cov=src/intersect_sdk/ --cov-fail-under=80 --cov-report=html:reports/htmlcov/ --cov-report=xml:reports/coverage_report.xml --junitxml=reports/junit.xml"
|
|
73
|
-
test-all-debug = "pytest tests/ --cov=src/intersect_sdk/ --cov-fail-under=80 --cov-report=html:reports/htmlcov/ --cov-report=xml:reports/coverage_report.xml --junitxml=reports/junit.xml -s"
|
|
74
|
-
test-unit = "pytest tests/unit --cov=src/intersect_sdk/"
|
|
75
|
-
test-e2e = "pytest tests/e2e --cov=src/intersect_sdk/"
|
|
76
|
-
lint-format = "ruff format"
|
|
77
|
-
lint-ruff = "ruff check --fix"
|
|
78
|
-
lint-mypy = "mypy src/intersect_sdk/"
|
|
79
|
-
|
|
80
|
-
[tool.pdm.scripts.lint]
|
|
81
|
-
composite = [
|
|
82
|
-
"lint-format",
|
|
83
|
-
"lint-ruff",
|
|
84
|
-
"lint-mypy",
|
|
85
|
-
]
|
|
86
|
-
|
|
87
|
-
[tool.pdm.build]
|
|
88
|
-
package-dir = "src"
|
|
89
|
-
|
|
90
|
-
[tool.pdm.version]
|
|
91
|
-
source = "file"
|
|
92
|
-
path = "src/intersect_sdk/version.py"
|
|
93
|
-
|
|
94
58
|
[tool.ruff]
|
|
95
59
|
line-length = 100
|
|
96
60
|
|
|
@@ -144,8 +108,6 @@ ignore = [
|
|
|
144
108
|
"COM812",
|
|
145
109
|
"ISC001",
|
|
146
110
|
"SIM105",
|
|
147
|
-
"ANN101",
|
|
148
|
-
"ANN102",
|
|
149
111
|
"ANN401",
|
|
150
112
|
"PLR2004",
|
|
151
113
|
]
|
|
@@ -231,6 +193,51 @@ exclude_also = [
|
|
|
231
193
|
"except.* ImportError",
|
|
232
194
|
]
|
|
233
195
|
|
|
196
|
+
[tool.pdm.dev-dependencies]
|
|
197
|
+
lint = [
|
|
198
|
+
"pre-commit>=3.3.1",
|
|
199
|
+
"ruff==0.9.4",
|
|
200
|
+
"mypy>=1.10.0",
|
|
201
|
+
"types-paho-mqtt>=1.6.0.20240106",
|
|
202
|
+
"codespell>=2.3.0",
|
|
203
|
+
]
|
|
204
|
+
test = [
|
|
205
|
+
"pytest>=7.3.2",
|
|
206
|
+
"pytest-cov>=4.1.0",
|
|
207
|
+
"httpretty>=1.1.4",
|
|
208
|
+
]
|
|
209
|
+
|
|
210
|
+
[tool.pdm.scripts]
|
|
211
|
+
test-all = "pytest tests/ --cov=src/intersect_sdk/ --cov-fail-under=80 --cov-report=html:reports/htmlcov/ --cov-report=xml:reports/coverage_report.xml --junitxml=reports/junit.xml"
|
|
212
|
+
test-all-debug = "pytest tests/ --cov=src/intersect_sdk/ --cov-fail-under=80 --cov-report=html:reports/htmlcov/ --cov-report=xml:reports/coverage_report.xml --junitxml=reports/junit.xml -s"
|
|
213
|
+
test-unit = "pytest tests/unit --cov=src/intersect_sdk/"
|
|
214
|
+
test-e2e = "pytest tests/e2e --cov=src/intersect_sdk/"
|
|
215
|
+
lint-docs = "sphinx-build -W --keep-going docs docs/_build"
|
|
216
|
+
lint-format = "ruff format"
|
|
217
|
+
lint-ruff = "ruff check --fix"
|
|
218
|
+
lint-mypy = "mypy src/intersect_sdk/"
|
|
219
|
+
lint-spelling = "codespell -w docs examples src tests *.md -S docs/_build"
|
|
220
|
+
|
|
221
|
+
[tool.pdm.scripts.lint]
|
|
222
|
+
composite = [
|
|
223
|
+
"lint-format",
|
|
224
|
+
"lint-ruff",
|
|
225
|
+
"lint-mypy",
|
|
226
|
+
"lint-spelling",
|
|
227
|
+
"lint-docs",
|
|
228
|
+
]
|
|
229
|
+
|
|
230
|
+
[tool.pdm.build]
|
|
231
|
+
package-dir = "src"
|
|
232
|
+
|
|
233
|
+
[tool.pdm.version]
|
|
234
|
+
source = "file"
|
|
235
|
+
path = "src/intersect_sdk/version.py"
|
|
236
|
+
|
|
237
|
+
[tool.codespell]
|
|
238
|
+
skip = ".git*,*.lock,.venv,.*cache/*,./docs/_build/*"
|
|
239
|
+
check-hidden = true
|
|
240
|
+
|
|
234
241
|
[build-system]
|
|
235
242
|
requires = [
|
|
236
243
|
"pdm-backend",
|
|
@@ -254,7 +254,7 @@ class AMQPClient(BrokerClient):
|
|
|
254
254
|
if self._should_disconnect:
|
|
255
255
|
connection.ioloop.stop()
|
|
256
256
|
else:
|
|
257
|
-
logger.
|
|
257
|
+
logger.warning('Connection closed, reopening in 5 seconds: %s', reason)
|
|
258
258
|
connection.ioloop.call_later(5, connection.ioloop.stop)
|
|
259
259
|
self._channel_flags.unset_all()
|
|
260
260
|
self._channel_out = None
|
|
@@ -113,7 +113,7 @@ class MQTTClient(BrokerClient):
|
|
|
113
113
|
topic: The topic on which to publish the message as a string.
|
|
114
114
|
payload: The message to publish, as raw bytes.
|
|
115
115
|
persist: Determine if the message should live until queue consumers or available (True), or
|
|
116
|
-
if it should be removed
|
|
116
|
+
if it should be removed immediately (False)
|
|
117
117
|
"""
|
|
118
118
|
# NOTE: RabbitMQ only works with QOS of 1 and 0, and seems to convert QOS2 to QOS1
|
|
119
119
|
self._connection.publish(topic, payload, qos=2 if persist else 0)
|
|
@@ -32,7 +32,7 @@ class DataPlaneManager:
|
|
|
32
32
|
|
|
33
33
|
# warn users about missing data plane
|
|
34
34
|
if not self._minio_providers:
|
|
35
|
-
logger.
|
|
35
|
+
logger.warning('WARNING: This service cannot support any MINIO instances')
|
|
36
36
|
|
|
37
37
|
def incoming_message_data_handler(self, message: UserspaceMessage | EventMessage) -> bytes:
|
|
38
38
|
"""Get data from the request data provider.
|
|
@@ -42,7 +42,7 @@ def definition_metadata_differences(
|
|
|
42
42
|
) -> list[tuple[str, str, str]]:
|
|
43
43
|
"""Return a list of differences between 'definition' and 'metadata'.
|
|
44
44
|
|
|
45
|
-
First tuple value =
|
|
45
|
+
First tuple value = definition key
|
|
46
46
|
Second tuple value = second value
|
|
47
47
|
Third tuple value = already cached value
|
|
48
48
|
"""
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
from typing import TYPE_CHECKING, Any
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Protocol
|
|
5
4
|
|
|
6
5
|
if TYPE_CHECKING:
|
|
7
6
|
from uuid import UUID
|
|
8
7
|
|
|
8
|
+
from ..client_callback_definitions import INTERSECT_CLIENT_EVENT_CALLBACK_TYPE
|
|
9
|
+
from ..config.shared import HierarchyConfig
|
|
9
10
|
from ..service_callback_definitions import (
|
|
10
11
|
INTERSECT_SERVICE_RESPONSE_CALLBACK_TYPE,
|
|
11
12
|
)
|
|
@@ -14,13 +15,12 @@ if TYPE_CHECKING:
|
|
|
14
15
|
)
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
class IntersectEventObserver(
|
|
18
|
+
class IntersectEventObserver(Protocol):
|
|
18
19
|
"""Abstract definition of an entity which observes an INTERSECT event (i.e. IntersectService).
|
|
19
20
|
|
|
20
21
|
Used as the common interface for event emitters (i.e. CapabilityImplementations).
|
|
21
22
|
"""
|
|
22
23
|
|
|
23
|
-
@abstractmethod
|
|
24
24
|
def _on_observe_event(self, event_name: str, event_value: Any, operation: str) -> None:
|
|
25
25
|
"""How to react to an event being fired.
|
|
26
26
|
|
|
@@ -31,17 +31,16 @@ class IntersectEventObserver(ABC):
|
|
|
31
31
|
"""
|
|
32
32
|
...
|
|
33
33
|
|
|
34
|
-
@abstractmethod
|
|
35
34
|
def create_external_request(
|
|
36
35
|
self,
|
|
37
36
|
request: IntersectDirectMessageParams,
|
|
38
37
|
response_handler: INTERSECT_SERVICE_RESPONSE_CALLBACK_TYPE | None = None,
|
|
39
38
|
timeout: float = 300.0,
|
|
40
39
|
) -> UUID:
|
|
41
|
-
"""Observed entity (
|
|
40
|
+
"""Observed entity (capability) tells observer (i.e. service) to send an external request.
|
|
42
41
|
|
|
43
42
|
Params:
|
|
44
|
-
- request: the request we want to send out, encapsulated as an
|
|
43
|
+
- request: the request we want to send out, encapsulated as an IntersectDirectMessageParams object
|
|
45
44
|
- response_handler: optional callback for how we want to handle the response from this request.
|
|
46
45
|
- timeout: optional value for how long we should wait on the request, in seconds (default: 300 seconds)
|
|
47
46
|
|
|
@@ -49,3 +48,18 @@ class IntersectEventObserver(ABC):
|
|
|
49
48
|
- generated RequestID associated with your request
|
|
50
49
|
"""
|
|
51
50
|
...
|
|
51
|
+
|
|
52
|
+
def register_event(
|
|
53
|
+
self,
|
|
54
|
+
service: HierarchyConfig,
|
|
55
|
+
event_name: str,
|
|
56
|
+
response_handler: INTERSECT_CLIENT_EVENT_CALLBACK_TYPE,
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Observed entity (capability) tells observer (i.e. service) to subscribe to a specific event.
|
|
59
|
+
|
|
60
|
+
Params:
|
|
61
|
+
- service: HierarchyConfig of the service we want to talk to
|
|
62
|
+
- event_name: name of event to subscribe to
|
|
63
|
+
- response_handler: callback for how to handle the reception of an event
|
|
64
|
+
"""
|
|
65
|
+
...
|
{intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/messages/userspace.py
RENAMED
|
@@ -25,13 +25,13 @@ from typing import Any, Union
|
|
|
25
25
|
from pydantic import AwareDatetime, Field, TypeAdapter
|
|
26
26
|
from typing_extensions import Annotated, TypedDict
|
|
27
27
|
|
|
28
|
-
from ...constants import SYSTEM_OF_SYSTEM_REGEX
|
|
29
|
-
from ...core_definitions import (
|
|
28
|
+
from ...constants import SYSTEM_OF_SYSTEM_REGEX
|
|
29
|
+
from ...core_definitions import (
|
|
30
30
|
IntersectDataHandler,
|
|
31
31
|
IntersectMimeType,
|
|
32
32
|
)
|
|
33
33
|
from ...version import version_string
|
|
34
|
-
from ..data_plane.minio_utils import MinioPayload # noqa:
|
|
34
|
+
from ..data_plane.minio_utils import MinioPayload # noqa: TC001 (this is runtime checked)
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
class UserspaceMessageHeader(TypedDict):
|
|
@@ -557,7 +557,7 @@ def get_schema_and_functions_from_capability_implementations(
|
|
|
557
557
|
if cap_status_fn_name and cap_status_schema and cap_status_type_adapter:
|
|
558
558
|
if status_function_name is not None:
|
|
559
559
|
# TODO may want to change this later
|
|
560
|
-
die('Only one
|
|
560
|
+
die('Only one capability may have an @intersect_status function')
|
|
561
561
|
status_function_cap = capability_type
|
|
562
562
|
status_function_name = cap_status_fn_name
|
|
563
563
|
status_function_schema = cap_status_schema
|
|
@@ -82,7 +82,7 @@ class SignalHandler:
|
|
|
82
82
|
----------
|
|
83
83
|
signal (int): signal code.
|
|
84
84
|
"""
|
|
85
|
-
logger.warning('shutting down and handling signal %
|
|
85
|
+
logger.warning('shutting down and handling signal %s', signal)
|
|
86
86
|
if self._cleanup_callback:
|
|
87
87
|
self._cleanup_callback(signal)
|
|
88
88
|
self._exit.set()
|
|
@@ -14,6 +14,8 @@ if TYPE_CHECKING:
|
|
|
14
14
|
from uuid import UUID
|
|
15
15
|
|
|
16
16
|
from .._internal.interfaces import IntersectEventObserver
|
|
17
|
+
from ..client_callback_definitions import INTERSECT_CLIENT_EVENT_CALLBACK_TYPE
|
|
18
|
+
from ..config.shared import HierarchyConfig
|
|
17
19
|
from ..service_callback_definitions import (
|
|
18
20
|
INTERSECT_SERVICE_RESPONSE_CALLBACK_TYPE,
|
|
19
21
|
)
|
|
@@ -137,8 +139,10 @@ class IntersectBaseCapabilityImplementation:
|
|
|
137
139
|
) -> list[UUID]:
|
|
138
140
|
"""Create an external request that we'll send to a different Service.
|
|
139
141
|
|
|
142
|
+
Note: You should generally NOT call this function until after you have initialized the IntersectService class.
|
|
143
|
+
|
|
140
144
|
Params:
|
|
141
|
-
- request: the request we want to send out, encapsulated as an
|
|
145
|
+
- request: the request we want to send out, encapsulated as an IntersectDirectMessageParams object
|
|
142
146
|
- response_handler: optional callback for how we want to handle the response from this request.
|
|
143
147
|
- timeout: optional value for how long we should wait on the request, in seconds (default: 300 seconds)
|
|
144
148
|
|
|
@@ -153,3 +157,27 @@ class IntersectBaseCapabilityImplementation:
|
|
|
153
157
|
observer.create_external_request(request, response_handler, timeout)
|
|
154
158
|
for observer in self.__intersect_sdk_observers__
|
|
155
159
|
]
|
|
160
|
+
|
|
161
|
+
@final
|
|
162
|
+
def intersect_sdk_listen_for_service_event(
|
|
163
|
+
self,
|
|
164
|
+
service: HierarchyConfig,
|
|
165
|
+
event_name: str,
|
|
166
|
+
response_handler: INTERSECT_CLIENT_EVENT_CALLBACK_TYPE,
|
|
167
|
+
) -> None:
|
|
168
|
+
"""Start listening to events from a specific Service.
|
|
169
|
+
|
|
170
|
+
Note: You should generally NOT call this function until after you have initialized the IntersectService class.
|
|
171
|
+
|
|
172
|
+
Params:
|
|
173
|
+
- service: The system-of-system hierarchy which points to the specific service
|
|
174
|
+
- event_name: The name of the event we want to listen for
|
|
175
|
+
- response_handler: callback for how to handle the reception of an event
|
|
176
|
+
The callback submits these parameters:
|
|
177
|
+
1) message source
|
|
178
|
+
2) name of operation
|
|
179
|
+
3) name of event
|
|
180
|
+
4) payload
|
|
181
|
+
"""
|
|
182
|
+
for observer in self.__intersect_sdk_observers__:
|
|
183
|
+
observer.register_event(service, event_name, response_handler)
|
|
@@ -129,7 +129,7 @@ class IntersectClient:
|
|
|
129
129
|
if user_callback:
|
|
130
130
|
# Do not persist, as we use the temporary client information to build this.
|
|
131
131
|
self._control_plane_manager.add_subscription_channel(
|
|
132
|
-
f
|
|
132
|
+
f'{self._hierarchy.hierarchy_string("/")}/response',
|
|
133
133
|
{self._handle_userspace_message_raw},
|
|
134
134
|
persist=False,
|
|
135
135
|
)
|
|
@@ -140,7 +140,7 @@ class IntersectClient:
|
|
|
140
140
|
service
|
|
141
141
|
) in config.initial_message_event_config.services_to_start_listening_for_events:
|
|
142
142
|
self._control_plane_manager.add_subscription_channel(
|
|
143
|
-
f
|
|
143
|
+
f'{service.replace(".", "/")}/events',
|
|
144
144
|
{self._handle_event_message_raw},
|
|
145
145
|
persist=False,
|
|
146
146
|
)
|
|
@@ -390,13 +390,13 @@ class IntersectClient:
|
|
|
390
390
|
if self._event_callback:
|
|
391
391
|
for add_event in validated_result.services_to_start_listening_for_events:
|
|
392
392
|
self._control_plane_manager.add_subscription_channel(
|
|
393
|
-
f
|
|
393
|
+
f'{add_event.replace(".", "/")}/events',
|
|
394
394
|
{self._handle_event_message_raw},
|
|
395
395
|
persist=False,
|
|
396
396
|
)
|
|
397
397
|
for remove_event in validated_result.services_to_stop_listening_for_events:
|
|
398
398
|
self._control_plane_manager.remove_subscription_channel(
|
|
399
|
-
f
|
|
399
|
+
f'{remove_event.replace(".", "/")}/events'
|
|
400
400
|
)
|
|
401
401
|
|
|
402
402
|
# sending userspace messages without the callback is okay, we just won't get the response
|
|
@@ -431,7 +431,7 @@ class IntersectClient:
|
|
|
431
431
|
payload=out_payload,
|
|
432
432
|
)
|
|
433
433
|
logger.debug(f'Send userspace message:\n{msg}')
|
|
434
|
-
channel = f
|
|
434
|
+
channel = f'{params.destination.replace(".", "/")}/request'
|
|
435
435
|
# WARNING: If both the Service and the Client drop, the Service will execute the command
|
|
436
436
|
# but cannot communicate the response to the Client.
|
|
437
437
|
# in experiment controllers or production, you'll want to set persist to True
|
|
@@ -28,7 +28,7 @@ ControlProvider = Literal['mqtt3.1.1', 'amqp0.9.1']
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class HierarchyConfig(BaseModel):
|
|
31
|
-
"""Configuration for
|
|
31
|
+
"""Configuration for registering this service in a system-of-system architecture."""
|
|
32
32
|
|
|
33
33
|
service: Annotated[str, Field(pattern=HIERARCHY_REGEX)]
|
|
34
34
|
"""
|
|
@@ -48,11 +48,11 @@ def get_schema_from_capability_implementations(
|
|
|
48
48
|
|
|
49
49
|
The generated schema is currently not a complete replica of the AsyncAPI spec. See https://www.asyncapi.com/docs/reference/specification/v2.6.0 for the complete specification.
|
|
50
50
|
Some key differences:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
- We utilize three custom fields: "capabilities", "events", and "status".
|
|
52
|
+
- "capabilities" contains a dictionary: the keys of this dictionary are capability names. The values are dictionaries with the "description" property being a string which describes the capability,
|
|
53
|
+
and a "channels" property which more closely follows the AsyncAPI specification of the top-level value "channels".
|
|
54
|
+
- "events" is a key-value dictionary: the keys represent the event name, the values represent the associated schema of the event type. Events are currently shared across all capabilities.
|
|
55
|
+
- "status" will have a value of the status schema - if no status has been defined, a null schema is used.
|
|
56
56
|
|
|
57
57
|
Params:
|
|
58
58
|
- capability_types - list of classes (not objects) of capabilities. We do not require capabilities to be instantiated here, in case the instantiation of a capability has external dependencies.
|
|
@@ -17,6 +17,7 @@ Most useful definitions and typings will be found in the service_definitions mod
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
19
|
import time
|
|
20
|
+
from collections import defaultdict
|
|
20
21
|
from threading import Lock
|
|
21
22
|
from types import MappingProxyType
|
|
22
23
|
from typing import TYPE_CHECKING, Any, Callable, Literal, Union
|
|
@@ -40,7 +41,10 @@ from ._internal.data_plane.data_plane_manager import DataPlaneManager
|
|
|
40
41
|
from ._internal.exceptions import IntersectApplicationError, IntersectError
|
|
41
42
|
from ._internal.interfaces import IntersectEventObserver
|
|
42
43
|
from ._internal.logger import logger
|
|
43
|
-
from ._internal.messages.event import
|
|
44
|
+
from ._internal.messages.event import (
|
|
45
|
+
create_event_message,
|
|
46
|
+
deserialize_and_validate_event_message,
|
|
47
|
+
)
|
|
44
48
|
from ._internal.messages.lifecycle import LifecycleType, create_lifecycle_message
|
|
45
49
|
from ._internal.messages.userspace import (
|
|
46
50
|
UserspaceMessage,
|
|
@@ -52,19 +56,24 @@ from ._internal.stoppable_thread import StoppableThread
|
|
|
52
56
|
from ._internal.utils import die
|
|
53
57
|
from ._internal.version_resolver import resolve_user_version
|
|
54
58
|
from .capability.base import IntersectBaseCapabilityImplementation
|
|
59
|
+
from .client_callback_definitions import (
|
|
60
|
+
INTERSECT_CLIENT_EVENT_CALLBACK_TYPE, # noqa: TC001 (runtime-checked-annotation)
|
|
61
|
+
)
|
|
55
62
|
from .config.service import IntersectServiceConfig
|
|
63
|
+
from .config.shared import HierarchyConfig
|
|
56
64
|
from .core_definitions import IntersectDataHandler, IntersectMimeType
|
|
57
65
|
from .service_callback_definitions import (
|
|
58
|
-
INTERSECT_SERVICE_RESPONSE_CALLBACK_TYPE, # noqa:
|
|
66
|
+
INTERSECT_SERVICE_RESPONSE_CALLBACK_TYPE, # noqa: TC001 (runtime-checked annotation)
|
|
59
67
|
)
|
|
60
68
|
from .shared_callback_definitions import (
|
|
61
|
-
INTERSECT_JSON_VALUE, # noqa:
|
|
62
|
-
IntersectDirectMessageParams, # noqa:
|
|
69
|
+
INTERSECT_JSON_VALUE, # noqa: TC001 (runtime-checked annotation)
|
|
70
|
+
IntersectDirectMessageParams, # noqa: TC001 (runtime-checked annotation)
|
|
63
71
|
)
|
|
64
72
|
from .version import version_string
|
|
65
73
|
|
|
66
74
|
if TYPE_CHECKING:
|
|
67
75
|
from ._internal.function_metadata import FunctionMetadata
|
|
76
|
+
from .config.shared import HierarchyConfig
|
|
68
77
|
|
|
69
78
|
|
|
70
79
|
@final
|
|
@@ -241,7 +250,9 @@ class IntersectService(IntersectEventObserver):
|
|
|
241
250
|
self._status_retrieval_fn: Callable[[], bytes] = (
|
|
242
251
|
(
|
|
243
252
|
lambda: status_type_adapter.dump_json(
|
|
244
|
-
getattr(status_fn_capability, status_fn_name)(),
|
|
253
|
+
getattr(status_fn_capability, status_fn_name)(),
|
|
254
|
+
by_alias=True,
|
|
255
|
+
warnings='error',
|
|
245
256
|
)
|
|
246
257
|
)
|
|
247
258
|
if status_type_adapter and status_fn_name
|
|
@@ -255,25 +266,49 @@ class IntersectService(IntersectEventObserver):
|
|
|
255
266
|
self._external_requests: dict[str, IntersectService._ExternalRequest] = {}
|
|
256
267
|
self._external_request_ctr = 0
|
|
257
268
|
|
|
269
|
+
self._svc2svc_events: defaultdict[
|
|
270
|
+
str, defaultdict[str, set[INTERSECT_CLIENT_EVENT_CALLBACK_TYPE]]
|
|
271
|
+
] = defaultdict(lambda: defaultdict(set))
|
|
272
|
+
"""
|
|
273
|
+
tree of other service events we're subscribed to, and what we need to call when we get an event
|
|
274
|
+
|
|
275
|
+
i.e.
|
|
276
|
+
|
|
277
|
+
{
|
|
278
|
+
"org.fac.sys1.subsys.service": {
|
|
279
|
+
"event_name_1": [
|
|
280
|
+
# user_function_1,
|
|
281
|
+
# user_function_2
|
|
282
|
+
]
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
"""
|
|
286
|
+
|
|
258
287
|
self._startup_messages: list[
|
|
259
|
-
tuple[
|
|
288
|
+
tuple[
|
|
289
|
+
IntersectDirectMessageParams,
|
|
290
|
+
INTERSECT_SERVICE_RESPONSE_CALLBACK_TYPE | None,
|
|
291
|
+
]
|
|
260
292
|
] = []
|
|
261
293
|
self._resend_startup_messages = True
|
|
262
294
|
self._sent_startup_messages = False
|
|
263
295
|
|
|
264
296
|
self._shutdown_messages: list[
|
|
265
|
-
tuple[
|
|
297
|
+
tuple[
|
|
298
|
+
IntersectDirectMessageParams,
|
|
299
|
+
INTERSECT_SERVICE_RESPONSE_CALLBACK_TYPE | None,
|
|
300
|
+
]
|
|
266
301
|
] = []
|
|
267
302
|
|
|
268
303
|
self._data_plane_manager = DataPlaneManager(self._hierarchy, config.data_stores)
|
|
269
304
|
# we PUBLISH messages on this channel
|
|
270
|
-
self._lifecycle_channel_name = f
|
|
305
|
+
self._lifecycle_channel_name = f'{config.hierarchy.hierarchy_string("/")}/lifecycle'
|
|
271
306
|
# we PUBLISH event messages on this channel
|
|
272
|
-
self._events_channel_name = f
|
|
307
|
+
self._events_channel_name = f'{config.hierarchy.hierarchy_string("/")}/events'
|
|
273
308
|
# we SUBSCRIBE to messages on this channel to receive requests
|
|
274
|
-
self._service_channel_name = f
|
|
309
|
+
self._service_channel_name = f'{config.hierarchy.hierarchy_string("/")}/request'
|
|
275
310
|
# we SUBSCRIBE to messages on this channel to receive responses
|
|
276
|
-
self._client_channel_name = f
|
|
311
|
+
self._client_channel_name = f'{config.hierarchy.hierarchy_string("/")}/response'
|
|
277
312
|
|
|
278
313
|
self._control_plane_manager = ControlPlaneManager(
|
|
279
314
|
control_configs=config.brokers,
|
|
@@ -323,7 +358,8 @@ class IntersectService(IntersectEventObserver):
|
|
|
323
358
|
# Start the status thread if it doesn't already exist
|
|
324
359
|
if self._status_thread is None:
|
|
325
360
|
self._status_thread = StoppableThread(
|
|
326
|
-
target=self._status_ticker,
|
|
361
|
+
target=self._status_ticker,
|
|
362
|
+
name=f'IntersectService_{self._uuid}_status_thread',
|
|
327
363
|
)
|
|
328
364
|
self._status_thread.start()
|
|
329
365
|
|
|
@@ -483,7 +519,10 @@ class IntersectService(IntersectEventObserver):
|
|
|
483
519
|
def add_startup_messages(
|
|
484
520
|
self,
|
|
485
521
|
messages: list[
|
|
486
|
-
tuple[
|
|
522
|
+
tuple[
|
|
523
|
+
IntersectDirectMessageParams,
|
|
524
|
+
INTERSECT_SERVICE_RESPONSE_CALLBACK_TYPE | None,
|
|
525
|
+
]
|
|
487
526
|
],
|
|
488
527
|
) -> None:
|
|
489
528
|
"""Add request messages to send out to various microservices when this service starts.
|
|
@@ -497,7 +536,10 @@ class IntersectService(IntersectEventObserver):
|
|
|
497
536
|
def add_shutdown_messages(
|
|
498
537
|
self,
|
|
499
538
|
messages: list[
|
|
500
|
-
tuple[
|
|
539
|
+
tuple[
|
|
540
|
+
IntersectDirectMessageParams,
|
|
541
|
+
INTERSECT_SERVICE_RESPONSE_CALLBACK_TYPE | None,
|
|
542
|
+
]
|
|
501
543
|
],
|
|
502
544
|
) -> None:
|
|
503
545
|
"""Add request messages to send out to various microservices on shutdown.
|
|
@@ -508,6 +550,67 @@ class IntersectService(IntersectEventObserver):
|
|
|
508
550
|
"""
|
|
509
551
|
self._shutdown_messages.extend(messages)
|
|
510
552
|
|
|
553
|
+
@validate_call(config=ConfigDict(revalidate_instances='always'))
|
|
554
|
+
def register_event(
|
|
555
|
+
self,
|
|
556
|
+
service: HierarchyConfig,
|
|
557
|
+
event_name: str,
|
|
558
|
+
response_handler: INTERSECT_CLIENT_EVENT_CALLBACK_TYPE,
|
|
559
|
+
) -> None:
|
|
560
|
+
"""Begin subscribing to events from a different Service.
|
|
561
|
+
|
|
562
|
+
Params:
|
|
563
|
+
- service: HierarchyConfig of the service we want to talk to
|
|
564
|
+
- event_name: name of event to subscribe to
|
|
565
|
+
- response_handler: callback for how to handle the reception of an event
|
|
566
|
+
"""
|
|
567
|
+
hierarchy = service.hierarchy_string('.')
|
|
568
|
+
self._svc2svc_events[hierarchy][event_name].add(response_handler)
|
|
569
|
+
|
|
570
|
+
self._control_plane_manager.add_subscription_channel(
|
|
571
|
+
f'{service.hierarchy_string("/")}/events',
|
|
572
|
+
{self._svc2svc_event_callback},
|
|
573
|
+
persist=True,
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
def _svc2svc_event_callback(self, raw: bytes) -> None:
|
|
577
|
+
"""Callback received when this service gets an event from another service.
|
|
578
|
+
|
|
579
|
+
Deserializes and validates an EventMessage, will call a userspace function accordingly.
|
|
580
|
+
"""
|
|
581
|
+
try:
|
|
582
|
+
message = deserialize_and_validate_event_message(raw)
|
|
583
|
+
except ValidationError as e:
|
|
584
|
+
logger.warning(
|
|
585
|
+
"Invalid message received from another service's events channel, ignoring. Full message:\n{}",
|
|
586
|
+
e,
|
|
587
|
+
)
|
|
588
|
+
return
|
|
589
|
+
logger.debug('Received event message:\n{}', message)
|
|
590
|
+
try:
|
|
591
|
+
payload = GENERIC_MESSAGE_SERIALIZER.validate_json(
|
|
592
|
+
self._data_plane_manager.incoming_message_data_handler(message)
|
|
593
|
+
)
|
|
594
|
+
except ValidationError as e:
|
|
595
|
+
logger.warning(
|
|
596
|
+
'Invalid payload message received as an event, ignoring. Full message: {}',
|
|
597
|
+
e,
|
|
598
|
+
)
|
|
599
|
+
return
|
|
600
|
+
source = message['headers']['source']
|
|
601
|
+
event_name = message['headers']['event_name']
|
|
602
|
+
for user_callback in self._svc2svc_events[source][event_name]:
|
|
603
|
+
try:
|
|
604
|
+
user_callback(source, message['operationId'], event_name, payload)
|
|
605
|
+
except Exception as e: # noqa: BLE001 (need to catch any possible user exception)
|
|
606
|
+
logger.warning(
|
|
607
|
+
'!!! INTERSECT: event callback function "%s" produced uncaught exception when handling event "%s" from "%s"',
|
|
608
|
+
user_callback.__name__,
|
|
609
|
+
event_name,
|
|
610
|
+
source,
|
|
611
|
+
)
|
|
612
|
+
logger.warning(e)
|
|
613
|
+
|
|
511
614
|
@validate_call(config=ConfigDict(revalidate_instances='always'))
|
|
512
615
|
def create_external_request(
|
|
513
616
|
self,
|
|
@@ -518,7 +621,7 @@ class IntersectService(IntersectEventObserver):
|
|
|
518
621
|
"""Create an external request that we'll send to a different Service.
|
|
519
622
|
|
|
520
623
|
Params:
|
|
521
|
-
- request: the request we want to send out, encapsulated as an
|
|
624
|
+
- request: the request we want to send out, encapsulated as an IntersectDirectMessageParams object
|
|
522
625
|
- response_handler: optional callback for how we want to handle the response from this request.
|
|
523
626
|
- timeout: optional value for how long we should wait on the request, in seconds (default: 300 seconds)
|
|
524
627
|
|
|
@@ -625,7 +728,7 @@ class IntersectService(IntersectEventObserver):
|
|
|
625
728
|
'error' if response_msg['headers']['has_error'] else 'userspace',
|
|
626
729
|
response_msg,
|
|
627
730
|
)
|
|
628
|
-
response_channel = f
|
|
731
|
+
response_channel = f'{message["headers"]["source"].replace(".", "/")}/response'
|
|
629
732
|
# Persistent userspace messages may be useful for orchestration.
|
|
630
733
|
# Persistence will not hurt anything.
|
|
631
734
|
self._control_plane_manager.publish_message(
|
|
@@ -796,7 +899,7 @@ class IntersectService(IntersectEventObserver):
|
|
|
796
899
|
message_id=request_id,
|
|
797
900
|
)
|
|
798
901
|
logger.debug(f'Sending client message:\n{msg}')
|
|
799
|
-
request_channel = f
|
|
902
|
+
request_channel = f'{params.destination.replace(".", "/")}/request'
|
|
800
903
|
self._control_plane_manager.publish_message(request_channel, msg, persist=True)
|
|
801
904
|
return True
|
|
802
905
|
|
|
@@ -923,8 +1026,7 @@ class IntersectService(IntersectEventObserver):
|
|
|
923
1026
|
event_name=event_name,
|
|
924
1027
|
payload=response_payload,
|
|
925
1028
|
)
|
|
926
|
-
|
|
927
|
-
self._control_plane_manager.publish_message(self._events_channel_name, msg, persist=False)
|
|
1029
|
+
self._control_plane_manager.publish_message(self._events_channel_name, msg, persist=True)
|
|
928
1030
|
|
|
929
1031
|
def _make_error_message(
|
|
930
1032
|
self, error_string: str, original_message: UserspaceMessage
|
|
@@ -143,3 +143,10 @@ def test_example_4_service_to_service():
|
|
|
143
143
|
'Received Response from Service 2: Acknowledging service one text -> Kicking off the example!\n'
|
|
144
144
|
'Received Second Response from Service 2: Acknowledging service one text -> Final Verification\n'
|
|
145
145
|
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_example_4_service_to_service_events():
|
|
149
|
+
assert run_example_test('4_service_to_service_events') == (
|
|
150
|
+
'From event "internal_service_event", received message "not_feeling_creative" from "example-organization.example-facility.example-system.example-subsystem.internal-service"\n'
|
|
151
|
+
'From event "internal_service_event", received message "not_feeling_creative" from "example-organization.example-facility.example-system.example-subsystem.internal-service"\n'
|
|
152
|
+
)
|
|
@@ -28,16 +28,6 @@ from typing import (
|
|
|
28
28
|
from uuid import UUID
|
|
29
29
|
|
|
30
30
|
from annotated_types import Ge, Le
|
|
31
|
-
from intersect_sdk import (
|
|
32
|
-
HierarchyConfig,
|
|
33
|
-
IntersectBaseCapabilityImplementation,
|
|
34
|
-
IntersectDataHandler,
|
|
35
|
-
IntersectEventDefinition,
|
|
36
|
-
IntersectMimeType,
|
|
37
|
-
intersect_event,
|
|
38
|
-
intersect_message,
|
|
39
|
-
intersect_status,
|
|
40
|
-
)
|
|
41
31
|
from pydantic import (
|
|
42
32
|
BaseModel,
|
|
43
33
|
Field,
|
|
@@ -49,6 +39,17 @@ from pydantic import (
|
|
|
49
39
|
from pydantic_core import PydanticCustomError, Url
|
|
50
40
|
from typing_extensions import Annotated, TypeAliasType, TypedDict
|
|
51
41
|
|
|
42
|
+
from intersect_sdk import (
|
|
43
|
+
HierarchyConfig,
|
|
44
|
+
IntersectBaseCapabilityImplementation,
|
|
45
|
+
IntersectDataHandler,
|
|
46
|
+
IntersectEventDefinition,
|
|
47
|
+
IntersectMimeType,
|
|
48
|
+
intersect_event,
|
|
49
|
+
intersect_message,
|
|
50
|
+
intersect_status,
|
|
51
|
+
)
|
|
52
|
+
|
|
52
53
|
FAKE_HIERARCHY_CONFIG = HierarchyConfig(
|
|
53
54
|
organization='test',
|
|
54
55
|
facility='test',
|
|
@@ -450,7 +451,7 @@ class DummyCapabilityImplementation(IntersectBaseCapabilityImplementation):
|
|
|
450
451
|
self.update_status('test_decimal')
|
|
451
452
|
decimal.getcontext().prec = 20
|
|
452
453
|
decimal.getcontext().rounding = decimal.ROUND_HALF_UP
|
|
453
|
-
return input_value / Decimal(3.14159265358979323846)
|
|
454
|
+
return input_value / Decimal('3.14159265358979323846')
|
|
454
455
|
|
|
455
456
|
@intersect_message(
|
|
456
457
|
response_content_type=IntersectMimeType.STRING,
|
|
@@ -31,7 +31,6 @@ from intersect_sdk._internal.messages.userspace import (
|
|
|
31
31
|
create_userspace_message,
|
|
32
32
|
deserialize_and_validate_userspace_message,
|
|
33
33
|
)
|
|
34
|
-
|
|
35
34
|
from tests.fixtures.example_schema import FAKE_HIERARCHY_CONFIG, DummyCapabilityImplementation
|
|
36
35
|
|
|
37
36
|
# HELPERS #############################
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import pytest
|
|
2
|
+
from pydantic import ValidationError
|
|
3
|
+
|
|
2
4
|
from intersect_sdk import (
|
|
3
5
|
IntersectBaseCapabilityImplementation,
|
|
4
6
|
IntersectEventDefinition,
|
|
@@ -6,7 +8,6 @@ from intersect_sdk import (
|
|
|
6
8
|
intersect_message,
|
|
7
9
|
intersect_status,
|
|
8
10
|
)
|
|
9
|
-
from pydantic import ValidationError
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
# this should immediately fail when trying to define the class itself
|
|
@@ -62,7 +63,7 @@ def test_incorrect_intersect_event_annotations():
|
|
|
62
63
|
assert len(errors) == 1
|
|
63
64
|
assert {'type': 'missing', 'loc': ('event_type',)} in errors
|
|
64
65
|
|
|
65
|
-
# special case: we have a custom
|
|
66
|
+
# special case: we have a custom validator for event_type which fails on some common instantiations
|
|
66
67
|
with pytest.raises(ValidationError) as ex:
|
|
67
68
|
|
|
68
69
|
class BadEventArgs4(IntersectBaseCapabilityImplementation):
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import pytest
|
|
2
|
+
from pydantic import TypeAdapter, ValidationError
|
|
3
|
+
|
|
2
4
|
from intersect_sdk import (
|
|
3
5
|
ControlPlaneConfig,
|
|
4
6
|
DataStoreConfig,
|
|
@@ -8,7 +10,6 @@ from intersect_sdk import (
|
|
|
8
10
|
IntersectClientConfig,
|
|
9
11
|
IntersectServiceConfig,
|
|
10
12
|
)
|
|
11
|
-
from pydantic import TypeAdapter, ValidationError
|
|
12
13
|
|
|
13
14
|
# TESTS #####################
|
|
14
15
|
|
|
@@ -6,13 +6,14 @@ import datetime
|
|
|
6
6
|
import uuid
|
|
7
7
|
|
|
8
8
|
import pytest
|
|
9
|
+
from pydantic import ValidationError
|
|
10
|
+
|
|
9
11
|
from intersect_sdk import version_string
|
|
10
12
|
from intersect_sdk._internal.messages.lifecycle import (
|
|
11
13
|
LifecycleType,
|
|
12
14
|
create_lifecycle_message,
|
|
13
15
|
deserialize_and_validate_lifecycle_message,
|
|
14
16
|
)
|
|
15
|
-
from pydantic import ValidationError
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
def test_valid_lifecycle_message_deserializes():
|
|
@@ -21,6 +21,9 @@ from typing import Any, Dict, FrozenSet, Generator, List, NamedTuple, Set, Tuple
|
|
|
21
21
|
|
|
22
22
|
import pytest
|
|
23
23
|
from annotated_types import Gt
|
|
24
|
+
from pydantic import BaseModel, Field
|
|
25
|
+
from typing_extensions import Annotated, TypeAliasType, TypedDict
|
|
26
|
+
|
|
24
27
|
from intersect_sdk import (
|
|
25
28
|
HierarchyConfig,
|
|
26
29
|
IntersectBaseCapabilityImplementation,
|
|
@@ -32,8 +35,6 @@ from intersect_sdk import (
|
|
|
32
35
|
intersect_message,
|
|
33
36
|
intersect_status,
|
|
34
37
|
)
|
|
35
|
-
from pydantic import BaseModel, Field
|
|
36
|
-
from typing_extensions import Annotated, TypeAliasType, TypedDict
|
|
37
38
|
|
|
38
39
|
# HELPERS #########################
|
|
39
40
|
|
|
@@ -132,12 +133,12 @@ def test_disallow_unparsable_annotation(caplog: pytest.LogCaptureFixture):
|
|
|
132
133
|
three: str
|
|
133
134
|
|
|
134
135
|
@intersect_message()
|
|
135
|
-
def cant_parse_annotation(self,
|
|
136
|
+
def cant_parse_annotation(self, unparsable: PydanticUnparsableInner) -> None: ...
|
|
136
137
|
|
|
137
138
|
with pytest.raises(SystemExit):
|
|
138
139
|
get_schema_helper([PydanticUnparsable])
|
|
139
140
|
assert (
|
|
140
|
-
"On capability 'PydanticUnparsable', parameter '
|
|
141
|
+
"On capability 'PydanticUnparsable', parameter 'unparsable' type annotation" in caplog.text
|
|
141
142
|
)
|
|
142
143
|
assert "on function 'cant_parse_annotation' is invalid" in caplog.text
|
|
143
144
|
|
|
@@ -158,7 +159,7 @@ def test_disallow_object_typing(caplog: pytest.LogCaptureFixture):
|
|
|
158
159
|
|
|
159
160
|
def test_disallow_object_subtyping(caplog: pytest.LogCaptureFixture):
|
|
160
161
|
# should fail because return type has a subtyping object (dynamic typing)
|
|
161
|
-
# note that 'object' is
|
|
162
|
+
# note that 'object' is evaluated exactly like it is as a root type
|
|
162
163
|
class MockObjectSubtype(IntersectBaseCapabilityImplementation):
|
|
163
164
|
intersect_sdk_capability_name = 'unused'
|
|
164
165
|
|
|
@@ -456,7 +457,7 @@ def test_disallow_empty_dataclass(caplog: pytest.LogCaptureFixture):
|
|
|
456
457
|
|
|
457
458
|
|
|
458
459
|
def test_disallow_ambiguous_typealiastype(caplog: pytest.LogCaptureFixture):
|
|
459
|
-
# should fail because the TypeVar is
|
|
460
|
+
# should fail because the TypeVar is ambiguous and would resolve to "Any"
|
|
460
461
|
class AmbiguousTypeAliasType(IntersectBaseCapabilityImplementation):
|
|
461
462
|
intersect_sdk_capability_name = 'unused'
|
|
462
463
|
T = TypeVar('T')
|
|
@@ -975,4 +976,4 @@ def test_multiple_status_functions_across_capabilities(caplog: pytest.LogCapture
|
|
|
975
976
|
|
|
976
977
|
with pytest.raises(SystemExit):
|
|
977
978
|
get_schema_helper([CapabilityName1, CapabilityName2])
|
|
978
|
-
assert 'Only one
|
|
979
|
+
assert 'Only one capability may have an @intersect_status function' in caplog.text
|
|
@@ -10,7 +10,6 @@ from intersect_sdk._internal.constants import (
|
|
|
10
10
|
)
|
|
11
11
|
from intersect_sdk._internal.schema import get_schema_and_functions_from_capability_implementations
|
|
12
12
|
from intersect_sdk.schema import get_schema_from_capability_implementations
|
|
13
|
-
|
|
14
13
|
from tests.fixtures.example_schema import (
|
|
15
14
|
FAKE_HIERARCHY_CONFIG,
|
|
16
15
|
DummyCapabilityImplementation,
|
|
@@ -6,12 +6,13 @@ import datetime
|
|
|
6
6
|
import uuid
|
|
7
7
|
|
|
8
8
|
import pytest
|
|
9
|
+
from pydantic import ValidationError
|
|
10
|
+
|
|
9
11
|
from intersect_sdk import IntersectDataHandler, IntersectMimeType, version_string
|
|
10
12
|
from intersect_sdk._internal.messages.userspace import (
|
|
11
13
|
create_userspace_message,
|
|
12
14
|
deserialize_and_validate_userspace_message,
|
|
13
15
|
)
|
|
14
|
-
from pydantic import ValidationError
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
def test_valid_userspace_message_deserializes():
|
|
File without changes
|
|
@@ -43,31 +43,31 @@ from .shared_callback_definitions import (
|
|
|
43
43
|
from .version import __version__, version_info, version_string
|
|
44
44
|
|
|
45
45
|
__all__ = [
|
|
46
|
-
'IntersectDataHandler',
|
|
47
|
-
'IntersectEventDefinition',
|
|
48
|
-
'IntersectMimeType',
|
|
49
|
-
'intersect_event',
|
|
50
|
-
'intersect_message',
|
|
51
|
-
'intersect_status',
|
|
52
|
-
'get_schema_from_capability_implementations',
|
|
53
|
-
'IntersectService',
|
|
54
|
-
'IntersectClient',
|
|
55
|
-
'IntersectClientCallback',
|
|
56
|
-
'IntersectDirectMessageParams',
|
|
57
|
-
'INTERSECT_CLIENT_RESPONSE_CALLBACK_TYPE',
|
|
58
46
|
'INTERSECT_CLIENT_EVENT_CALLBACK_TYPE',
|
|
47
|
+
'INTERSECT_CLIENT_RESPONSE_CALLBACK_TYPE',
|
|
59
48
|
'INTERSECT_JSON_VALUE',
|
|
60
49
|
'INTERSECT_SERVICE_RESPONSE_CALLBACK_TYPE',
|
|
61
|
-
'IntersectBaseCapabilityImplementation',
|
|
62
|
-
'default_intersect_lifecycle_loop',
|
|
63
|
-
'IntersectClientConfig',
|
|
64
|
-
'IntersectServiceConfig',
|
|
65
|
-
'HierarchyConfig',
|
|
66
50
|
'ControlPlaneConfig',
|
|
67
51
|
'ControlProvider',
|
|
68
52
|
'DataStoreConfig',
|
|
69
53
|
'DataStoreConfigMap',
|
|
54
|
+
'HierarchyConfig',
|
|
55
|
+
'IntersectBaseCapabilityImplementation',
|
|
56
|
+
'IntersectClient',
|
|
57
|
+
'IntersectClientCallback',
|
|
58
|
+
'IntersectClientConfig',
|
|
59
|
+
'IntersectDataHandler',
|
|
60
|
+
'IntersectDirectMessageParams',
|
|
61
|
+
'IntersectEventDefinition',
|
|
62
|
+
'IntersectMimeType',
|
|
63
|
+
'IntersectService',
|
|
64
|
+
'IntersectServiceConfig',
|
|
70
65
|
'__version__',
|
|
66
|
+
'default_intersect_lifecycle_loop',
|
|
67
|
+
'get_schema_from_capability_implementations',
|
|
68
|
+
'intersect_event',
|
|
69
|
+
'intersect_message',
|
|
70
|
+
'intersect_status',
|
|
71
71
|
'version_info',
|
|
72
72
|
'version_string',
|
|
73
73
|
]
|
|
File without changes
|
|
File without changes
|
{intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/data_plane/__init__.py
RENAMED
|
File without changes
|
{intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/data_plane/minio_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
{intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/function_metadata.py
RENAMED
|
File without changes
|
|
File without changes
|
{intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/messages/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/messages/lifecycle.py
RENAMED
|
File without changes
|
{intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/multi_flag_thread_event.py
RENAMED
|
File without changes
|
{intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/pydantic_schema_generator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/client_callback_definitions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/service_callback_definitions.py
RENAMED
|
File without changes
|
|
File without changes
|
{intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/shared_callback_definitions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|