Skip to content

hololinked.client.proxy.ObjectProxy

Procedural/scripting client for Thing.

Documentation

Once connected to a server, properties, methods and events are loaded and dynamically populated. One instance can be used with any supported protocol, however only one protocol at a time.

Use ClientFactory to create an instance of this class instead of directly creating it.

from hololinked.client import ClientFactory

http_client = ClientFactory.http(url="http://example.com/thing")
zmq_client = ClientFactory.zmq(access_point="IPC", server_id="example_server", thing_id="example_thing")
mqtt_client = ClientFactory.mqtt(broker_url="mqtt://example.com", thing_id="example_thing")
Source code in repo/hololinked/hololinked/client/proxy.py
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
class ObjectProxy:
    """
    Procedural/scripting client for `Thing`.

    [Documentation](https://docs.hololinked.dev/beginners-guide/articles/object-proxy/)

    Once connected to a server, properties, methods and events are loaded and
    dynamically populated. One instance can be used with any supported protocol, however only one protocol at a time.

    Use `ClientFactory` to create an instance of this class instead of directly creating it.

    ```python
    from hololinked.client import ClientFactory

    http_client = ClientFactory.http(url="http://example.com/thing")
    zmq_client = ClientFactory.zmq(access_point="IPC", server_id="example_server", thing_id="example_thing")
    mqtt_client = ClientFactory.mqtt(broker_url="mqtt://example.com", thing_id="example_thing")
    ```
    """

    _own_attrs = frozenset(
        [
            "__annotations__",
            "_allow_foreign_attributes",
            "id",
            "logger",
            "td",
            "execution_timeout",
            "invokation_timeout",
            "_execution_timeout",
            "_invokation_timeout",
            "_events",
            "_noblock_messages",
            "_schema_validator",
            "_security",
        ]
    )
    """
    Attributes of ObjectProxy that are not dynamically populated from the server and are used for internal logic 
    of the client. Dynamic properties are not supported unless `allow_foreign_attributes` is set to `True`.
    """

    __allowed_attribute_types__ = (
        ConsumedThingProperty,
        ConsumedThingAction,
        ConsumedThingEvent,
    )
    """Allowed types for dynamically populated attributes from the server."""

    def __init__(self, id: str, **kwargs) -> None:
        """
        Initialize the client with given id and other optional parameters.

        Parameters
        ----------
        id: str
            unique id for the client
        **kwargs:
            additional keyword arguments:

            - `allow_foreign_attributes`: `bool`, default `False`.
                allows local attributes apart from resources fetched from the server.
            - `logger`: `structlog.stdlib.BoundLogger`, default `None`.
                logger instance
            - `td`: `dict[str, Any]`, default `dict()`.
                Thing Description of the consumed Thing
            - `security`: `BasicSecurity` | `APIKeySecurity`, optional.
                security scheme to be used for authentication
        """
        self.id = id
        self._allow_foreign_attributes = kwargs.get("allow_foreign_attributes", False)
        self._noblock_messages = {}  # type: dict[str, ConsumedThingAction | ConsumedThingProperty]
        self._schema_validator = kwargs.get("schema_validator", None)
        self._security = kwargs.get("security", None)  # type: BasicSecurity | APIKeySecurity | None
        self.logger = kwargs.pop("logger", structlog.get_logger())
        self.td = kwargs.get("td", {})  # type: dict[str, Any]

    def __getattribute__(self, name: str) -> Any:  # noqa: D105
        obj = super().__getattribute__(name)
        if isinstance(obj, ConsumedThingProperty):
            return obj.get()
        return obj

    def __setattr__(self, name: str, value: Any) -> None:  # noqa: D105
        if (
            name in ObjectProxy._own_attrs
            or (name not in self.__dict__ and isinstance(value, ObjectProxy.__allowed_attribute_types__))
            or self._allow_foreign_attributes
        ):
            # allowed attribute types are ConsumedThingProperty and ConsumedThingAction defined after this class
            return super().__setattr__(name, value)
        elif name in self.__dict__:
            obj = self.__dict__[name]
            if isinstance(obj, ConsumedThingProperty):
                obj.set(value=value)
                return
            raise AttributeError(f"Cannot set attribute {name} again to ObjectProxy for {self.id}.")
        raise AttributeError(
            f"Cannot set foreign attribute {name} to ObjectProxy for {self.id}."
            + "Given attribute not found in server object."
        )

    def __repr__(self) -> str:  # noqa: D105
        return f"ObjectProxy {self.id}"

    def __enter__(self):  # noqa: D105
        return self

    def __exit__(self, exc_type, exc_value, traceback):  # noqa: D105
        pass

    def __eq__(self, other) -> bool:  # noqa: D105
        if other is self:
            return True
        return isinstance(other, ObjectProxy) and other.id == self.id and other.TD == self.TD

    def __ne__(self, other) -> bool:  # noqa: D105
        if other and isinstance(other, ObjectProxy):
            return other.id != self.id or other.TD != self.TD
        return True

    def __hash__(self) -> int:  # noqa: D105
        return hash(self.id)

    # @abstractmethod
    # def is_supported_interaction(self, td, name):
    #     """Returns True if the any of the Forms for the Interaction
    #     with the given name is supported in this Protocol Binding client."""
    #     raise NotImplementedError()

    def invoke_action(self, name: str, *args, **kwargs) -> Any:
        """
        Invoke an action specified by name on the served Thing with positional/keyword arguments.

        Parameters
        ----------
        name: str
            name of the action
        oneway: bool, optional, default False
            only send an instruction to invoke the action but do not fetch the reply.
            only accepted as keyword argument.
        noblock: bool, optional, default False
            schedule an action invokation but collect the reply later using a reply id.
            only accepted as keyword argument.
        *args: Any
            arguments for the action.
        **kwargs: dict[str, Any]
            keyword arguments for the action.

        Returns
        -------
        Any
            return value of the action call or a message id if `noblock` is True

        Raises
        ------
        AttributeError
            if action with specified name not found in the Thing Description
        Exception
            server raised exception are propagated
        """
        action = getattr(self, name, None)  # type: ConsumedThingAction
        if not action:
            raise AttributeError(f"No action named {name} in Thing {self.td['id']}")
        oneway = kwargs.pop("oneway", False)
        noblock = kwargs.pop("noblock", False)
        if noblock:
            return action.noblock(*args, **kwargs)
        elif oneway:
            action.oneway(*args, **kwargs)
        else:
            return action(*args, **kwargs)

    async def async_invoke_action(self, name: str, *args, **kwargs) -> Any:
        """
        async(io) call an action specified by name on the served Thing with positional/keyword arguments.

        `noblock` and `oneway` are not supported for async calls.

        Parameters
        ----------
        name: str
            name of the action.
        *args: Any
            arguments for the action.
        **kwargs: dict[str, Any]
            keyword arguments for the action.

        Returns
        -------
        Any
            return value of the action call.

        Raises
        ------
        AttributeError
            if action with specified name not found in the Thing Description.
        Exception
            server raised exception are propagated.
        """
        action = getattr(self, name, None)  # type: ConsumedThingAction
        if not action:
            raise AttributeError(f"No remote action named {name}")
        return await action.async_call(*args, **kwargs)

    def read_property(self, name: str, noblock: bool = False) -> Any:
        """
        Read property specified by name on the served Thing.

        Parameters
        ----------
        name: str
            name of the property.
        noblock: bool, default False
            request the property but collect the reply/value later using a reply id.

        Returns
        -------
        Any
            value of the property or a message id if `noblock` is True.

        Raises
        ------
        AttributeError
            if no property with specified name found in the Thing Description.
        Exception
            server raised exception are propagated.
        """
        prop = self.__dict__.get(name, None)  # type: ConsumedThingProperty
        if not prop:
            raise AttributeError(f"No property named {name}")
        if noblock:
            return prop.noblock_get()
        else:
            return prop.get()

    def write_property(self, name: str, value: Any, oneway: bool = False, noblock: bool = False) -> None:
        """
        Write property specified by name on the served Thing with given value.

        Parameters
        ----------
        name: str
            name of the property.
        value: Any
            value of property to be set.
        oneway: bool, default False
            only send an instruction to write the property but do not fetch the reply.
            (irrespective of whether write was successful or not).
        noblock: bool, default False
            request the write property but collect the reply later using a reply id.

        Raises
        ------
        AttributeError
            if no property with specified name found in the Thing Description.
        Exception
            server raised exception are propagated.
        """
        prop = self.__dict__.get(name, None)  # type: ConsumedThingProperty
        if not prop:
            raise AttributeError(f"No property named {name}")
        if oneway:
            prop.oneway_set(value)
        elif noblock:
            return prop.noblock_set(value)
        else:
            prop.set(value)

    async def async_read_property(self, name: str) -> Any:
        """
        async(io) read property specified by name on the served Thing.

        `noblock` and `oneway` are not supported for async calls.

        Parameters
        ----------
        name: str
            name of the property to fetch.

        Returns
        -------
        Any
            value of the property.

        Raises
        ------
        AttributeError
            if no property with specified name found in the Thing Description.
        Exception
            server raised exception are propagated.
        """
        prop = self.__dict__.get(name, None)  # type: ConsumedThingProperty
        if not prop:
            raise AttributeError(f"No property named {name}")
        return await prop.async_get()

    async def async_write_property(self, name: str, value: Any) -> None:
        """
        async(io) write property specified by name on the served Thing with specified value.

        `noblock` and `oneway` are not supported for async calls.

        Parameters
        ----------
        name: str
            name of the property.
        value: Any
            value of the property to be written.

        Raises
        ------
        AttributeError
            if no property with specified name found in the Thing Description.
        Exception
            server raised exception are propagated.
        """
        prop = self.__dict__.get(name, None)  # type: ConsumedThingProperty
        if not prop:
            raise AttributeError(f"No property named {name}")
        await prop.async_set(value)

    def read_multiple_properties(self, names: list[str], noblock: bool = False) -> dict[str, Any]:
        """
        Read properties from the served Thing specified by list of names.

        Parameters
        ----------
        names: List[str]
            names of properties to be fetched.
        noblock: bool, default False
            request the fetch but collect the reply later using a reply id.

        Returns
        -------
        dict[str, Any]
            dictionary with names as keys and values corresponding to those keys.

        Raises
        ------
        RuntimeError
            if internal `_get_properties` method is not found, which means client did not load server resources
            correctly.
        Exception
            server raised exception are propagated.
        """
        method = getattr(self, "_get_properties", None)  # type: ConsumedThingAction
        if not method:
            raise RuntimeError("Client did not load server resources correctly. Report issue at github.")
        if noblock:
            return method.noblock(names=names)
        else:
            return method(names=names)

    def write_multiple_properties(
        self,
        oneway: bool = False,
        noblock: bool = False,
        **properties: dict[str, Any],
    ) -> None:
        """
        Write properties onto the served Thing whose name is specified as keyword arguments.

        Parameters
        ----------
        oneway: bool, default False
            only send an instruction to write the property but do not fetch the reply.
            (irrespective of whether write was successful or not)
        noblock: bool, default False
            request the write property but collect the reply later using a reply id
        **properties: Dict[str, Any]
            name and value of properties to be written

        Raises
        ------
        ValueError
            if no properties are given to be written
        RuntimeError
            if internal `_set_properties` method is not found, which means client did not load server resources
            correctly.
        Exception
            server raised exception are propagated
        """
        if len(properties) == 0:
            raise ValueError("no properties given to set_properties")
        method = getattr(self, "_set_properties", None)  # type: ConsumedThingAction
        if not method:
            raise RuntimeError("Client did not load server resources correctly. Report issue at github.")
        if oneway:
            method.oneway(**properties)
        elif noblock:
            return method.noblock(**properties)
        else:
            return method(**properties)

    async def async_read_multiple_properties(self, names: list[str]) -> dict[str, Any]:
        """
        async(io) read properties from the served Thing specified by list of names.

        `noblock` reads are not supported for asyncio.

        Parameters
        ----------
        names: List[str]
            names of properties to be fetched.

        Returns
        -------
        dict[str, Any]
            dictionary with property names as keys and values corresponding to those keys.

        Raises
        ------
        RuntimeError
            if internal `_get_properties` method is not found, which means client did not load server resources
            correctly.
        Exception
            server raised exception are propagated.
        """
        # TODO, actually noblock could be fine for async calls too
        method = getattr(self, "_get_properties", None)  # type: ConsumedThingAction
        if not method:
            raise RuntimeError("Client did not load server resources correctly. Report issue at github.")
        return await method.async_call(names=names)

    async def async_write_multiple_properties(self, **properties: dict[str, Any]) -> None:
        """
        async(io) write properties whose name is specified by keys of a dictionary.

        Parameters
        ----------
        properties: dict[str, Any]
            name and value of properties to be written.

        Raises
        ------
        ValueError
            if no properties are given to be written.
        RuntimeError
            if internal `_set_properties` method is not found, which means client did not load server resources
            correctly.
        Exception
            server raised exception are propagated.
        """
        if len(properties) == 0:
            raise ValueError("no properties given to set_properties")
        method = getattr(self, "_set_properties", None)  # type: ConsumedThingAction
        if not method:
            raise RuntimeError("Client did not load server resources correctly. Report issue at github.")
        await method.async_call(**properties)

    def observe_property(
        self,
        name: str,
        callbacks: list[Callable] | Callable,
        asynch: bool = False,
        concurrent: bool = False,
        deserialize: bool = True,
    ) -> None:
        """
        Observe a property specified by name from the served Thing for change events.

        This method returns immediately after subscribing to the change events of the property.

        Parameters
        ----------
        name: str
            name of the property.
        callbacks: Callable | List[Callable]
            one or more callbacks that will be executed when the property changes.
        asynch: bool
            whether the event should be listened as an asyncio task.
        concurrent: bool
            - when asynch is `False`, whether to thread each of the callbacks otherwise the callbacks will be executed serially.
            - when asynch is `True`, whether to create a new task for each callback otherwise the callbacks will be awaited serially.
        deserialize: bool
            whether to deserialize the event data before passing it to the callbacks.

        Raises
        ------
        AttributeError
            if no property with specified name found in the Thing Description or if the property is not observable.
        """
        event = getattr(self, f"{name}_change_event", None)  # type: ConsumedThingEvent
        if not event:
            raise AttributeError(f"No events for property {name} or property not found")
        self.subscribe_event(
            name=f"{name}_change_event",
            callbacks=callbacks,
            asynch=asynch,
            concurrent=concurrent,
            deserialize=deserialize,
        )

    def unobserve_property(self, name: str) -> None:
        """
        Unsubscribe to property from the served Thing specified by name.

        Parameters
        ----------
        name: str
            name of the property.

        Raises
        ------
        AttributeError
            if no property with specified name found in the Thing Description or if the property is not observable.
        """
        event = getattr(self, f"{name}_change_event", None)  # type: ConsumedThingEvent
        if not event:
            raise AttributeError(f"No events for property {name} or property not found")
        event.unsubscribe()

    def subscribe_event(
        self,
        name: str,
        callbacks: list[Callable] | Callable,
        asynch: bool = False,
        concurrent: bool = False,
        deserialize: bool = True,
        # create_new_connection: bool = False,
    ) -> None:
        """
        Subscribe to event specified by name.

        This method returns immediately after subscribing to the event.

        Parameters
        ----------
        name: str
            name of the event, either the object name used in the server or the name specified in the name argument of
            the Event object.
        callbacks: Callable | List[Callable]
            one or more callbacks that will be executed when the event is received.
        asynch: bool
            whether the event should be listened as an asyncio task.
        concurrent: bool
            - when asynch is `False`, whether to thread the callbacks otherwise the callbacks will be executed serially.
            - when asynch is `True`, whether to create a new task for each callback otherwise the callbacks will be awaited serially.
        deserialize: bool
            whether to deserialize the event data before passing it to the callbacks.

        Raises
        ------
        AttributeError
            if no event with specified name is found.
        """
        event = getattr(self, name, None)  # type: ConsumedThingEvent
        if not event:
            raise AttributeError(f"No event named {name}")
        # TODO: fix the logic below to reuse connections when possible
        # if not create_new_connection:
        # see logic in tag v0.3.2
        event.subscribe(
            callbacks,
            asynch=asynch,
            concurrent=concurrent,
            deserialize=deserialize,
            # create_new_connection=create_new_connection,
        )

    def unsubscribe_event(self, name: str) -> None:
        """
        Unsubscribe to event from served Thing specified by name.

        Parameters
        ----------
        name: str
            name of the event.

        Raises
        ------
        AttributeError
            if no event with specified name is found.
        """
        event = getattr(self, name, None)  # type: ConsumedThingEvent
        if not event:
            raise AttributeError(f"No event named {name}")
        event.unsubscribe()

    def read_reply(self, message_id: str, timeout: float | None = 5.0) -> Any:
        """
        Read reply of no block calls of an action or no block calls of a property read/write.

        Parameters
        ----------
        message_id: str
            id returned by the no block call
        timeout: float, optional, default 5.0
            time to wait for a reply before raising TimeoutError. None waits indefinitely.

        Returns
        -------
        Any
            reply of the action or property.

        Raises
        ------
        ValueError
            if given message id is not found.
        """
        obj = self._noblock_messages.get(message_id, None)
        if not obj:
            raise ValueError("given message id not a one way call or invalid.")
        return obj.read_reply(message_id=message_id, timeout=timeout)

    @property
    def properties(self) -> list[ConsumedThingProperty]:
        """List of properties that were consumed from the Thing Description."""
        return [prop for prop in self.__dict__.values() if isinstance(prop, ConsumedThingProperty)]

    @property
    def actions(self) -> list[ConsumedThingAction]:
        """List of actions that were consumed from the Thing Description."""
        return [action for action in self.__dict__.values() if isinstance(action, ConsumedThingAction)]

    @property
    def events(self) -> list[ConsumedThingEvent]:
        """List of events that were consumed from the Thing Description."""
        return [event for event in self.__dict__.values() if isinstance(event, ConsumedThingEvent)]

    @property
    def thing_id(self) -> str:
        """Thing ID this client is connected to."""
        return self.td.get("id", None)

    @property
    def TD(self) -> dict[str, Any]:
        """Thing Description of the consumed thing."""
        return self.td

Attributes

properties property

properties: list[ConsumedThingProperty]

List of properties that were consumed from the Thing Description.

actions property

actions: list[ConsumedThingAction]

List of actions that were consumed from the Thing Description.

events property

events: list[ConsumedThingEvent]

List of events that were consumed from the Thing Description.

thing_id property

thing_id: str

Thing ID this client is connected to.

TD property

TD: dict[str, Any]

Thing Description of the consumed thing.

Functions

__init__

__init__(id: str, **kwargs) -> None

Initialize the client with given id and other optional parameters.

Parameters:

Name Type Description Default

id

str

unique id for the client

required

**kwargs

additional keyword arguments:

  • allow_foreign_attributes: bool, default False. allows local attributes apart from resources fetched from the server.
  • logger: structlog.stdlib.BoundLogger, default None. logger instance
  • td: dict[str, Any], default dict(). Thing Description of the consumed Thing
  • security: BasicSecurity | APIKeySecurity, optional. security scheme to be used for authentication
{}
Source code in repo/hololinked/hololinked/client/proxy.py
def __init__(self, id: str, **kwargs) -> None:
    """
    Initialize the client with given id and other optional parameters.

    Parameters
    ----------
    id: str
        unique id for the client
    **kwargs:
        additional keyword arguments:

        - `allow_foreign_attributes`: `bool`, default `False`.
            allows local attributes apart from resources fetched from the server.
        - `logger`: `structlog.stdlib.BoundLogger`, default `None`.
            logger instance
        - `td`: `dict[str, Any]`, default `dict()`.
            Thing Description of the consumed Thing
        - `security`: `BasicSecurity` | `APIKeySecurity`, optional.
            security scheme to be used for authentication
    """
    self.id = id
    self._allow_foreign_attributes = kwargs.get("allow_foreign_attributes", False)
    self._noblock_messages = {}  # type: dict[str, ConsumedThingAction | ConsumedThingProperty]
    self._schema_validator = kwargs.get("schema_validator", None)
    self._security = kwargs.get("security", None)  # type: BasicSecurity | APIKeySecurity | None
    self.logger = kwargs.pop("logger", structlog.get_logger())
    self.td = kwargs.get("td", {})  # type: dict[str, Any]

invoke_action

invoke_action(name: str, *args, **kwargs) -> Any

Invoke an action specified by name on the served Thing with positional/keyword arguments.

Parameters:

Name Type Description Default

name

str

name of the action

required

oneway

only send an instruction to invoke the action but do not fetch the reply. only accepted as keyword argument.

required

noblock

schedule an action invokation but collect the reply later using a reply id. only accepted as keyword argument.

required

*args

arguments for the action.

()

**kwargs

keyword arguments for the action.

{}

Returns:

Type Description
Any

return value of the action call or a message id if noblock is True

Raises:

Type Description
AttributeError

if action with specified name not found in the Thing Description

Exception

server raised exception are propagated

Source code in repo/hololinked/hololinked/client/proxy.py
def invoke_action(self, name: str, *args, **kwargs) -> Any:
    """
    Invoke an action specified by name on the served Thing with positional/keyword arguments.

    Parameters
    ----------
    name: str
        name of the action
    oneway: bool, optional, default False
        only send an instruction to invoke the action but do not fetch the reply.
        only accepted as keyword argument.
    noblock: bool, optional, default False
        schedule an action invokation but collect the reply later using a reply id.
        only accepted as keyword argument.
    *args: Any
        arguments for the action.
    **kwargs: dict[str, Any]
        keyword arguments for the action.

    Returns
    -------
    Any
        return value of the action call or a message id if `noblock` is True

    Raises
    ------
    AttributeError
        if action with specified name not found in the Thing Description
    Exception
        server raised exception are propagated
    """
    action = getattr(self, name, None)  # type: ConsumedThingAction
    if not action:
        raise AttributeError(f"No action named {name} in Thing {self.td['id']}")
    oneway = kwargs.pop("oneway", False)
    noblock = kwargs.pop("noblock", False)
    if noblock:
        return action.noblock(*args, **kwargs)
    elif oneway:
        action.oneway(*args, **kwargs)
    else:
        return action(*args, **kwargs)

async_invoke_action async

async_invoke_action(name: str, *args, **kwargs) -> Any

async(io) call an action specified by name on the served Thing with positional/keyword arguments.

noblock and oneway are not supported for async calls.

Parameters:

Name Type Description Default

name

str

name of the action.

required

*args

arguments for the action.

()

**kwargs

keyword arguments for the action.

{}

Returns:

Type Description
Any

return value of the action call.

Raises:

Type Description
AttributeError

if action with specified name not found in the Thing Description.

Exception

server raised exception are propagated.

Source code in repo/hololinked/hololinked/client/proxy.py
async def async_invoke_action(self, name: str, *args, **kwargs) -> Any:
    """
    async(io) call an action specified by name on the served Thing with positional/keyword arguments.

    `noblock` and `oneway` are not supported for async calls.

    Parameters
    ----------
    name: str
        name of the action.
    *args: Any
        arguments for the action.
    **kwargs: dict[str, Any]
        keyword arguments for the action.

    Returns
    -------
    Any
        return value of the action call.

    Raises
    ------
    AttributeError
        if action with specified name not found in the Thing Description.
    Exception
        server raised exception are propagated.
    """
    action = getattr(self, name, None)  # type: ConsumedThingAction
    if not action:
        raise AttributeError(f"No remote action named {name}")
    return await action.async_call(*args, **kwargs)

read_property

read_property(name: str, noblock: bool = False) -> Any

Read property specified by name on the served Thing.

Parameters:

Name Type Description Default

name

str

name of the property.

required

noblock

bool

request the property but collect the reply/value later using a reply id.

False

Returns:

Type Description
Any

value of the property or a message id if noblock is True.

Raises:

Type Description
AttributeError

if no property with specified name found in the Thing Description.

Exception

server raised exception are propagated.

Source code in repo/hololinked/hololinked/client/proxy.py
def read_property(self, name: str, noblock: bool = False) -> Any:
    """
    Read property specified by name on the served Thing.

    Parameters
    ----------
    name: str
        name of the property.
    noblock: bool, default False
        request the property but collect the reply/value later using a reply id.

    Returns
    -------
    Any
        value of the property or a message id if `noblock` is True.

    Raises
    ------
    AttributeError
        if no property with specified name found in the Thing Description.
    Exception
        server raised exception are propagated.
    """
    prop = self.__dict__.get(name, None)  # type: ConsumedThingProperty
    if not prop:
        raise AttributeError(f"No property named {name}")
    if noblock:
        return prop.noblock_get()
    else:
        return prop.get()

write_property

write_property(name: str, value: Any, oneway: bool = False, noblock: bool = False) -> None

Write property specified by name on the served Thing with given value.

Parameters:

Name Type Description Default

name

str

name of the property.

required

value

Any

value of property to be set.

required

oneway

bool

only send an instruction to write the property but do not fetch the reply. (irrespective of whether write was successful or not).

False

noblock

bool

request the write property but collect the reply later using a reply id.

False

Raises:

Type Description
AttributeError

if no property with specified name found in the Thing Description.

Exception

server raised exception are propagated.

Source code in repo/hololinked/hololinked/client/proxy.py
def write_property(self, name: str, value: Any, oneway: bool = False, noblock: bool = False) -> None:
    """
    Write property specified by name on the served Thing with given value.

    Parameters
    ----------
    name: str
        name of the property.
    value: Any
        value of property to be set.
    oneway: bool, default False
        only send an instruction to write the property but do not fetch the reply.
        (irrespective of whether write was successful or not).
    noblock: bool, default False
        request the write property but collect the reply later using a reply id.

    Raises
    ------
    AttributeError
        if no property with specified name found in the Thing Description.
    Exception
        server raised exception are propagated.
    """
    prop = self.__dict__.get(name, None)  # type: ConsumedThingProperty
    if not prop:
        raise AttributeError(f"No property named {name}")
    if oneway:
        prop.oneway_set(value)
    elif noblock:
        return prop.noblock_set(value)
    else:
        prop.set(value)

async_read_property async

async_read_property(name: str) -> Any

async(io) read property specified by name on the served Thing.

noblock and oneway are not supported for async calls.

Parameters:

Name Type Description Default

name

str

name of the property to fetch.

required

Returns:

Type Description
Any

value of the property.

Raises:

Type Description
AttributeError

if no property with specified name found in the Thing Description.

Exception

server raised exception are propagated.

Source code in repo/hololinked/hololinked/client/proxy.py
async def async_read_property(self, name: str) -> Any:
    """
    async(io) read property specified by name on the served Thing.

    `noblock` and `oneway` are not supported for async calls.

    Parameters
    ----------
    name: str
        name of the property to fetch.

    Returns
    -------
    Any
        value of the property.

    Raises
    ------
    AttributeError
        if no property with specified name found in the Thing Description.
    Exception
        server raised exception are propagated.
    """
    prop = self.__dict__.get(name, None)  # type: ConsumedThingProperty
    if not prop:
        raise AttributeError(f"No property named {name}")
    return await prop.async_get()

async_write_property async

async_write_property(name: str, value: Any) -> None

async(io) write property specified by name on the served Thing with specified value.

noblock and oneway are not supported for async calls.

Parameters:

Name Type Description Default

name

str

name of the property.

required

value

Any

value of the property to be written.

required

Raises:

Type Description
AttributeError

if no property with specified name found in the Thing Description.

Exception

server raised exception are propagated.

Source code in repo/hololinked/hololinked/client/proxy.py
async def async_write_property(self, name: str, value: Any) -> None:
    """
    async(io) write property specified by name on the served Thing with specified value.

    `noblock` and `oneway` are not supported for async calls.

    Parameters
    ----------
    name: str
        name of the property.
    value: Any
        value of the property to be written.

    Raises
    ------
    AttributeError
        if no property with specified name found in the Thing Description.
    Exception
        server raised exception are propagated.
    """
    prop = self.__dict__.get(name, None)  # type: ConsumedThingProperty
    if not prop:
        raise AttributeError(f"No property named {name}")
    await prop.async_set(value)

read_multiple_properties

read_multiple_properties(names: list[str], noblock: bool = False) -> dict[str, Any]

Read properties from the served Thing specified by list of names.

Parameters:

Name Type Description Default

names

list[str]

names of properties to be fetched.

required

noblock

bool

request the fetch but collect the reply later using a reply id.

False

Returns:

Type Description
dict[str, Any]

dictionary with names as keys and values corresponding to those keys.

Raises:

Type Description
RuntimeError

if internal _get_properties method is not found, which means client did not load server resources correctly.

Exception

server raised exception are propagated.

Source code in repo/hololinked/hololinked/client/proxy.py
def read_multiple_properties(self, names: list[str], noblock: bool = False) -> dict[str, Any]:
    """
    Read properties from the served Thing specified by list of names.

    Parameters
    ----------
    names: List[str]
        names of properties to be fetched.
    noblock: bool, default False
        request the fetch but collect the reply later using a reply id.

    Returns
    -------
    dict[str, Any]
        dictionary with names as keys and values corresponding to those keys.

    Raises
    ------
    RuntimeError
        if internal `_get_properties` method is not found, which means client did not load server resources
        correctly.
    Exception
        server raised exception are propagated.
    """
    method = getattr(self, "_get_properties", None)  # type: ConsumedThingAction
    if not method:
        raise RuntimeError("Client did not load server resources correctly. Report issue at github.")
    if noblock:
        return method.noblock(names=names)
    else:
        return method(names=names)

write_multiple_properties

write_multiple_properties(oneway: bool = False, noblock: bool = False, **properties: dict[str, Any]) -> None

Write properties onto the served Thing whose name is specified as keyword arguments.

Parameters:

Name Type Description Default

oneway

bool

only send an instruction to write the property but do not fetch the reply. (irrespective of whether write was successful or not)

False

noblock

bool

request the write property but collect the reply later using a reply id

False

**properties

dict[str, Any]

name and value of properties to be written

{}

Raises:

Type Description
ValueError

if no properties are given to be written

RuntimeError

if internal _set_properties method is not found, which means client did not load server resources correctly.

Exception

server raised exception are propagated

Source code in repo/hololinked/hololinked/client/proxy.py
def write_multiple_properties(
    self,
    oneway: bool = False,
    noblock: bool = False,
    **properties: dict[str, Any],
) -> None:
    """
    Write properties onto the served Thing whose name is specified as keyword arguments.

    Parameters
    ----------
    oneway: bool, default False
        only send an instruction to write the property but do not fetch the reply.
        (irrespective of whether write was successful or not)
    noblock: bool, default False
        request the write property but collect the reply later using a reply id
    **properties: Dict[str, Any]
        name and value of properties to be written

    Raises
    ------
    ValueError
        if no properties are given to be written
    RuntimeError
        if internal `_set_properties` method is not found, which means client did not load server resources
        correctly.
    Exception
        server raised exception are propagated
    """
    if len(properties) == 0:
        raise ValueError("no properties given to set_properties")
    method = getattr(self, "_set_properties", None)  # type: ConsumedThingAction
    if not method:
        raise RuntimeError("Client did not load server resources correctly. Report issue at github.")
    if oneway:
        method.oneway(**properties)
    elif noblock:
        return method.noblock(**properties)
    else:
        return method(**properties)

async_read_multiple_properties async

async_read_multiple_properties(names: list[str]) -> dict[str, Any]

async(io) read properties from the served Thing specified by list of names.

noblock reads are not supported for asyncio.

Parameters:

Name Type Description Default

names

list[str]

names of properties to be fetched.

required

Returns:

Type Description
dict[str, Any]

dictionary with property names as keys and values corresponding to those keys.

Raises:

Type Description
RuntimeError

if internal _get_properties method is not found, which means client did not load server resources correctly.

Exception

server raised exception are propagated.

Source code in repo/hololinked/hololinked/client/proxy.py
async def async_read_multiple_properties(self, names: list[str]) -> dict[str, Any]:
    """
    async(io) read properties from the served Thing specified by list of names.

    `noblock` reads are not supported for asyncio.

    Parameters
    ----------
    names: List[str]
        names of properties to be fetched.

    Returns
    -------
    dict[str, Any]
        dictionary with property names as keys and values corresponding to those keys.

    Raises
    ------
    RuntimeError
        if internal `_get_properties` method is not found, which means client did not load server resources
        correctly.
    Exception
        server raised exception are propagated.
    """
    # TODO, actually noblock could be fine for async calls too
    method = getattr(self, "_get_properties", None)  # type: ConsumedThingAction
    if not method:
        raise RuntimeError("Client did not load server resources correctly. Report issue at github.")
    return await method.async_call(names=names)

async_write_multiple_properties async

async_write_multiple_properties(**properties: dict[str, Any]) -> None

async(io) write properties whose name is specified by keys of a dictionary.

Parameters:

Name Type Description Default

properties

dict[str, Any]

name and value of properties to be written.

{}

Raises:

Type Description
ValueError

if no properties are given to be written.

RuntimeError

if internal _set_properties method is not found, which means client did not load server resources correctly.

Exception

server raised exception are propagated.

Source code in repo/hololinked/hololinked/client/proxy.py
async def async_write_multiple_properties(self, **properties: dict[str, Any]) -> None:
    """
    async(io) write properties whose name is specified by keys of a dictionary.

    Parameters
    ----------
    properties: dict[str, Any]
        name and value of properties to be written.

    Raises
    ------
    ValueError
        if no properties are given to be written.
    RuntimeError
        if internal `_set_properties` method is not found, which means client did not load server resources
        correctly.
    Exception
        server raised exception are propagated.
    """
    if len(properties) == 0:
        raise ValueError("no properties given to set_properties")
    method = getattr(self, "_set_properties", None)  # type: ConsumedThingAction
    if not method:
        raise RuntimeError("Client did not load server resources correctly. Report issue at github.")
    await method.async_call(**properties)

observe_property

observe_property(name: str, callbacks: list[Callable] | Callable, asynch: bool = False, concurrent: bool = False, deserialize: bool = True) -> None

Observe a property specified by name from the served Thing for change events.

This method returns immediately after subscribing to the change events of the property.

Parameters:

Name Type Description Default

name

str

name of the property.

required

callbacks

list[Callable] | Callable

one or more callbacks that will be executed when the property changes.

required

asynch

bool

whether the event should be listened as an asyncio task.

False

concurrent

bool
  • when asynch is False, whether to thread each of the callbacks otherwise the callbacks will be executed serially.
  • when asynch is True, whether to create a new task for each callback otherwise the callbacks will be awaited serially.
False

deserialize

bool

whether to deserialize the event data before passing it to the callbacks.

True

Raises:

Type Description
AttributeError

if no property with specified name found in the Thing Description or if the property is not observable.

Source code in repo/hololinked/hololinked/client/proxy.py
def observe_property(
    self,
    name: str,
    callbacks: list[Callable] | Callable,
    asynch: bool = False,
    concurrent: bool = False,
    deserialize: bool = True,
) -> None:
    """
    Observe a property specified by name from the served Thing for change events.

    This method returns immediately after subscribing to the change events of the property.

    Parameters
    ----------
    name: str
        name of the property.
    callbacks: Callable | List[Callable]
        one or more callbacks that will be executed when the property changes.
    asynch: bool
        whether the event should be listened as an asyncio task.
    concurrent: bool
        - when asynch is `False`, whether to thread each of the callbacks otherwise the callbacks will be executed serially.
        - when asynch is `True`, whether to create a new task for each callback otherwise the callbacks will be awaited serially.
    deserialize: bool
        whether to deserialize the event data before passing it to the callbacks.

    Raises
    ------
    AttributeError
        if no property with specified name found in the Thing Description or if the property is not observable.
    """
    event = getattr(self, f"{name}_change_event", None)  # type: ConsumedThingEvent
    if not event:
        raise AttributeError(f"No events for property {name} or property not found")
    self.subscribe_event(
        name=f"{name}_change_event",
        callbacks=callbacks,
        asynch=asynch,
        concurrent=concurrent,
        deserialize=deserialize,
    )

unobserve_property

unobserve_property(name: str) -> None

Unsubscribe to property from the served Thing specified by name.

Parameters:

Name Type Description Default

name

str

name of the property.

required

Raises:

Type Description
AttributeError

if no property with specified name found in the Thing Description or if the property is not observable.

Source code in repo/hololinked/hololinked/client/proxy.py
def unobserve_property(self, name: str) -> None:
    """
    Unsubscribe to property from the served Thing specified by name.

    Parameters
    ----------
    name: str
        name of the property.

    Raises
    ------
    AttributeError
        if no property with specified name found in the Thing Description or if the property is not observable.
    """
    event = getattr(self, f"{name}_change_event", None)  # type: ConsumedThingEvent
    if not event:
        raise AttributeError(f"No events for property {name} or property not found")
    event.unsubscribe()

subscribe_event

subscribe_event(name: str, callbacks: list[Callable] | Callable, asynch: bool = False, concurrent: bool = False, deserialize: bool = True) -> None

Subscribe to event specified by name.

This method returns immediately after subscribing to the event.

Parameters:

Name Type Description Default

name

str

name of the event, either the object name used in the server or the name specified in the name argument of the Event object.

required

callbacks

list[Callable] | Callable

one or more callbacks that will be executed when the event is received.

required

asynch

bool

whether the event should be listened as an asyncio task.

False

concurrent

bool
  • when asynch is False, whether to thread the callbacks otherwise the callbacks will be executed serially.
  • when asynch is True, whether to create a new task for each callback otherwise the callbacks will be awaited serially.
False

deserialize

bool

whether to deserialize the event data before passing it to the callbacks.

True

Raises:

Type Description
AttributeError

if no event with specified name is found.

Source code in repo/hololinked/hololinked/client/proxy.py
def subscribe_event(
    self,
    name: str,
    callbacks: list[Callable] | Callable,
    asynch: bool = False,
    concurrent: bool = False,
    deserialize: bool = True,
    # create_new_connection: bool = False,
) -> None:
    """
    Subscribe to event specified by name.

    This method returns immediately after subscribing to the event.

    Parameters
    ----------
    name: str
        name of the event, either the object name used in the server or the name specified in the name argument of
        the Event object.
    callbacks: Callable | List[Callable]
        one or more callbacks that will be executed when the event is received.
    asynch: bool
        whether the event should be listened as an asyncio task.
    concurrent: bool
        - when asynch is `False`, whether to thread the callbacks otherwise the callbacks will be executed serially.
        - when asynch is `True`, whether to create a new task for each callback otherwise the callbacks will be awaited serially.
    deserialize: bool
        whether to deserialize the event data before passing it to the callbacks.

    Raises
    ------
    AttributeError
        if no event with specified name is found.
    """
    event = getattr(self, name, None)  # type: ConsumedThingEvent
    if not event:
        raise AttributeError(f"No event named {name}")
    # TODO: fix the logic below to reuse connections when possible
    # if not create_new_connection:
    # see logic in tag v0.3.2
    event.subscribe(
        callbacks,
        asynch=asynch,
        concurrent=concurrent,
        deserialize=deserialize,
        # create_new_connection=create_new_connection,
    )

unsubscribe_event

unsubscribe_event(name: str) -> None

Unsubscribe to event from served Thing specified by name.

Parameters:

Name Type Description Default

name

str

name of the event.

required

Raises:

Type Description
AttributeError

if no event with specified name is found.

Source code in repo/hololinked/hololinked/client/proxy.py
def unsubscribe_event(self, name: str) -> None:
    """
    Unsubscribe to event from served Thing specified by name.

    Parameters
    ----------
    name: str
        name of the event.

    Raises
    ------
    AttributeError
        if no event with specified name is found.
    """
    event = getattr(self, name, None)  # type: ConsumedThingEvent
    if not event:
        raise AttributeError(f"No event named {name}")
    event.unsubscribe()

read_reply

read_reply(message_id: str, timeout: float | None = 5.0) -> Any

Read reply of no block calls of an action or no block calls of a property read/write.

Parameters:

Name Type Description Default

message_id

str

id returned by the no block call

required

timeout

float | None

time to wait for a reply before raising TimeoutError. None waits indefinitely.

5.0

Returns:

Type Description
Any

reply of the action or property.

Raises:

Type Description
ValueError

if given message id is not found.

Source code in repo/hololinked/hololinked/client/proxy.py
def read_reply(self, message_id: str, timeout: float | None = 5.0) -> Any:
    """
    Read reply of no block calls of an action or no block calls of a property read/write.

    Parameters
    ----------
    message_id: str
        id returned by the no block call
    timeout: float, optional, default 5.0
        time to wait for a reply before raising TimeoutError. None waits indefinitely.

    Returns
    -------
    Any
        reply of the action or property.

    Raises
    ------
    ValueError
        if given message id is not found.
    """
    obj = self._noblock_messages.get(message_id, None)
    if not obj:
        raise ValueError("given message id not a one way call or invalid.")
    return obj.read_reply(message_id=message_id, timeout=timeout)