Hello world¶
This is a basic example on how to communicate with the INTERSECT SDK. One Service and one Client are deployed. The client sends a message with a string payload to the service, which responds with a string telling the string payload “Hello”.
To run these locally, you must first make sure that you have all necessary backing services running; see the Installation and usage page for details on how to do this.
Afterwards, you can start the service in one terminal, wait a brief moment, then finally run the client in another terminal.
Service walkthrough¶
import logging
from intersect_sdk import (
HierarchyConfig,
IntersectBaseCapabilityImplementation,
IntersectService,
IntersectServiceConfig,
default_intersect_lifecycle_loop,
intersect_message,
intersect_status,
)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class HelloServiceCapabilityImplementation(IntersectBaseCapabilityImplementation):
"""Rudimentary capability implementation example.
All capability implementations are required to have an @intersect_status decorated function,
but we do not use it here.
The operation we are calling is `say_hello_to_name` , so the message being sent will need to have
an operation_id of `say_hello_to_name`. The operation expects a string sent to it in the payload,
and will send a string back in its own payload.
"""
intersect_sdk_capability_name = 'HelloExample'
@intersect_status()
def status(self) -> str:
"""Basic status function which returns a hard-coded string."""
return 'Up'
@intersect_message()
def say_hello_to_name(self, name: str) -> str:
"""Takes in a string parameter and says 'Hello' to the parameter!"""
return f'Hello, {name}!'
if __name__ == '__main__':
"""
step one: create configuration class, which handles validation - see the IntersectServiceConfig class documentation for more info
In most cases, everything under from_config_file should come from a configuration file, command line arguments, or environment variables.
"""
from_config_file = {
'brokers': [
{
'username': 'intersect_username',
'password': 'intersect_password',
'port': 1883,
'protocol': 'mqtt5.0',
},
],
}
config = IntersectServiceConfig(
hierarchy=HierarchyConfig(
organization='hello-organization',
facility='hello-facility',
system='hello-system',
subsystem='hello-subsystem',
service='hello-service',
),
**from_config_file,
)
"""
step two - create your own capability implementation class.
You have complete control over how you construct this class, as long as it has decorated functions with
@intersect_message and @intersect_status, and that these functions are appropriately type-annotated.
"""
capability = HelloServiceCapabilityImplementation()
"""
step three - create service from both the configuration and your own capability
"""
service = IntersectService([capability], config)
"""
step four - start lifecycle loop. The only necessary parameter is your service.
with certain applications (i.e. REST APIs) you'll want to integrate the service in the existing lifecycle,
instead of using this one.
In that case, just be sure to call service.startup() and service.shutdown() at appropriate stages.
"""
logger.info('Starting hello_service, use Ctrl+C to exit.')
default_intersect_lifecycle_loop(
service,
)
"""
Note that the service will run forever until you explicitly kill the application (i.e. Ctrl+C)
"""
Client walkthrough¶
import logging
from intersect_sdk import (
INTERSECT_RESPONSE_VALUE,
IntersectClient,
IntersectClientCallback,
IntersectClientConfig,
IntersectDirectMessageParams,
default_intersect_lifecycle_loop,
)
logging.basicConfig(level=logging.INFO)
def simple_client_callback(
_source: str, _operation: str, _has_error: bool, payload: INTERSECT_RESPONSE_VALUE
) -> None:
"""This simply prints the response from the service to your console.
As we don't want to engage in a back-and-forth, we simply throw an exception to break out of the message loop.
Ways to continue listening to messages or sending messages will be explored in other examples.
Params:
_source: the source of the response message. In this case it will always be from the hello_service.
_operation: the name of the function we called in the original message. In this case it will always be "say_hello_to_name".
_has_error: Boolean value which represents an error. Since there is never an error in this example, it will always be "False".
payload: Value of the response from the Service. The typing of the payload varies, based on the operation called and whether or not
_has_error was set to "True". In this case, since we do not have an error, we can defer to the operation's response type. This response type is
"str", so the type will be "str". The value will always be "Hello, hello_client!".
Note that the payload will always be a deserialized Python object, but the types are fairly limited: str, bool, float, int, None, List[T], and Dict[str, T]
are the only types the payload can have. "T" in this case can be any of the 7 types just mentioned.
"""
print(payload)
# raise exception to break out of message loop - we only send and wait for one message
raise Exception
if __name__ == '__main__':
"""
step one: create configuration class, which handles user input validation - see the IntersectClientConfig class documentation for more info
In most cases, everything under from_config_file should come from a configuration file, command line arguments, or environment variables.
"""
from_config_file = {
'brokers': [
{
'username': 'intersect_username',
'password': 'intersect_password',
'port': 1883,
'protocol': 'mqtt5.0',
},
],
}
"""
step two: construct the initial messages you want to send. In this case we will only send a single starting message.
- The destination should match info.title in the service's schema. Another way to look at this is to see the service's
HierarchyConfig, and then write it as a single string: <ORGANIZATION>.<FACILITY>.<SYSTEM>.<SUBSYSTEM>.<SERVICE> .
If you don't have a subsystem, use a "-" character instead.
- The operation should match one of the channels listed in the schema. In this case, 'say_hello_to_name' is the only
operation exposed in the service.
- The payload should represent what you want to send to the service's operation. You can determine the valid format
from the service's schema. In this case, we're sending it a simple string. As long as the payload is a string,
you'll get a message back.
"""
initial_messages = [
IntersectDirectMessageParams(
destination='hello-organization.hello-facility.hello-system.hello-subsystem.hello-service',
operation='HelloExample.say_hello_to_name',
payload='hello_client',
)
]
config = IntersectClientConfig(
initial_message_event_config=IntersectClientCallback(messages_to_send=initial_messages),
**from_config_file,
)
"""
step three: create the client.
We also need a callback to handle incoming user messages.
"""
client = IntersectClient(
config=config,
user_callback=simple_client_callback,
)
"""
step four - start lifecycle loop. The only necessary parameter is your client.
with certain applications (i.e. REST APIs) you'll want to integrate the client in the existing lifecycle,
instead of using this one.
In that case, just be sure to call client.startup() and client.shutdown() at appropriate stages.
"""
default_intersect_lifecycle_loop(
client,
)
"""
When running the loop, you should have 'Hello, hello_client!' printed to your console.
Note that the client will automatically exit.
"""
Running it yourself: Using a Python environment¶
# to suppress progress messages and only show stdout, you can add
# 2>/dev/null
# to the end of your command on UNIX systems
# First, run the service
python -m examples.1_hello_world.hello_service
# Next, run the client in a separate terminal
python -m examples.1_hello_world.hello_client
After several seconds, the output from hello_client will be:
Hello, hello_client!
The client will exit automatically; you will have to use Ctrl+C on the terminal running the service process.
Running it yourself: Using Docker¶
First, you will need to build the latest INTERSECT-SDK image; run the following command from the repository root:
docker build -t intersect-sdk .
Then you can run the examples like this:
# to suppress progress messages and only show stdout, you can add
# 2>/dev/null
# to the end of your command on UNIX systems
# First, run the service
docker run --rm -it --name intersect-service --network host intersect-sdk python -m examples.1_hello_world.hello_service
# Next, run the client in a separate terminal
docker run --rm -it --name intersect-client --network host intersect-sdk python -m examples.1_hello_world.hello_client
After several seconds, the output from hello_client will be:
Hello, hello_client!
The client will exit automatically; you will have to use Ctrl+C on the terminal running the service process.
(If you ran Docker in detached mode (-d), you can instead run docker stop intersect-client.)