Skip to content

Commit d113226

Browse files
committed
test RW multiple properties
1 parent 229c68a commit d113226

File tree

12 files changed

+241
-408
lines changed

12 files changed

+241
-408
lines changed

hololinked/client/abstractions.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,22 @@ def observe(self, *callbacks: typing.Callable) -> None:
260260
def unobserve(self) -> None:
261261
"""Stop observing property value changes"""
262262
raise NotImplementedError("implement property unobserve per protocol")
263+
264+
def read_reply(self, message_id: str, timeout: float | int | None = None) -> typing.Any:
265+
"""
266+
Read the reply of the action call
267+
268+
Parameters
269+
----------
270+
message_id: str
271+
id of the request or message (UUID4 as string)
272+
273+
Returns
274+
-------
275+
typing.Any
276+
reply of the action call
277+
"""
278+
raise NotImplementedError("implement action read_reply per protocol")
263279

264280

265281
class ConsumedThingEvent:

hololinked/client/factory.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import uuid
22

33
from .abstractions import ConsumedThingAction, ConsumedThingProperty, ConsumedThingEvent
4-
from .zmq.consumed_interactions import ZMQAction, ZMQEvent, ZMQProperty
4+
from .zmq.consumed_interactions import ZMQAction, ZMQEvent, ZMQProperty, WriteMultipleProperties, ReadMultipleProperties
55
from ..core.zmq import SyncZMQClient, AsyncZMQClient
66
from ..core import Thing, Action
77
from ..td.interaction_affordance import PropertyAffordance, ActionAffordance, EventAffordance
@@ -39,6 +39,7 @@ def zmq(self, server_id: str, thing_id: str, protocol: str, **kwargs):
3939
async_client=async_zmq_client,
4040
)
4141
TD = FetchTD(ignore_errors=True)
42+
object_proxy.td = TD
4243
for name in TD["properties"]:
4344
affordance = PropertyAffordance.from_TD(name, TD)
4445
consumed_property = ZMQProperty(
@@ -72,6 +73,16 @@ def zmq(self, server_id: str, thing_id: str, protocol: str, **kwargs):
7273
execution_timeout=object_proxy.execution_timeout,
7374
)
7475
self.add_event(object_proxy, consumed_event)
76+
for opname, ophandler in zip(['_get_properties', '_set_properties'], [ReadMultipleProperties, WriteMultipleProperties]):
77+
setattr(
78+
object_proxy,
79+
opname,
80+
ophandler(
81+
sync_client=sync_zmq_client,
82+
async_client=async_zmq_client,
83+
owner_inst=object_proxy
84+
)
85+
)
7586
return object_proxy
7687

7788
@classmethod

hololinked/client/proxy.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class ObjectProxy:
5252
_own_attrs = frozenset([
5353
'__annotations__',
5454
'_allow_foreign_attributes',
55-
'id', 'logger',
55+
'id', 'logger', 'td',
5656
'execution_timeout', 'invokation_timeout', '_execution_timeout', '_invokation_timeout',
5757
'_events',
5858
'_noblock_messages',
@@ -73,6 +73,7 @@ def __init__(self,
7373
)
7474
self.invokation_timeout = kwargs.get("invokation_timeout", 5)
7575
self.execution_timeout = kwargs.get("execution_timeout", 5)
76+
self.td = kwargs.get('td', dict()) # type: typing.Dict[str, typing.Any]
7677
# compose ZMQ client in Proxy client so that all sending and receiving is
7778
# done by the ZMQ client and not by the Proxy client directly. Proxy client only
7879
# bothers mainly about __setattr__ and _getattr__
@@ -161,10 +162,10 @@ def set_execution_timeout(self, value : typing.Union[float, int]) -> None:
161162
)
162163

163164
# @abstractmethod
164-
def is_supported_interaction(self, td, name):
165-
"""Returns True if the any of the Forms for the Interaction
166-
with the given name is supported in this Protocol Binding client."""
167-
raise NotImplementedError()
165+
# def is_supported_interaction(self, td, name):
166+
# """Returns True if the any of the Forms for the Interaction
167+
# with the given name is supported in this Protocol Binding client."""
168+
# raise NotImplementedError()
168169

169170

170171
def invoke_action(
@@ -568,18 +569,7 @@ def read_reply(self, message_id: str, timeout: typing.Optional[float] = 5000) ->
568569
if not obj:
569570
raise ValueError('given message id not a one way call or invalid.')
570571
return obj.read_reply(message_id=message_id, timeout=timeout)
571-
# reply = obj.sync_zmq_client._.get(message_id, None)
572-
if not reply:
573-
reply = self.zmq_client.recv_reply(message_id=message_id, timeout=timeout,
574-
raise_client_side_exception=True)
575-
if not reply:
576-
raise ReplyNotArrivedError(f"could not fetch reply within timeout for message id '{message_id}'")
577-
if isinstance(obj, ConsumedThingAction):
578-
obj._last_zmq_response = reply
579-
return obj.last_return_value # note the missing underscore
580-
elif isinstance(obj, ConsumedThingProperty):
581-
obj._last_value = reply
582-
return obj.last_read_value
572+
583573

584574
@property
585575
def properties(self) -> typing.List[ConsumedThingProperty]:
@@ -601,6 +591,20 @@ def events(self) -> typing.List[ConsumedThingEvent]:
601591
list of events in the server object
602592
"""
603593
return [event for event in self.__dict__.values() if isinstance(event, ConsumedThingEvent)]
594+
595+
@property
596+
def thing_id(self) -> str:
597+
"""
598+
id of the server object
599+
"""
600+
return self.td.get("id", None)
601+
602+
@property
603+
def TD(self) -> typing.Dict[str, typing.Any]:
604+
"""
605+
Thing description of the server object
606+
"""
607+
return self.td
604608

605609

606610
__all__ = [

hololinked/client/zmq/consumed_interactions.py

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
import threading
55
import warnings
66
import traceback
7-
from uuid import uuid4
8-
97

108
from ...utils import get_current_async_loop
119
from ...constants import Operations
@@ -16,6 +14,8 @@
1614
from ...core.zmq.message import ResponseMessage
1715
from ...core.zmq.message import EMPTY_BYTE, REPLY, TIMEOUT, ERROR, INVALID_MESSAGE
1816
from ...core.zmq.brokers import SyncZMQClient, AsyncZMQClient, EventConsumer, AsyncEventConsumer
17+
from ...core import Thing, Action
18+
from ..exceptions import ReplyNotArrivedError
1919

2020

2121

@@ -24,7 +24,7 @@
2424

2525
class ZMQConsumedAffordanceMixin:
2626

27-
__slots__ = ['_resource', '_schema_validator', '__name__', '__qualname__', '__doc__',
27+
__slots__ = ['_resource', '_schema_validator', '__name__', '__qualname__', '__doc__', '_owner_inst',
2828
'_sync_zmq_client', '_async_zmq_client', '_invokation_timeout', '_execution_timeout',
2929
'_thing_execution_context', '_last_zmq_response' ] # __slots__ dont support multiple inheritance
3030

@@ -64,9 +64,16 @@ def last_zmq_response(self) -> ResponseMessage:
6464
"""
6565
return self._last_zmq_response
6666

67+
def read_reply(self, message_id: str, timeout: int = None) -> typing.Any:
68+
if self._owner_inst._noblock_messages.get(message_id) != self:
69+
raise RuntimeError(f"Message ID {message_id} does not belong to this property.")
70+
self._last_zmq_response = self._sync_zmq_client.recv_response(message_id=message_id)
71+
if not self._last_zmq_response:
72+
raise ReplyNotArrivedError(f"could not fetch reply within timeout for message id '{message_id}'")
73+
return ZMQConsumedAffordanceMixin.get_last_return_value(self, True)
74+
6775

68-
69-
class ZMQAction(ConsumedThingAction, ZMQConsumedAffordanceMixin):
76+
class ZMQAction(ZMQConsumedAffordanceMixin, ConsumedThingAction):
7077

7178
# method call abstraction
7279
# Dont add doc otherwise __doc__ in slots will conflict with class variable
@@ -187,14 +194,9 @@ def noblock(self, *args, **kwargs) -> str:
187194
self._owner_inst._noblock_messages[msg_id] = self
188195
return msg_id
189196

190-
def read_reply(self, message_id, timeout = None):
191-
if self._owner_inst._noblock_messages.get(message_id) != self:
192-
raise RuntimeError(f"Message ID {message_id} does not belong to this action.")
193-
self._last_zmq_response = self._sync_zmq_client.recv_response(message_id=message_id)
194-
return ZMQConsumedAffordanceMixin.get_last_return_value(self, True)
195197

196198

197-
class ZMQProperty(ConsumedThingProperty, ZMQConsumedAffordanceMixin):
199+
class ZMQProperty(ZMQConsumedAffordanceMixin, ConsumedThingProperty):
198200

199201
# property get set abstraction
200202
# Dont add doc otherwise __doc__ in slots will conflict with class variable
@@ -337,9 +339,9 @@ def noblock_set(self, value: typing.Any) -> None:
337339
)
338340
self._owner_inst._noblock_messages[msg_id] = self
339341
return msg_id
342+
343+
340344

341-
342-
343345
class ZMQEvent(ConsumedThingEvent, ZMQConsumedAffordanceMixin):
344346

345347
__slots__ = ['__name__', '__qualname__', '__doc__',
@@ -442,6 +444,54 @@ def unsubscribe(self, join_thread: bool = True) -> None:
442444
self._thread = None
443445

444446

447+
448+
class WriteMultipleProperties(ZMQAction):
449+
"""
450+
Read and write multiple properties at once
451+
"""
452+
453+
def __init__(self,
454+
sync_client: SyncZMQClient,
455+
async_client: AsyncZMQClient | None = None,
456+
owner_inst: typing.Optional[typing.Any] = None,
457+
**kwargs
458+
) -> None:
459+
action = Thing._set_properties # type: Action
460+
resource = action.to_affordance(Thing)
461+
resource._thing_id = owner_inst.thing_id
462+
super().__init__(
463+
resource=resource,
464+
sync_client=sync_client,
465+
async_client=async_client,
466+
owner_inst=owner_inst,
467+
**kwargs
468+
)
469+
470+
471+
class ReadMultipleProperties(ZMQAction):
472+
"""
473+
Read multiple properties at once
474+
"""
475+
476+
def __init__(
477+
self,
478+
sync_client: SyncZMQClient,
479+
async_client: AsyncZMQClient | None = None,
480+
owner_inst: typing.Optional[typing.Any] = None,
481+
**kwargs
482+
) -> None:
483+
action = Thing._get_properties # type: Action
484+
resource = action.to_affordance(Thing)
485+
resource._thing_id = owner_inst.thing_id
486+
super().__init__(
487+
resource=resource,
488+
sync_client=sync_client,
489+
async_client=async_client,
490+
owner_inst=owner_inst,
491+
**kwargs
492+
)
493+
494+
445495
__all__ = [
446496
ZMQAction.__name__,
447497
ZMQProperty.__name__,

hololinked/core/meta.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from ..utils import getattr_without_descriptor_read
1111
from ..constants import JSON, JSONSerializable
1212
from ..serializers import Serializers
13-
from .actions import Action, BoundAction
13+
from .actions import Action, BoundAction, action
1414
from .property import Property
1515
from .events import Event, EventPublisher, EventDispatcher
1616

@@ -754,6 +754,54 @@ def properties(self) -> PropertiesRegistry:
754754
"""container for the property descriptors of the object."""
755755
return self._properties_registry
756756

757+
@action()
758+
def _get_properties(self, **kwargs) -> typing.Dict[str, typing.Any]:
759+
"""
760+
"""
761+
return self.properties.get(**kwargs)
762+
763+
@action()
764+
def _set_properties(self, **values : typing.Dict[str, typing.Any]) -> None:
765+
"""
766+
set properties whose name is specified by keys of a dictionary
767+
768+
Parameters
769+
----------
770+
values: Dict[str, Any]
771+
dictionary of property names and its values
772+
"""
773+
return self.properties.set(**values) # returns None
774+
775+
@action()
776+
def _get_properties_in_db(self) -> typing.Dict[str, JSONSerializable]:
777+
"""
778+
get all properties in the database
779+
780+
Returns
781+
-------
782+
Dict[str, JSONSerializable]
783+
dictionary of property names and their values
784+
"""
785+
return self.properties.get_from_DB()
786+
787+
@action()
788+
def _add_property(self, name: str, prop: JSON) -> None:
789+
"""
790+
add a property to the object
791+
792+
Parameters
793+
----------
794+
name: str
795+
name of the property
796+
prop: Property
797+
property object
798+
"""
799+
raise NotImplementedError("this method will be implemented properly in a future release")
800+
prop = Property(**prop)
801+
self.properties.add(name, prop)
802+
self._prepare_resources()
803+
# instruct the clients to fetch the new resources
804+
757805

758806
class RemoteInvokable:
759807
"""

hololinked/core/thing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class Thing(Propertized, RemoteInvokable, EventSource, metaclass=ThingMeta):
2525
actions to instruct the object to perform tasks and events to get notified of any relevant information. State Machines
2626
can be used to contrain operations on properties and actions.
2727
28-
[UML Diagram](http://localhost:8000/UML/PDF/Thing.pdf)
28+
[UML Diagram](https://docs.hololinked.dev/UML/PDF/Thing.pdf)
2929
"""
3030

3131
# local properties

hololinked/td/metadata.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from .base import Schema
55

66

7-
87
class Link(Schema):
98
href : str
109
anchor: typing.Optional[str]

hololinked/td/security_definitions.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from .base import Schema
77

88

9-
109
class SecurityScheme(Schema):
1110
"""
1211
create security scheme.

0 commit comments

Comments
 (0)