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.
Files changed (73) hide show
  1. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/PKG-INFO +7 -3
  2. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/README.md +4 -0
  3. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/pyproject.toml +46 -39
  4. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/brokers/amqp_client.py +1 -1
  5. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/brokers/mqtt_client.py +1 -1
  6. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/data_plane/data_plane_manager.py +1 -1
  7. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/event_metadata.py +1 -1
  8. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/interfaces.py +21 -7
  9. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/messages/userspace.py +3 -3
  10. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/schema.py +1 -1
  11. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/app_lifecycle.py +1 -1
  12. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/capability/base.py +29 -1
  13. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/client.py +5 -5
  14. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/config/shared.py +1 -1
  15. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/schema.py +5 -5
  16. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/service.py +121 -19
  17. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/version.py +1 -1
  18. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/e2e/test_examples.py +7 -0
  19. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/fixtures/example_schema.py +12 -11
  20. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/integration/test_return_type_mismatch.py +0 -1
  21. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/integration/test_service.py +0 -1
  22. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/unit/test_annotations.py +3 -2
  23. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/unit/test_base_capability_implementation.py +1 -0
  24. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/unit/test_config.py +2 -1
  25. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/unit/test_invalid_schema_runtime.py +1 -0
  26. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/unit/test_lifecycle_message.py +2 -1
  27. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/unit/test_schema_invalids.py +8 -7
  28. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/unit/test_schema_valid.py +0 -1
  29. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/unit/test_userspace_message.py +2 -1
  30. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/unit/test_version_resolver.py +1 -0
  31. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/LICENSE +0 -0
  32. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/__init__.py +17 -17
  33. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/__init__.py +0 -0
  34. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/constants.py +0 -0
  35. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/__init__.py +0 -0
  36. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/brokers/__init__.py +0 -0
  37. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/brokers/broker_client.py +0 -0
  38. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/control_plane_manager.py +0 -0
  39. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/discovery_service.py +0 -0
  40. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/topic_handler.py +0 -0
  41. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/data_plane/__init__.py +0 -0
  42. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/data_plane/minio_utils.py +0 -0
  43. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/exceptions.py +0 -0
  44. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/function_metadata.py +0 -0
  45. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/logger.py +0 -0
  46. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/messages/__init__.py +0 -0
  47. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/messages/event.py +0 -0
  48. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/messages/lifecycle.py +0 -0
  49. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/multi_flag_thread_event.py +0 -0
  50. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/pydantic_schema_generator.py +0 -0
  51. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/stoppable_thread.py +0 -0
  52. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/utils.py +0 -0
  53. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/version.py +0 -0
  54. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/version_resolver.py +0 -0
  55. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/capability/__init__.py +0 -0
  56. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/client_callback_definitions.py +0 -0
  57. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/config/__init__.py +0 -0
  58. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/config/client.py +0 -0
  59. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/config/service.py +0 -0
  60. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/constants.py +0 -0
  61. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/core_definitions.py +0 -0
  62. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/py.typed +0 -0
  63. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/service_callback_definitions.py +0 -0
  64. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/service_definitions.py +0 -0
  65. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/src/intersect_sdk/shared_callback_definitions.py +0 -0
  66. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/__init__.py +0 -0
  67. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/conftest.py +0 -0
  68. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/e2e/__init__.py +0 -0
  69. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/fixtures/__init__.py +0 -0
  70. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/fixtures/example_schema.json +0 -0
  71. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/fixtures/return_type_mismatch.py +0 -0
  72. {intersect_sdk-0.8.0 → intersect_sdk-0.8.1}/tests/integration/__init__.py +0 -0
  73. {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.0
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
+ [![Static Badge](https://img.shields.io/badge/DOI-10.11578%2Fdc.20240927.1-blue)](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
+ [![Static Badge](https://img.shields.io/badge/DOI-10.11578%2Fdc.20240927.1-blue)](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.0"
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.warn('Connection closed, reopening in 5 seconds: %s', reason)
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 immediatedly (False)
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.warn('WARNING: This service cannot support any MINIO instances')
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 = defintion key
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 abc import ABC, abstractmethod
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(ABC):
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 (capabilitiy) tells observer (i.e. service) to send an external request.
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 IntersectClientMessageParams object
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
+ ...
@@ -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 # noqa: TCH001 (this is runtime checked)
29
- from ...core_definitions import ( # noqa: TCH001 (this is runtime checked)
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: TCH001 (this is runtime checked)
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 capabilitiy may have an @intersect_status function')
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 %d' % 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 IntersectClientMessageParams object
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"{self._hierarchy.hierarchy_string('/')}/response",
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"{service.replace('.', '/')}/events",
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"{add_event.replace('.', '/')}/events",
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"{remove_event.replace('.', '/')}/events"
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"{params.destination.replace('.', '/')}/request"
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 registring this service in a system-of-system architecture."""
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
- - 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.
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 create_event_message
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: TCH001 (runtime-checked annotation)
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: TCH001 (runtime-checked annotation)
62
- IntersectDirectMessageParams, # noqa: TCH001 (runtime-checked annotation)
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)(), by_alias=True, warnings='error'
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[IntersectDirectMessageParams, INTERSECT_SERVICE_RESPONSE_CALLBACK_TYPE | None]
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[IntersectDirectMessageParams, INTERSECT_SERVICE_RESPONSE_CALLBACK_TYPE | None]
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"{config.hierarchy.hierarchy_string('/')}/lifecycle"
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"{config.hierarchy.hierarchy_string('/')}/events"
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"{config.hierarchy.hierarchy_string('/')}/request"
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"{config.hierarchy.hierarchy_string('/')}/response"
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, name=f'IntersectService_{self._uuid}_status_thread'
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[IntersectDirectMessageParams, INTERSECT_SERVICE_RESPONSE_CALLBACK_TYPE | None]
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[IntersectDirectMessageParams, INTERSECT_SERVICE_RESPONSE_CALLBACK_TYPE | None]
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 IntersectClientMessageParams object
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"{message['headers']['source'].replace('.', '/')}/response"
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"{params.destination.replace('.', '/')}/request"
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
- # Event messages are meant to be short-lived and should not persist.
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
@@ -8,7 +8,7 @@ from __future__ import annotations
8
8
  from ._internal.version import strip_version_metadata
9
9
 
10
10
  # may include build metadata
11
- __version__ = '0.8.0'
11
+ __version__ = '0.8.1'
12
12
 
13
13
  version_string = strip_version_metadata(__version__)
14
14
  """
@@ -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,
@@ -28,7 +28,6 @@ from intersect_sdk._internal.messages.userspace import (
28
28
  create_userspace_message,
29
29
  deserialize_and_validate_userspace_message,
30
30
  )
31
-
32
31
  from tests.fixtures.example_schema import FAKE_HIERARCHY_CONFIG
33
32
 
34
33
  # FIXTURE #############################
@@ -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 vaildator for event_type which fails on some common instantiations
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):
@@ -4,6 +4,7 @@ from typing import Any
4
4
  from uuid import UUID, uuid4
5
5
 
6
6
  import pytest
7
+
7
8
  from intersect_sdk import (
8
9
  INTERSECT_SERVICE_RESPONSE_CALLBACK_TYPE,
9
10
  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,6 +6,7 @@ check that service configuration is valid.
6
6
  """
7
7
 
8
8
  import pytest
9
+
9
10
  from intersect_sdk import (
10
11
  ControlPlaneConfig,
11
12
  IntersectBaseCapabilityImplementation,
@@ -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, unparseable: PydanticUnparsableInner) -> None: ...
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 'unparseable' type annotation" in caplog.text
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 evalutated exactly like it is as a root type
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 ambigous and would resolve to "Any"
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 capabilitiy may have an @intersect_status function' in caplog.text
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():
@@ -14,6 +14,7 @@ import datetime
14
14
  from uuid import uuid4
15
15
 
16
16
  import pytest
17
+
17
18
  from intersect_sdk import (
18
19
  IntersectDataHandler,
19
20
  IntersectMimeType,
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
  ]