ubii.framework.services module¶
- class ubii.framework.services.ServiceConnection¶
Bases:
ABCA ServiceConnection is a two-way request-reply connection. If the connection is present the only defined public API method is supposed to send a message in the ServiceRequest format, await a response from the master node and return it to the caller.
- abstract async send(request: ServiceRequest) ServiceReply¶
Send a ServiceRequest message, and receive a ServiceReply, according to the protobuf specifications. Can be implemented with any communication transports the master node supports.
- Parameters:
request – ServiceRequest protobuf message (wrapper)
- class ubii.framework.services.ServiceCallFactory(*args, **kwargs)¶
Bases:
Protocol[T_Service_Cov]- __call__(mapping: Service) T_Service_Cov¶
ServiceCallFactoryobjects need to have this call signature- Parameters:
mapping – instance of a
ubii.proto.Servicewrapper for a Service message- Returns:
A type of
ServiceCallobject
- class ubii.framework.services.ServiceMap(mapping=None, *, service_call_factory: ServiceCallFactory[T_Service], **kwargs)¶
Bases:
ServiceList,Mapping[str,T_Service],Generic[T_Service]A
ServiceMapis a wrapper around a ServiceList proto message which provides a mapping for theServicemessages by topic.An adapter like this is needed because the master node advertises its services in a
ubii.proto.ServiceListmessage, but the semantics are to access them by topic – typically with the default topics provided by the master node.Additionally, a
ServiceMapconverts the respectiveubii.proto.Servicefor a topic to aServiceCall(the service_call_factory argument can be supplied during creation, it has to be a callable with signature matching aServiceCallFactory, and will be called with theServicemessage as only argument). The results are cached.If you change the protobuf contents of a
ServiceMapobject after creation (e.g. when you get a newServiceListfrom the master node), make sure to invalidate the cache by callingcache_clear(). This is automatically done if you assign to theelementsattribute directly, but if you e.g. copy the contents of aServiceListmessage into aServiceMapby means ofcopy_from(), you need to invalidate the cache manually.Until the message format changes and the master node advertises its services in a mapping, use this adapter.
Example
Create a
ServiceMap– the example sets the transport for theServiceCallto None, real code needs to use a validServiceConnection>>> from ubii.framework.services import ServiceMap, ServiceCall >>> from functools import partial >>> from ubii.proto import Service >>> services = [Service(topic=topic) for topic in ['foo', 'bar', 'foobar']] >>> service_map = ServiceMap(service_call_factory=partial(ServiceCall, transport=None), elements=services)
When accessing keys that have corresponding services in the
elementsof the map, aServiceCallis returned>>> ... >>> service = service_map['foo'] >>> type(service) <class 'ubii.framework.services.ServiceCall'> >>> service.topic 'foo'
Services that are not defined in the
elementsraise aKeyErroras expected>>> ... >>> service_map['thing'] Traceback <...> KeyError: 'found no services for topic thing'
The created
ServiceCallwill be cached>>> ... >>> service is service_map['foo'] True
If you assign a Service list to
elementsthe cache will be cleared implictily and a new but equivalentServiceCallis created for the topic>>> ... >>> service_map.elements = services >>> service is service_map['foo'] False >>> service == service_map['foo'] True
The
elementscan change implicitly if the internal protobuf message changes. In this case the code also needs to callcache_clear()explicitly>>> ... >>> service_map.elements = services >>> from ubii.proto import ServiceList >>> service_list = ServiceList(elements=[service for service in services if not service.topic == 'foo']) >>> type(service_list).copy_from(service_map, service_list) >>> service is service_map['foo'] True >>> service_map.elements [ topic: "bar", topic: "foobar" ]
As you can see the foo service is cached, although no foo topic is in the
elementsanymore. After cache invalidation, accessing'foo'topic raises aKeyErroras expected>>> ... >>> service_map.elements = services >>> service_map.cache_clear() >>> service is service_map['foo'] Traceback <...> KeyError: 'found no services for topic foo'
- elements¶
RepeatedFieldof typeService– inherited fromServiceList
- __deepcopy__(memo)¶
Not provided by the proto plus base class. Needed because proto plus base implements __getattr__, so when you try to deepcopy a ServiceMap (e.g.
dataclasses.dataclass.replace()with a dataclass that contains aServiceMap) you get infinite recursion if you don’t also implement __deepcopy__
- __init__(mapping=None, *, service_call_factory: ServiceCallFactory[T_Service], **kwargs)¶
- Parameters:
mapping – used to initialize the protobuf wrapper, can also itself be a
ServiceListwrapperservice_call_factory – used to convert the protobuf messages to
ServiceCallobjects**kwargs – passed to protobuf wrapper initialization
- cache_clear()¶
Clear cached service calls. Implicitly used if a new list of
Servicesis assigned to theelementsfield, need to call manually if assignment is done implicitly.See also
ServiceMap code example – shows caching behaviour in detail
- elements: MutableSequence[Service]¶
- class ubii.framework.services.DefaultServiceMap(*args, defaults: MutableMapping[str, str] | None = None, **kwargs)¶
Bases:
ServiceMap[T_Service]Automatically creates Services for missing topics (like a
defaultdict) Takes and optional mapping \(name \rightarrow topic\), which can be accessed as attributes of the ServiceMap.Example
Make a map with defaults
>>> from ubii.framework.services import ServiceCall, DefaultServiceMap >>> from functools import partial >>> service_map = DefaultServiceMap( ... service_call_factory=lambda service: ServiceCall(topic=service.topic, transport=None), ... defaults = {'foo': 'services/foo', 'bar': 'services/bar'}, ... )
Now the services for which there are defined
defaultscan be accessed as an attribute, and will be automatically added to theelements>>> ... >>> service_map.elements [] >>> service = service_map.foo >>> type(service) <class 'ubii.framework.services.ServiceCall'> >>> service.topic 'services/foo' >>> service_map.elements [topic: "services/foo"]
If no default is set, an
AttributeErroris raised as expected>>> ... >>> service_map.boo Traceback <...> AttributeError: Unknown field for DefaultServiceMap: boo
If the framework is used in debug mode, the
AttributeErrorcontains information about possible matches in thedefaults>>> ... >>> from ubii.framework import debug >>> debug(True) True >>> service_map.boo Traceback <...> AttributeError: Unknown field for DefaultServiceMap: boo The above exception was the direct cause of the following exception: Traceback <...> AttributeError: DefaultServiceMap has no attribute 'boo'. Best match[es] in default topics: 'foo'
services are only automatically created when the key is present as a value in
defaults>>> ... >>> service_map['bar'] Traceback <...> KeyError: 'found no services for topic boo' >>> assert service_map['services/bar'] True
- elements¶
RepeatedFieldof typeService– inherited fromServiceList– inherited fromServiceMap
- property defaults: MutableMapping[str, str]¶
Reference to the mapping that was passed as
defaultsduring initialization.Basically, attribute access is equivalent to item access for the corresponding value inside this mapping (if present)
service_map = DefaultServiceMap(...) assert 'some_attr' in service_map.defaults service_map.some_attr == service_map[service_map.defaults['some_attr']]
- elements: MutableSequence[Service]¶
- class ubii.framework.services.ServiceCall(mapping=None, *, transport: ServiceConnection, **kwargs)¶
Bases:
ServiceA ServiceCall is a callable that can be represented as a
ubii.proto.Serviceprotobuf message.- tags¶
RepeatedFieldof typeSTRING– inherited fromService
- __init__(mapping=None, *, transport: ServiceConnection, **kwargs)¶
- Parameters:
mapping – for
ubii.proto.Serviceinitializationtransport – connection used to transfer messages
**kwargs – for
ubii.proto.Serviceinitialization
- property transport¶
Reference to the connection used
- __call__(**payload) Awaitable[ubii.proto.ServiceReply]¶
send the
ubii.proto.ServiceRequestdefined by thepayloadkeyword arguments and thetopicof thisServiceCallusing thetransport.The reply is awaited and error handling is applied.
Example
You can get
ServiceCallobjects from a running client’sServicesbehaviour>>> from ubii.node import connect_client >>> from ubii.framework.client import Services >>> import asyncio >>> service_call = None >>> reply = None >>> async def main(): ... global service_call ... global reply ... async with connect_client() as client: ... assert client.implements(Services) ... service_call = client[Services].service_map.server_config ... reply = await service_call() ... >>> asyncio.run(main()) >>> service_call topic: "/services/server_configuration" response_message_format: "ubii.servers.Server" >>> reply server { id: "f491e47e-e591-4961-b4ca-8e1142175ae8" name: "master-node" ... }
- Parameters:
**payload – will be converted to a
ubii.proto.ServiceRequestmessage withtopicdefined by this callstopic, i.e. typically only one keyword argument will be used, to define the field for thetypeoneof group. Values can be mappings or protobuf wrappers.- Returns:
the reply from the master node
- Raises:
ubii.framework.errors.UbiiError – if master node replies with an error message
This callable had the
hookdecorator applied. Original signature:async def __call__(self, **payload) -> 'ubii.proto.ServiceReply'
- classmethod register_decorator(decorator, instance: object | None = None) None¶
Since
__call__()is ahook, you can easily decorate it. There are two ways to do that – either directly use the fact that__call__()is ahookfrom ubii.framework.services import ServiceCall ServiceCall.__call__.register_decorator(decorator)
Or use this method for convenience
from ubii.framework.services import ServiceCall ServiceCall.register_decorator(decorator)
- Parameters:
decorator – Some callable to decorate
__call__()instance – only register decorator for this instance – optional
See also
ubii.framework.util.functools.hook– more info about the hook decorator
- tags: MutableSequence[str]¶