Skip to content

Commit 47d4333

Browse files
committed
add functionality for capability to send out messages, rename 'IntersectClientMessageParams' to 'DirectMessageParams'
Signed-off-by: Lance Drane <[email protected]>
1 parent 966db98 commit 47d4333

File tree

14 files changed

+287
-84
lines changed

14 files changed

+287
-84
lines changed

examples/1_hello_world/hello_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
from intersect_sdk import (
44
INTERSECT_JSON_VALUE,
5+
DirectMessageParams,
56
IntersectClient,
67
IntersectClientCallback,
78
IntersectClientConfig,
8-
IntersectClientMessageParams,
99
default_intersect_lifecycle_loop,
1010
)
1111

@@ -74,7 +74,7 @@ def simple_client_callback(
7474
you'll get a message back.
7575
"""
7676
initial_messages = [
77-
IntersectClientMessageParams(
77+
DirectMessageParams(
7878
destination='hello-organization.hello-facility.hello-system.hello-subsystem.hello-service',
7979
operation='HelloExample.say_hello_to_name',
8080
payload='hello_client',

examples/1_hello_world_amqp/hello_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
from intersect_sdk import (
44
INTERSECT_JSON_VALUE,
5+
DirectMessageParams,
56
IntersectClient,
67
IntersectClientCallback,
78
IntersectClientConfig,
8-
IntersectClientMessageParams,
99
default_intersect_lifecycle_loop,
1010
)
1111

@@ -76,7 +76,7 @@ def simple_client_callback(
7676
you'll get a message back.
7777
"""
7878
initial_messages = [
79-
IntersectClientMessageParams(
79+
DirectMessageParams(
8080
destination='hello-organization.hello-facility.hello-system.hello-subsystem.hello-service',
8181
operation='HelloExample.say_hello_to_name',
8282
payload='hello_client',

examples/1_hello_world_events/hello_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
from intersect_sdk import (
44
INTERSECT_JSON_VALUE,
5+
DirectMessageParams,
56
IntersectClient,
67
IntersectClientCallback,
78
IntersectClientConfig,
8-
IntersectClientMessageParams,
99
default_intersect_lifecycle_loop,
1010
)
1111

@@ -95,7 +95,7 @@ def simple_event_callback(
9595
you'll get a message back.
9696
"""
9797
initial_messages = [
98-
IntersectClientMessageParams(
98+
DirectMessageParams(
9999
destination='hello-organization.hello-facility.hello-system.hello-subsystem.hello-service',
100100
operation='HelloExample.say_hello_to_name',
101101
payload='hello_client',

examples/2_counting/counting_client.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
from intersect_sdk import (
66
INTERSECT_JSON_VALUE,
7+
DirectMessageParams,
78
IntersectClient,
89
IntersectClientCallback,
910
IntersectClientConfig,
10-
IntersectClientMessageParams,
1111
default_intersect_lifecycle_loop,
1212
)
1313

@@ -38,7 +38,7 @@ def __init__(self) -> None:
3838
self.message_stack = [
3939
# wait 5 seconds before stopping the counter. "Count" in response will be approx. 6
4040
(
41-
IntersectClientMessageParams(
41+
DirectMessageParams(
4242
destination='counting-organization.counting-facility.counting-system.counting-subsystem.counting-service',
4343
operation='CountingExample.stop_count',
4444
payload=None,
@@ -47,7 +47,7 @@ def __init__(self) -> None:
4747
),
4848
# start the counter up again - it will not be 0 at this point! "Count" in response will be approx. 7
4949
(
50-
IntersectClientMessageParams(
50+
DirectMessageParams(
5151
destination='counting-organization.counting-facility.counting-system.counting-subsystem.counting-service',
5252
operation='CountingExample.start_count',
5353
payload=None,
@@ -56,7 +56,7 @@ def __init__(self) -> None:
5656
),
5757
# reset the counter, but have it immediately start running again. "Count" in response will be approx. 10
5858
(
59-
IntersectClientMessageParams(
59+
DirectMessageParams(
6060
destination='counting-organization.counting-facility.counting-system.counting-subsystem.counting-service',
6161
operation='CountingExample.reset_count',
6262
payload=True,
@@ -65,7 +65,7 @@ def __init__(self) -> None:
6565
),
6666
# reset the counter, but don't have it run again. "Count" in response will be approx. 6
6767
(
68-
IntersectClientMessageParams(
68+
DirectMessageParams(
6969
destination='counting-organization.counting-facility.counting-system.counting-subsystem.counting-service',
7070
operation='CountingExample.reset_count',
7171
payload=False,
@@ -74,7 +74,7 @@ def __init__(self) -> None:
7474
),
7575
# start the counter back up. "Count" in response will be approx. 1
7676
(
77-
IntersectClientMessageParams(
77+
DirectMessageParams(
7878
destination='counting-organization.counting-facility.counting-system.counting-subsystem.counting-service',
7979
operation='CountingExample.start_count',
8080
payload=None,
@@ -83,7 +83,7 @@ def __init__(self) -> None:
8383
),
8484
# finally, stop the counter one last time. "Count" in response will be approx. 4
8585
(
86-
IntersectClientMessageParams(
86+
DirectMessageParams(
8787
destination='counting-organization.counting-facility.counting-system.counting-subsystem.counting-service',
8888
operation='CountingExample.stop_count',
8989
payload=None,
@@ -158,7 +158,7 @@ def client_callback(
158158
# The counter will start after the initial message.
159159
# If the service is already active and counting, this may do nothing.
160160
initial_messages = [
161-
IntersectClientMessageParams(
161+
DirectMessageParams(
162162
destination='counting-organization.counting-facility.counting-system.counting-subsystem.counting-service',
163163
operation='CountingExample.start_count',
164164
payload=None,

src/intersect_sdk/__init__.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@
1313
from .client_callback_definitions import (
1414
INTERSECT_CLIENT_EVENT_CALLBACK_TYPE,
1515
INTERSECT_CLIENT_RESPONSE_CALLBACK_TYPE,
16-
INTERSECT_JSON_VALUE,
1716
IntersectClientCallback,
18-
IntersectClientMessageParams,
1917
)
2018
from .config.client import IntersectClientConfig
2119
from .config.service import IntersectServiceConfig
@@ -28,12 +26,19 @@
2826
from .core_definitions import IntersectDataHandler, IntersectMimeType
2927
from .schema import get_schema_from_capability_implementation
3028
from .service import IntersectService
29+
from .service_callback_definitions import (
30+
INTERSECT_SERVICE_RESPONSE_CALLBACK_TYPE,
31+
)
3132
from .service_definitions import (
3233
IntersectEventDefinition,
3334
intersect_event,
3435
intersect_message,
3536
intersect_status,
3637
)
38+
from .shared_callback_definitions import (
39+
INTERSECT_JSON_VALUE,
40+
DirectMessageParams,
41+
)
3742
from .version import __version__, version_info, version_string
3843

3944
__all__ = [
@@ -47,10 +52,11 @@
4752
'IntersectService',
4853
'IntersectClient',
4954
'IntersectClientCallback',
50-
'IntersectClientMessageParams',
55+
'DirectMessageParams',
5156
'INTERSECT_CLIENT_RESPONSE_CALLBACK_TYPE',
5257
'INTERSECT_CLIENT_EVENT_CALLBACK_TYPE',
5358
'INTERSECT_JSON_VALUE',
59+
'INTERSECT_SERVICE_RESPONSE_CALLBACK_TYPE',
5460
'IntersectBaseCapabilityImplementation',
5561
'default_intersect_lifecycle_loop',
5662
'IntersectClientConfig',

src/intersect_sdk/_internal/interfaces.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
1+
from __future__ import annotations
2+
13
from abc import ABC, abstractmethod
2-
from typing import Any
4+
from typing import TYPE_CHECKING, Any
5+
6+
if TYPE_CHECKING:
7+
from uuid import UUID
8+
9+
from ..service_callback_definitions import (
10+
INTERSECT_SERVICE_RESPONSE_CALLBACK_TYPE,
11+
)
12+
from ..shared_callback_definitions import (
13+
DirectMessageParams,
14+
)
315

416

517
class IntersectEventObserver(ABC):
@@ -18,3 +30,20 @@ def _on_observe_event(self, event_name: str, event_value: Any, operation: str) -
1830
operation: The source of the event (generally the function name, not directly invoked by application devs)
1931
"""
2032
...
33+
34+
@abstractmethod
35+
def create_external_request(
36+
self,
37+
request: DirectMessageParams,
38+
response_handler: INTERSECT_SERVICE_RESPONSE_CALLBACK_TYPE | None = None,
39+
) -> UUID:
40+
"""Observed entity (capabilitiy) tells observer (i.e. service) to send an external request.
41+
42+
Params:
43+
- request: the request we want to send out, encapsulated as an IntersectClientMessageParams object
44+
- response_handler: optional callback for how we want to handle the response from this request.
45+
46+
Returns:
47+
- generated RequestID associated with your request
48+
"""
49+
...

src/intersect_sdk/_internal/messages/userspace.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
33
This module is internal-facing and should not be used directly by users.
44
5+
Services have two associated channels which handle userspace messages: their request channel
6+
and their response channel. Services always CONSUME messages from these channels, but never PRODUCE messages
7+
on these channels. (A message is always sent in the receiver's namespace).
8+
9+
The response channel is how the service handles external requests, the request channel is used when this service itself
10+
needs to make external requests through INTERSECT.
11+
512
Services should ALWAYS be CONSUMING from their userspace channel.
613
They should NEVER be PRODUCING messages on their userspace channel.
714

src/intersect_sdk/capability/base.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,15 @@
1111
from .._internal.logger import logger
1212

1313
if TYPE_CHECKING:
14+
from uuid import UUID
15+
1416
from .._internal.interfaces import IntersectEventObserver
17+
from ..service_callback_definitions import (
18+
INTERSECT_SERVICE_RESPONSE_CALLBACK_TYPE,
19+
)
20+
from ..shared_callback_definitions import (
21+
DirectMessageParams,
22+
)
1523

1624

1725
class IntersectBaseCapabilityImplementation:
@@ -39,14 +47,20 @@ def __init__(self) -> None:
3947
"""
4048

4149
def __init_subclass__(cls) -> None:
42-
"""This prevents users from overriding a few key functions."""
50+
"""This prevents users from overriding a few key functions.
51+
52+
General rule of thumb is that any function which starts with "intersect_sdk_" is a protected namespace for defining
53+
the INTERSECT-SDK public API between a capability and its observers.
54+
"""
4355
if (
4456
cls._intersect_sdk_register_observer
4557
is not IntersectBaseCapabilityImplementation._intersect_sdk_register_observer
4658
or cls.intersect_sdk_emit_event
4759
is not IntersectBaseCapabilityImplementation.intersect_sdk_emit_event
60+
or cls.intersect_sdk_call_service
61+
is not IntersectBaseCapabilityImplementation.intersect_sdk_call_service
4862
):
49-
msg = f"{cls.__name__}: Cannot override functions '_intersect_sdk_register_observer' or 'intersect_sdk_emit_event'"
63+
msg = f"{cls.__name__}: Attempted to override a reserved INTERSECT-SDK function (don't start your function names with '_intersect_sdk_' or 'intersect_sdk_')"
5064
raise RuntimeError(msg)
5165

5266
@property
@@ -113,3 +127,27 @@ def intersect_sdk_emit_event(self, event_name: str, event_value: Any) -> None:
113127
return
114128
for observer in self.__intersect_sdk_observers__:
115129
observer._on_observe_event(event_name, event_value, annotated_operation) # noqa: SLF001 (private for application devs, NOT for base implementation)
130+
131+
@final
132+
def intersect_sdk_call_service(
133+
self,
134+
request: DirectMessageParams,
135+
response_handler: INTERSECT_SERVICE_RESPONSE_CALLBACK_TYPE | None = None,
136+
) -> list[UUID]:
137+
"""Create an external request that we'll send to a different Service.
138+
139+
Params:
140+
- request: the request we want to send out, encapsulated as an IntersectClientMessageParams object
141+
- response_handler: optional callback for how we want to handle the response from this request.
142+
143+
Returns:
144+
- list of generated RequestIDs associated with your request. Note that for almost all use cases,
145+
this list will have only one associated RequestID.
146+
147+
Raises:
148+
- pydantic.ValidationError - if the request parameter isn't valid
149+
"""
150+
return [
151+
observer.create_external_request(request, response_handler)
152+
for observer in self.__intersect_sdk_observers__
153+
]

src/intersect_sdk/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
from ._internal.version_resolver import resolve_user_version
4141
from .client_callback_definitions import (
4242
IntersectClientCallback,
43-
IntersectClientMessageParams,
4443
)
4544
from .config.client import IntersectClientConfig
4645
from .config.shared import HierarchyConfig
@@ -50,6 +49,7 @@
5049
INTERSECT_CLIENT_EVENT_CALLBACK_TYPE,
5150
INTERSECT_CLIENT_RESPONSE_CALLBACK_TYPE,
5251
)
52+
from .shared_callback_definitions import DirectMessageParams
5353

5454

5555
@final
@@ -425,7 +425,7 @@ def _handle_client_callback(self, user_value: IntersectClientCallback | None) ->
425425
for message in validated_result.messages_to_send:
426426
self._send_userspace_message(message)
427427

428-
def _send_userspace_message(self, params: IntersectClientMessageParams) -> None:
428+
def _send_userspace_message(self, params: DirectMessageParams) -> None:
429429
"""Send a userspace message, be it an initial message from the user or from the user's callback function."""
430430
# ONE: SERIALIZE FUNCTION RESULTS
431431
# (function input should already be validated at this point)

src/intersect_sdk/client_callback_definitions.py

Lines changed: 7 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,15 @@
1-
"""Data types used in regard to client callbacks. Only relevant for Client authors."""
1+
"""Data types used in regard to client callbacks. Only relevant for Client authors.
22
3-
from typing import Any, Callable, Dict, List, Optional, Union
3+
See shared_callback_definitions for additional typings which are also shared by service authors.
4+
"""
5+
6+
from typing import Callable, Dict, List, Optional, Union
47

58
from pydantic import BaseModel, ConfigDict, Field
69
from typing_extensions import Annotated, TypeAlias, final
710

811
from .constants import SYSTEM_OF_SYSTEM_REGEX
9-
from .core_definitions import IntersectDataHandler, IntersectMimeType
10-
11-
12-
class IntersectClientMessageParams(BaseModel):
13-
"""The user implementing the IntersectClient class will need to return this object in order to send a message to another Service."""
14-
15-
destination: Annotated[str, Field(pattern=SYSTEM_OF_SYSTEM_REGEX)]
16-
"""
17-
The destination string. You'll need to know the system-of-system representation of the Service.
18-
19-
Note that this should match what you would see in the schema.
20-
"""
21-
22-
operation: str
23-
"""
24-
The name of the operation you want to call from the Service - this should be represented as it is in the Service's schema.
25-
"""
26-
27-
payload: Any
28-
"""
29-
The raw Python object you want to have serialized as the payload.
30-
31-
If you want to just use the service's default value for a request (assuming it has a default value for a request), you may set this as None.
32-
"""
33-
34-
content_type: IntersectMimeType = IntersectMimeType.JSON
35-
"""
36-
The IntersectMimeType of your message. You'll want this to match with the ContentType of the function from the schema.
37-
38-
default: IntersectMimeType.JSON
39-
"""
40-
41-
data_handler: IntersectDataHandler = IntersectDataHandler.MESSAGE
42-
"""
43-
The IntersectDataHandler you want to use (most people can just use IntersectDataHandler.MESSAGE here, unless your data is very large)
44-
45-
default: IntersectDataHandler.MESSAGE
46-
"""
47-
48-
# pydantic config
49-
model_config = ConfigDict(revalidate_instances='always')
12+
from .shared_callback_definitions import DirectMessageParams
5013

5114

5215
@final
@@ -56,7 +19,7 @@ class IntersectClientCallback(BaseModel):
5619
If you do not return a value of this type (or None), this will be treated as an Exception and will break the pub-sub loop.
5720
"""
5821

59-
messages_to_send: List[IntersectClientMessageParams] = [] # noqa: FA100 (runtime annotation)
22+
messages_to_send: List[DirectMessageParams] = [] # noqa: FA100 (runtime annotation)
6023
"""
6124
Messages to send as a result of an event or a response from a Service.
6225
"""

0 commit comments

Comments
 (0)