Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
mosmuell opened this issue Oct 16, 2023 · 1 comment
Labels
documentation Improvements or additions to documentation question Further information is requested

Comments

@mosmuell
Copy link
Member

mosmuell commented Oct 16, 2023

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 mosmuell added documentation Improvements or additions to documentation question Further information is requested labels Oct 16, 2023
@mosmuell
Copy link
Member Author

mosmuell commented Dec 11, 2023

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation question Further information is requested
Projects
None yet
Development

No branches or pull requests

1 participant