intersect-sdk 0.7.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 (76) hide show
  1. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/PKG-INFO +11 -3
  2. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/README.md +4 -0
  3. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/pyproject.toml +56 -39
  4. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/__init__.py +20 -18
  5. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/brokers/amqp_client.py +1 -1
  6. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/brokers/mqtt_client.py +1 -1
  7. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/control_plane_manager.py +1 -1
  8. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/data_plane/data_plane_manager.py +1 -1
  9. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/event_metadata.py +1 -1
  10. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/interfaces.py +23 -7
  11. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/messages/userspace.py +3 -3
  12. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/schema.py +57 -39
  13. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/app_lifecycle.py +1 -1
  14. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/capability/base.py +48 -18
  15. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/client.py +5 -50
  16. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/client_callback_definitions.py +3 -19
  17. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/config/shared.py +2 -1
  18. intersect_sdk-0.8.1/src/intersect_sdk/schema.py +70 -0
  19. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/service.py +240 -87
  20. intersect_sdk-0.8.1/src/intersect_sdk/service_callback_definitions.py +24 -0
  21. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/version.py +1 -1
  22. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/tests/e2e/test_examples.py +14 -3
  23. intersect_sdk-0.8.1/tests/fixtures/example_schema.json +1067 -0
  24. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/tests/fixtures/example_schema.py +14 -12
  25. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/tests/integration/test_return_type_mismatch.py +2 -2
  26. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/tests/integration/test_service.py +0 -1
  27. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/tests/unit/test_annotations.py +3 -2
  28. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/tests/unit/test_base_capability_implementation.py +11 -2
  29. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/tests/unit/test_config.py +2 -1
  30. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/tests/unit/test_invalid_schema_runtime.py +4 -1
  31. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/tests/unit/test_lifecycle_message.py +2 -1
  32. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/tests/unit/test_schema_invalids.py +242 -58
  33. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/tests/unit/test_schema_valid.py +8 -11
  34. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/tests/unit/test_userspace_message.py +2 -1
  35. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/tests/unit/test_version_resolver.py +1 -0
  36. intersect_sdk-0.7.0/src/intersect_sdk/schema.py +0 -78
  37. intersect_sdk-0.7.0/src/intersect_sdk/service_callback_definitions.py +0 -16
  38. intersect_sdk-0.7.0/tests/fixtures/example_schema.json +0 -1062
  39. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/LICENSE +0 -0
  40. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/__init__.py +0 -0
  41. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/constants.py +0 -0
  42. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/__init__.py +0 -0
  43. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/brokers/__init__.py +0 -0
  44. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/brokers/broker_client.py +0 -0
  45. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/discovery_service.py +0 -0
  46. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/control_plane/topic_handler.py +0 -0
  47. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/data_plane/__init__.py +0 -0
  48. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/data_plane/minio_utils.py +0 -0
  49. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/exceptions.py +0 -0
  50. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/function_metadata.py +0 -0
  51. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/logger.py +0 -0
  52. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/messages/__init__.py +0 -0
  53. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/messages/event.py +0 -0
  54. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/messages/lifecycle.py +0 -0
  55. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/multi_flag_thread_event.py +0 -0
  56. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/pydantic_schema_generator.py +0 -0
  57. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/stoppable_thread.py +0 -0
  58. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/utils.py +0 -0
  59. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/version.py +0 -0
  60. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/_internal/version_resolver.py +0 -0
  61. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/capability/__init__.py +0 -0
  62. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/config/__init__.py +0 -0
  63. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/config/client.py +0 -0
  64. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/config/service.py +0 -0
  65. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/constants.py +0 -0
  66. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/core_definitions.py +0 -0
  67. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/py.typed +0 -0
  68. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/service_definitions.py +0 -0
  69. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/src/intersect_sdk/shared_callback_definitions.py +0 -0
  70. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/tests/__init__.py +0 -0
  71. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/tests/conftest.py +0 -0
  72. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/tests/e2e/__init__.py +0 -0
  73. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/tests/fixtures/__init__.py +0 -0
  74. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/tests/fixtures/return_type_mismatch.py +0 -0
  75. {intersect_sdk-0.7.0 → intersect_sdk-0.8.1}/tests/integration/__init__.py +0 -0
  76. {intersect_sdk-0.7.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.7.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>
@@ -10,6 +10,10 @@ Classifier: Programming Language :: Python :: 3.8
10
10
  Classifier: Programming Language :: Python :: 3.9
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
+ Project-URL: Homepage, https://github.com/INTERSECT-SDK/python-sdk/
14
+ Project-URL: Changelog, https://github.com/INTERSECT-SDK/python-sdk/blob/main/CHANGELOG.md
15
+ Project-URL: Documentation, https://intersect-python-sdk.readthedocs.io/en/latest/
16
+ Project-URL: Issues, https://github.com/INTERSECT-SDK/python-sdk/issues
13
17
  Requires-Python: <4.0,>=3.8.10
14
18
  Requires-Dist: pydantic>=2.7.0
15
19
  Requires-Dist: retrying<2.0.0,>=1.3.4
@@ -17,13 +21,15 @@ Requires-Dist: paho-mqtt<2.0.0,>=1.6.1
17
21
  Requires-Dist: minio>=7.2.3
18
22
  Requires-Dist: jsonschema[format-nongpl]>=4.21.1
19
23
  Requires-Dist: eval-type-backport>=0.1.3; python_version < "3.10"
24
+ Provides-Extra: amqp
20
25
  Requires-Dist: pika<2.0.0,>=1.3.2; extra == "amqp"
26
+ Provides-Extra: docs
21
27
  Requires-Dist: sphinx>=5.3.0; extra == "docs"
22
28
  Requires-Dist: furo>=2023.3.27; extra == "docs"
23
- Provides-Extra: amqp
24
- Provides-Extra: docs
25
29
  Description-Content-Type: text/markdown
26
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
+
27
33
  # INTERSECT-SDK
28
34
 
29
35
  The INTERSECT-SDK is a framework for microservices to integrate themselves into the wider Interconnected Science Ecosystem (INTERSECT).
@@ -33,6 +39,8 @@ Please note that this README is currently a work in progress.
33
39
  ## What is INTERSECT?
34
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.
35
41
 
42
+ For a high-level overview, please see [the architecture website.](https://intersect-architecture.readthedocs.io/en/latest/)
43
+
36
44
  ## What are the core design philosophies of the SDK?
37
45
 
38
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,11 +35,17 @@ 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.7.0"
38
+ version = "0.8.1"
39
39
 
40
40
  [project.license]
41
41
  text = "BSD-3-Clause"
42
42
 
43
+ [project.urls]
44
+ Homepage = "https://github.com/INTERSECT-SDK/python-sdk/"
45
+ Changelog = "https://github.com/INTERSECT-SDK/python-sdk/blob/main/CHANGELOG.md"
46
+ Documentation = "https://intersect-python-sdk.readthedocs.io/en/latest/"
47
+ Issues = "https://github.com/INTERSECT-SDK/python-sdk/issues"
48
+
43
49
  [project.optional-dependencies]
44
50
  amqp = [
45
51
  "pika>=1.3.2,<2.0.0",
@@ -49,42 +55,6 @@ docs = [
49
55
  "furo>=2023.3.27",
50
56
  ]
51
57
 
52
- [tool.pdm.dev-dependencies]
53
- lint = [
54
- "pre-commit>=3.3.1",
55
- "ruff>=0.4.2",
56
- "mypy>=1.10.0",
57
- "types-paho-mqtt>=1.6.0.20240106",
58
- ]
59
- test = [
60
- "pytest>=7.3.2",
61
- "pytest-cov>=4.1.0",
62
- "httpretty>=1.1.4",
63
- ]
64
-
65
- [tool.pdm.scripts]
66
- 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"
67
- 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"
68
- test-unit = "pytest tests/unit --cov=src/intersect_sdk/"
69
- test-e2e = "pytest tests/e2e --cov=src/intersect_sdk/"
70
- lint-format = "ruff format"
71
- lint-ruff = "ruff check --fix"
72
- lint-mypy = "mypy src/intersect_sdk/"
73
-
74
- [tool.pdm.scripts.lint]
75
- composite = [
76
- "lint-format",
77
- "lint-ruff",
78
- "lint-mypy",
79
- ]
80
-
81
- [tool.pdm.build]
82
- package-dir = "src"
83
-
84
- [tool.pdm.version]
85
- source = "file"
86
- path = "src/intersect_sdk/version.py"
87
-
88
58
  [tool.ruff]
89
59
  line-length = 100
90
60
 
@@ -138,8 +108,6 @@ ignore = [
138
108
  "COM812",
139
109
  "ISC001",
140
110
  "SIM105",
141
- "ANN101",
142
- "ANN102",
143
111
  "ANN401",
144
112
  "PLR2004",
145
113
  ]
@@ -214,13 +182,62 @@ addopts = "-ra"
214
182
  [tool.coverage.report]
215
183
  omit = [
216
184
  "*__init__*",
185
+ "*/discovery_service.py",
217
186
  ]
218
187
  exclude_also = [
219
188
  "pragma: no-cover",
220
189
  "if (typing\\\\.)?TYPE_CHECKING:",
221
190
  "@(abc\\\\.)?abstractmethod",
191
+ "class .*\\bProtocol\\):",
192
+ "raise NotImplementedError",
193
+ "except.* ImportError",
222
194
  ]
223
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
+
224
241
  [build-system]
225
242
  requires = [
226
243
  "pdm-backend",
@@ -19,12 +19,13 @@ from .config.client import IntersectClientConfig
19
19
  from .config.service import IntersectServiceConfig
20
20
  from .config.shared import (
21
21
  ControlPlaneConfig,
22
+ ControlProvider,
22
23
  DataStoreConfig,
23
24
  DataStoreConfigMap,
24
25
  HierarchyConfig,
25
26
  )
26
27
  from .core_definitions import IntersectDataHandler, IntersectMimeType
27
- from .schema import get_schema_from_capability_implementation
28
+ from .schema import get_schema_from_capability_implementations
28
29
  from .service import IntersectService
29
30
  from .service_callback_definitions import (
30
31
  INTERSECT_SERVICE_RESPONSE_CALLBACK_TYPE,
@@ -42,30 +43,31 @@ from .shared_callback_definitions import (
42
43
  from .version import __version__, version_info, version_string
43
44
 
44
45
  __all__ = [
45
- 'IntersectDataHandler',
46
- 'IntersectEventDefinition',
47
- 'IntersectMimeType',
48
- 'intersect_event',
49
- 'intersect_message',
50
- 'intersect_status',
51
- 'get_schema_from_capability_implementation',
52
- 'IntersectService',
53
- 'IntersectClient',
54
- 'IntersectClientCallback',
55
- 'IntersectDirectMessageParams',
56
- 'INTERSECT_CLIENT_RESPONSE_CALLBACK_TYPE',
57
46
  'INTERSECT_CLIENT_EVENT_CALLBACK_TYPE',
47
+ 'INTERSECT_CLIENT_RESPONSE_CALLBACK_TYPE',
58
48
  'INTERSECT_JSON_VALUE',
59
49
  'INTERSECT_SERVICE_RESPONSE_CALLBACK_TYPE',
60
- 'IntersectBaseCapabilityImplementation',
61
- 'default_intersect_lifecycle_loop',
62
- 'IntersectClientConfig',
63
- 'IntersectServiceConfig',
64
- 'HierarchyConfig',
65
50
  'ControlPlaneConfig',
51
+ 'ControlProvider',
66
52
  'DataStoreConfig',
67
53
  'DataStoreConfigMap',
54
+ 'HierarchyConfig',
55
+ 'IntersectBaseCapabilityImplementation',
56
+ 'IntersectClient',
57
+ 'IntersectClientCallback',
58
+ 'IntersectClientConfig',
59
+ 'IntersectDataHandler',
60
+ 'IntersectDirectMessageParams',
61
+ 'IntersectEventDefinition',
62
+ 'IntersectMimeType',
63
+ 'IntersectService',
64
+ 'IntersectServiceConfig',
68
65
  '__version__',
66
+ 'default_intersect_lifecycle_loop',
67
+ 'get_schema_from_capability_implementations',
68
+ 'intersect_event',
69
+ 'intersect_message',
70
+ 'intersect_status',
69
71
  'version_info',
70
72
  'version_string',
71
73
  ]
@@ -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)
@@ -66,7 +66,7 @@ class ControlPlaneManager:
66
66
  """
67
67
  if control_configs == 'discovery':
68
68
  msg = 'Discovery service not implemented yet'
69
- raise ValueError(msg)
69
+ raise NotImplementedError(msg)
70
70
  self._control_providers = [
71
71
  create_control_provider(config, self.get_subscription_channels)
72
72
  for config in control_configs
@@ -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,19 +31,35 @@ 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,
38
+ timeout: float = 300.0,
39
39
  ) -> UUID:
40
- """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.
41
41
 
42
42
  Params:
43
- - 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
44
44
  - response_handler: optional callback for how we want to handle the response from this request.
45
+ - timeout: optional value for how long we should wait on the request, in seconds (default: 300 seconds)
45
46
 
46
47
  Returns:
47
48
  - generated RequestID associated with your request
48
49
  """
49
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):
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import inspect
6
+ import re
6
7
  from enum import Enum
7
8
  from typing import (
8
9
  TYPE_CHECKING,
@@ -55,6 +56,9 @@ For networking types, https://docs.pydantic.dev/latest/api/networks/
55
56
  For a complete reference, https://docs.pydantic.dev/latest/concepts/conversion_table
56
57
  """
57
58
 
59
+ CAPABILITY_NAME_PATTERN = r'[\w-]+'
60
+ """Regular expression we use to check valid capability names. Since capability namespacing only occurs in services, we can be more lax than for how we name services/systems/etc. """
61
+
58
62
 
59
63
  class _FunctionAnalysisResult(NamedTuple):
60
64
  """private class generated from static analysis of function."""
@@ -302,7 +306,7 @@ def _add_events(
302
306
 
303
307
 
304
308
  def _introspection_baseline(
305
- capability: IntersectBaseCapabilityImplementation,
309
+ capability: type[IntersectBaseCapabilityImplementation],
306
310
  excluded_data_handlers: set[IntersectDataHandler],
307
311
  ) -> tuple[
308
312
  dict[Any, Any], # $defs for schemas (common)
@@ -332,8 +336,9 @@ def _introspection_baseline(
332
336
  function_map = {}
333
337
  event_metadatas: dict[str, EventMetadata] = {}
334
338
 
335
- cap_name = capability.capability_name
336
- status_func, response_funcs, event_funcs = _get_functions(type(capability))
339
+ # capability_name should have already been checked before calling this function
340
+ cap_name = capability.intersect_sdk_capability_name
341
+ status_func, response_funcs, event_funcs = _get_functions(capability)
337
342
 
338
343
  # parse functions
339
344
  for class_name, name, method, min_params in response_funcs:
@@ -439,7 +444,7 @@ def _introspection_baseline(
439
444
 
440
445
  # final function mapping
441
446
  function_map[public_name] = FunctionMetadata(
442
- type(capability),
447
+ capability,
443
448
  method,
444
449
  function_cache_request_adapter,
445
450
  function_cache_response_adapter,
@@ -477,7 +482,7 @@ def _introspection_baseline(
477
482
  if status_fn_type_adapter and status_fn and status_fn_name:
478
483
  public_status_name = f'{cap_name}.{status_fn_name}'
479
484
  function_map[public_status_name] = FunctionMetadata(
480
- type(capability),
485
+ capability,
481
486
  status_fn,
482
487
  None,
483
488
  status_fn_type_adapter,
@@ -494,52 +499,78 @@ def _introspection_baseline(
494
499
 
495
500
 
496
501
  def get_schema_and_functions_from_capability_implementations(
497
- capabilities: list[IntersectBaseCapabilityImplementation],
502
+ capabilities: list[type[IntersectBaseCapabilityImplementation]],
498
503
  service_name: HierarchyConfig,
499
504
  excluded_data_handlers: set[IntersectDataHandler],
500
505
  ) -> tuple[
501
506
  dict[str, Any],
502
507
  dict[str, FunctionMetadata],
503
508
  dict[str, EventMetadata],
504
- IntersectBaseCapabilityImplementation | None,
509
+ type[IntersectBaseCapabilityImplementation] | None,
505
510
  str | None,
506
511
  TypeAdapter[Any] | None,
507
512
  ]:
508
513
  """This function generates the core AsyncAPI schema, and the core mappings which are derived from the schema.
509
514
 
515
+ Importantly, this function needs to be able to work with static classes, and not instances. This is because users
516
+ should be free to define their constructor as they wish, with any arbitrary parameters. Users should also be allowed
517
+ to execute whatever code they'd like in their constructor, such as establishing remote connections. At the same time,
518
+ we want to allow users to quickly generate an INTERSECT schema, without having to worry about any dependencies from their constructor code.
519
+
510
520
  In-depth introspection is handled later on.
511
521
  """
512
- capability_type_docs: str = ''
513
- status_function_cap: IntersectBaseCapabilityImplementation | None = None
522
+ status_function_cap: type[IntersectBaseCapabilityImplementation] | None = None
514
523
  status_function_name: str | None = None
515
524
  status_function_schema: dict[str, Any] | None = None
516
525
  status_function_adapter: TypeAdapter[Any] | None = None
517
- schemas: dict[Any, Any] = {}
518
- channels: dict[str, dict[str, dict[str, Any]]] = {} # endpoint schemas
526
+ shared_schemas: dict[Any, Any] = {} # "shared" schemas which get put in $defs
527
+ capability_schemas: dict[str, Any] = {} # endpoint schemas
519
528
  function_map: dict[str, FunctionMetadata] = {} # endpoint functionality
520
- events: dict[str, Any] = {} # event schemas
529
+ events: dict[
530
+ str, Any
531
+ ] = {} # event schemas - TODO event names are currently "global" across capabilities, may want to change this?
521
532
  event_map: dict[str, EventMetadata] = {} # event functionality
522
- for capability in capabilities:
523
- capability_type: type[IntersectBaseCapabilityImplementation] = type(capability)
524
- if capability_type.__doc__:
525
- capability_type_docs += inspect.cleandoc(capability_type.__doc__) + '\n'
533
+ for capability_type in capabilities:
534
+ cap_name = capability_type.intersect_sdk_capability_name
535
+ if (
536
+ not cap_name
537
+ or not isinstance(cap_name, str)
538
+ or not re.fullmatch(CAPABILITY_NAME_PATTERN, cap_name)
539
+ ):
540
+ die(
541
+ f'Invalid intersect_sdk_capability_name on capability {capability_type.__name__} - must be a non-empty string with only alphanumeric characters and hyphens (you must explicitly set this, and do so on the class and not an instance).'
542
+ )
543
+ if cap_name in capability_schemas:
544
+ die(
545
+ f'Invalid intersect_sdk_capability_name on capability {capability_type.__name__} - value "{cap_name}" is a duplicate and has already appeared in another capability.'
546
+ )
547
+
526
548
  (
527
- cap_schemas,
549
+ subschemas,
528
550
  (cap_status_fn_name, cap_status_schema, cap_status_type_adapter),
529
- cap_channels,
551
+ cap_functions,
530
552
  cap_function_map,
531
553
  cap_events,
532
554
  cap_event_map,
533
- ) = _introspection_baseline(capability, excluded_data_handlers)
555
+ ) = _introspection_baseline(capability_type, excluded_data_handlers)
534
556
 
535
557
  if cap_status_fn_name and cap_status_schema and cap_status_type_adapter:
536
- status_function_cap = capability
558
+ if status_function_name is not None:
559
+ # TODO may want to change this later
560
+ die('Only one capability may have an @intersect_status function')
561
+ status_function_cap = capability_type
537
562
  status_function_name = cap_status_fn_name
538
563
  status_function_schema = cap_status_schema
539
564
  status_function_adapter = cap_status_type_adapter
540
565
 
541
- schemas.update(cap_schemas)
542
- channels.update(cap_channels)
566
+ shared_schemas.update(subschemas)
567
+ # NOTE: we will still add the capability to the schema, even if there are no @intersect_message annotations
568
+ capability_schemas[cap_name] = {
569
+ 'channels': cap_functions,
570
+ }
571
+ # add documentation for the capabilities
572
+ if capability_type.__doc__:
573
+ capability_schemas[cap_name]['description'] = inspect.cleandoc(capability_type.__doc__)
543
574
  function_map.update(cap_function_map)
544
575
  events.update(cap_events)
545
576
  event_map.update(cap_event_map)
@@ -549,15 +580,17 @@ def get_schema_and_functions_from_capability_implementations(
549
580
  'x-intersect-version': version_string,
550
581
  'info': {
551
582
  'title': service_name.hierarchy_string('.'),
583
+ 'description': 'INTERSECT schema',
552
584
  'version': '0.0.0', # NOTE: this will be modified by INTERSECT CORE, users do not manage their schema versions
553
585
  },
554
586
  # applies to how an incoming message payload will be parsed.
555
587
  # can be changed per channel
556
588
  'defaultContentType': 'application/json',
557
- 'channels': channels,
589
+ 'capabilities': capability_schemas,
558
590
  'events': events,
591
+ 'status': status_function_schema if status_function_schema else {'type': 'null'},
559
592
  'components': {
560
- 'schemas': schemas,
593
+ 'schemas': shared_schemas,
561
594
  'messageTraits': {
562
595
  # this is where we can define our message headers
563
596
  'commonHeaders': {
@@ -574,21 +607,6 @@ def get_schema_and_functions_from_capability_implementations(
574
607
  },
575
608
  }
576
609
 
577
- if capability_type_docs != '':
578
- asyncapi_spec['info']['description'] = capability_type_docs # type: ignore[index]
579
-
580
- if status_function_schema:
581
- asyncapi_spec['status'] = status_function_schema
582
-
583
- """
584
- TODO - might want to include these fields
585
- "securitySchemes": {},
586
- "operationTraits": {},
587
- "externalDocumentation": {
588
- "url": "https://example.com", # REQUIRED
589
- },
590
- """
591
-
592
610
  return (
593
611
  asyncapi_spec,
594
612
  function_map,
@@ -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()