tiqi-group / pydase

A Python library for creating remote control interfaces of Python objects.
https://pydase.readthedocs.io
MIT License
1 stars 1 forks source link

Passing "parents" of type DataService to "children" (of type DataService) does not work #31

Open mosmuell opened 10 months ago

mosmuell commented 10 months ago

Passing the parent to a child DataService does not work with the current implementation (and probably shouldn't). When registering callbacks, this will produce a recursive loop. At the moment, it just does not work as some assumptions are not fulfilled.

from typing import Optional

import pydase

class OtherService(pydase.DataService):
    _parent: Optional["MyService"]

    def __init__(self, parent: Optional["MyService"] = None) -> None:
        super().__init__()
        self._parent = parent

class MyService(pydase.DataService):
    def __init__(self) -> None:
        super().__init__()
        self.child = OtherService(self)

if __name__ == "__main__":
    service = MyService()
    pydase.Server(service).run()

This results in the following error message:

Thread stopped due to exception of type KeyError       (note: full exception trace is shown but execution is paused at: _run_module_as_main) (unhandled)
Description: '__root__'
Stack trace:
  File "/home/mose/work/repositories/pydase/src/pydase/utils/helpers.py", line 21, in get_class_and_instance_attributes
    attrs.pop("__root__")
  File "/home/mose/work/repositories/pydase/src/pydase/data_service/callback_manager.py", line 87, in _register_list_change_callbacks
    attrs = get_class_and_instance_attributes(obj)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/mose/work/repositories/pydase/src/pydase/data_service/callback_manager.py", line 92, in _register_list_change_callbacks
    self._register_list_change_callbacks(attr_value, new_path)
  File "/home/mose/work/repositories/pydase/src/pydase/data_service/callback_manager.py", line 367, in register_callbacks
    self._register_list_change_callbacks(
  File "/home/mose/work/repositories/pydase/src/pydase/data_service/data_service.py", line 56, in __init__
    self._callback_manager.register_callbacks()
  File "/home/mose/work/repositories/pydase/foo.py", line 12, in __init__
    super().__init__()
  File "/home/mose/work/repositories/pydase/foo.py", line 17, in __init__
    child = OtherService(self)
            ^^^^^^^^^^^^^^^^^^
  File "/home/mose/work/repositories/pydase/foo.py", line 22, in <module>
    service = MyService()
              ^^^^^^^^^^^
  File "/usr/lib64/python3.11/runpy.py", line 88, in _run_code
    exec(code, run_globals)
  File "/usr/lib64/python3.11/runpy.py", line 198, in _run_module_as_main (Current frame)
    return _run_code(code, main_globals, None,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
KeyError: '__root__'

When changing the helper function code to

    attrs.pop("__root__", None)

I get another error:

Thread stopped due to exception of type AttributeError       (note: full exception trace is shown but execution is paused at: _run_module_as_main) (unhandled)
Description: 'MyService' object has no attribute '_callback_manager'
Stack trace:
  File "/home/mose/work/repositories/pydase/src/pydase/data_service/callback_manager.py", line 176, in _register_DataService_instance_callbacks
    obj._callback_manager.callbacks.add(callback)
    ^^^^^^^^^^^^^^^^^^^^^
  File "/home/mose/work/repositories/pydase/src/pydase/data_service/callback_manager.py", line 211, in _register_service_callbacks
    self._register_DataService_instance_callbacks(nested_attr, new_path)
  File "/home/mose/work/repositories/pydase/src/pydase/data_service/callback_manager.py", line 187, in _register_DataService_instance_callbacks
    self._register_service_callbacks(
  File "/home/mose/work/repositories/pydase/src/pydase/data_service/callback_manager.py", line 370, in register_callbacks
    self._register_DataService_instance_callbacks(
  File "/home/mose/work/repositories/pydase/src/pydase/data_service/data_service.py", line 56, in __init__
    self._callback_manager.register_callbacks()
  File "/home/mose/work/repositories/pydase/foo.py", line 11, in __init__
    super().__init__()
  File "/home/mose/work/repositories/pydase/foo.py", line 16, in __init__
    child = OtherService(self)
            ^^^^^^^^^^^^^^^^^^
  File "/home/mose/work/repositories/pydase/foo.py", line 21, in <module>
    service = MyService()
              ^^^^^^^^^^^
  File "/usr/lib64/python3.11/runpy.py", line 88, in _run_code
    exec(code, run_globals)
  File "/usr/lib64/python3.11/runpy.py", line 198, in _run_module_as_main (Current frame)
    return _run_code(code, main_globals, None,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'MyService' object has no attribute '_callback_manager'

Passing the "parent" instance to the "children" should not be done. Maybe I can throw an exception, explaining this.

mosmuell commented 8 months ago

While the code still does not work, the error message has changed due to code base changes:

Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/home/mose/work/repositories/pydase/foo.py", line 22, in <module>
    service = MyService()
              ^^^^^^^^^^^
  File "/home/mose/work/repositories/pydase/foo.py", line 18, in __init__
    self.child = OtherService(self)
    ^^^^^^^^^^
  File "/home/mose/work/repositories/pydase/src/pydase/data_service/data_service.py", line 77, in __setattr__
    super().__setattr__(__name, __value)
  File "/home/mose/work/repositories/pydase/src/pydase/observer_pattern/observable/observable.py", line 33, in __setattr__
    value = self._handle_observable_setattr(name, value)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/mose/work/repositories/pydase/src/pydase/observer_pattern/observable/observable.py", line 56, in _handle_observable_setattr
    self._notify_change_start(name)
  File "/home/mose/work/repositories/pydase/src/pydase/observer_pattern/observable/observable_object.py", line 82, in _notify_change_start
    observer._notify_change_start(extended_attr_path)
  File "/home/mose/work/repositories/pydase/src/pydase/observer_pattern/observable/observable_object.py", line 82, in _notify_change_start
    observer._notify_change_start(extended_attr_path)
  File "/home/mose/work/repositories/pydase/src/pydase/observer_pattern/observable/observable_object.py", line 82, in _notify_change_start
    observer._notify_change_start(extended_attr_path)
  [Previous line repeated 986 more times]
  File "/home/mose/work/repositories/pydase/src/pydase/observer_pattern/observable/observable_object.py", line 77, in _notify_change_start
    for attr_name, observer_list in self._observers.items():
                                    ^^^^^^^^^^^^^^^
  File "/home/mose/work/repositories/pydase/src/pydase/observer_pattern/observable/observable.py", line 40, in __getattribute__
    if is_property_attribute(self, name):
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RecursionError: maximum recursion depth exceeded