From 488415436c644197c6b37fc40f93e5096d453752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mose=20M=C3=BCller?= Date: Sat, 21 Sep 2024 08:32:54 +0200 Subject: [PATCH 1/6] fixes recursion when defining task without autostart --- src/pydase/task/autostart.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pydase/task/autostart.py b/src/pydase/task/autostart.py index 234b2a9..5d45be1 100644 --- a/src/pydase/task/autostart.py +++ b/src/pydase/task/autostart.py @@ -17,7 +17,9 @@ def autostart_service_tasks( """ for attr in dir(service): - if is_property_attribute(service, attr): # prevent eval of property attrs + if is_property_attribute(service, attr) or attr in { + "__dict__", + }: # prevent eval of property attrs and recursion continue val = getattr(service, attr) From 1ac9e45c73a563633b1a5efcae23ede1425a3752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mose=20M=C3=BCller?= Date: Sat, 21 Sep 2024 08:36:47 +0200 Subject: [PATCH 2/6] test: updates task test to catch recursion when defining without autostart --- tests/task/test_task.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/task/test_task.py b/tests/task/test_task.py index de21dbf..d5fc6d9 100644 --- a/tests/task/test_task.py +++ b/tests/task/test_task.py @@ -26,6 +26,9 @@ async def test_start_and_stop_task(caplog: LogCaptureFixture) -> None: service_instance = MyService() state_manager = StateManager(service_instance) DataServiceObserver(state_manager) + + autostart_service_tasks(service_instance) + service_instance.my_task.start() await asyncio.sleep(0.1) From e48046795e064b6bded170f362789291a19fabb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mose=20M=C3=BCller?= Date: Sat, 21 Sep 2024 08:37:34 +0200 Subject: [PATCH 3/6] updates version to v0.10.2 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4cf9b39..c93714e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pydase" -version = "0.10.1" +version = "0.10.2" description = "A flexible and robust Python library for creating, managing, and interacting with data services, with built-in support for web and RPC servers, and customizable features for diverse use cases." authors = ["Mose Mueller "] readme = "README.md" From 20028c379d3d1799bf11e541449a8bd7783bbdee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mose=20M=C3=BCller?= Date: Sat, 21 Sep 2024 09:04:04 +0200 Subject: [PATCH 4/6] test: updates task tests --- tests/task/test_task.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/task/test_task.py b/tests/task/test_task.py index d5fc6d9..bd53c3d 100644 --- a/tests/task/test_task.py +++ b/tests/task/test_task.py @@ -18,9 +18,9 @@ async def test_start_and_stop_task(caplog: LogCaptureFixture) -> None: class MyService(pydase.DataService): @task() async def my_task(self) -> None: + logger.info("Triggered task.") while True: - logger.debug("Logging message") - await asyncio.sleep(0.01) + await asyncio.sleep(1) # Your test code here service_instance = MyService() @@ -28,16 +28,20 @@ async def test_start_and_stop_task(caplog: LogCaptureFixture) -> None: DataServiceObserver(state_manager) autostart_service_tasks(service_instance) + await asyncio.sleep(0.1) + assert service_instance.my_task.status == TaskStatus.NOT_RUNNING service_instance.my_task.start() await asyncio.sleep(0.1) + assert service_instance.my_task.status == TaskStatus.RUNNING assert "'my_task.status' changed to 'TaskStatus.RUNNING'" in caplog.text - assert "Logging message" in caplog.text + assert "Triggered task." in caplog.text caplog.clear() service_instance.my_task.stop() await asyncio.sleep(0.1) + assert service_instance.my_task.status == TaskStatus.NOT_RUNNING assert "Task 'my_task' was cancelled" in caplog.text @@ -47,6 +51,8 @@ async def test_autostart_task(caplog: LogCaptureFixture) -> None: @task(autostart=True) async def my_task(self) -> None: logger.info("Triggered task.") + while True: + await asyncio.sleep(1) # Your test code here service_instance = MyService() @@ -56,6 +62,7 @@ async def test_autostart_task(caplog: LogCaptureFixture) -> None: autostart_service_tasks(service_instance) await asyncio.sleep(0.1) + assert service_instance.my_task.status == TaskStatus.RUNNING assert "'my_task.status' changed to 'TaskStatus.RUNNING'" in caplog.text @@ -68,6 +75,8 @@ async def test_nested_list_autostart_task( @task(autostart=True) async def my_task(self) -> None: logger.info("Triggered task.") + while True: + await asyncio.sleep(1) class MyService(pydase.DataService): sub_services_list = [MySubService() for i in range(2)] @@ -78,6 +87,8 @@ async def test_nested_list_autostart_task( autostart_service_tasks(service_instance) await asyncio.sleep(0.1) + assert service_instance.sub_services_list[0].my_task.status == TaskStatus.RUNNING + assert service_instance.sub_services_list[1].my_task.status == TaskStatus.RUNNING assert ( "'sub_services_list[0].my_task.status' changed to 'TaskStatus.RUNNING'" @@ -114,6 +125,10 @@ async def test_nested_dict_autostart_task( assert ( service_instance.sub_services_dict["first"].my_task.status == TaskStatus.RUNNING ) + assert ( + service_instance.sub_services_dict["second"].my_task.status + == TaskStatus.RUNNING + ) assert ( "'sub_services_dict[\"first\"].my_task.status' changed to 'TaskStatus.RUNNING'" From 56dd9dd8aad178fe525183a394ecd48827551231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mose=20M=C3=BCller?= Date: Sat, 21 Sep 2024 09:12:01 +0200 Subject: [PATCH 5/6] adapts autostart to support nested lists in dicts and vice versa --- src/pydase/task/autostart.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pydase/task/autostart.py b/src/pydase/task/autostart.py index 5d45be1..e9a6c7d 100644 --- a/src/pydase/task/autostart.py +++ b/src/pydase/task/autostart.py @@ -18,6 +18,7 @@ def autostart_service_tasks( for attr in dir(service): if is_property_attribute(service, attr) or attr in { + "_observers", "__dict__", }: # prevent eval of property attrs and recursion continue @@ -40,7 +41,7 @@ def autostart_nested_service_tasks( autostart_service_tasks(service) elif isinstance(service, list): for entry in service: - autostart_service_tasks(entry) + autostart_nested_service_tasks(entry) elif isinstance(service, dict): for entry in service.values(): - autostart_service_tasks(entry) + autostart_nested_service_tasks(entry) From 983d392ba8805fe45b49ea2970c7d7fff19c42be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mose=20M=C3=BCller?= Date: Sat, 21 Sep 2024 11:40:25 +0200 Subject: [PATCH 6/6] properly handle Task objects in autostart method Tasks that are not autostart or are already running were passed to autostart_nested_services. This caused the recursion as tasks have a __self__ attribute pointing to the containing service. --- src/pydase/task/autostart.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/pydase/task/autostart.py b/src/pydase/task/autostart.py index e9a6c7d..927457e 100644 --- a/src/pydase/task/autostart.py +++ b/src/pydase/task/autostart.py @@ -24,12 +24,11 @@ def autostart_service_tasks( continue val = getattr(service, attr) - if ( - isinstance(val, pydase.task.task.Task) - and val.autostart - and val.status == TaskStatus.NOT_RUNNING - ): - val.start() + if isinstance(val, pydase.task.task.Task): + if val.autostart and val.status == TaskStatus.NOT_RUNNING: + val.start() + else: + continue else: autostart_nested_service_tasks(val)