mirror of
https://github.com/tiqi-group/pydase.git
synced 2025-04-20 00:10:03 +02:00
docs: updates Task documentation
- updates Tasks.md - updates docstrings - adds api section
This commit is contained in:
parent
0c95b5e3cb
commit
ece68b4b99
@ -13,6 +13,12 @@
|
|||||||
::: pydase.components
|
::: pydase.components
|
||||||
handler: python
|
handler: python
|
||||||
|
|
||||||
|
::: pydase.task
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
inherited_members: false
|
||||||
|
show_submodules: true
|
||||||
|
|
||||||
::: pydase.utils.serialization.serializer
|
::: pydase.utils.serialization.serializer
|
||||||
handler: python
|
handler: python
|
||||||
|
|
||||||
|
@ -1,20 +1,18 @@
|
|||||||
# Understanding Tasks
|
# Understanding Tasks
|
||||||
|
|
||||||
In `pydase`, a task is defined as an asynchronous function without arguments contained in a class that inherits from `pydase.DataService`. These tasks usually contain a while loop and are designed to carry out periodic functions.
|
In `pydase`, a task is defined as an asynchronous function without arguments that is decorated with the `@task` decorator and contained in a class that inherits from `pydase.DataService`. These tasks usually contain a while loop and are designed to carry out periodic functions. For example, a task might be used to periodically read sensor data, update a database, or perform any other recurring job.
|
||||||
|
|
||||||
For example, a task might be used to periodically read sensor data, update a database, or perform any other recurring job. One core feature of `pydase` is its ability to automatically generate start and stop functions for these tasks. This allows you to control task execution via both the frontend and python clients, giving you flexible and powerful control over your service's operation.
|
`pydase` allows you to control task execution via both the frontend and Python clients and can automatically start tasks upon initialization of the service. By using the `@task` decorator with the `autostart=True` argument in your service class, `pydase` will automatically start these tasks when the server is started. Here's an example:
|
||||||
|
|
||||||
Another powerful feature of `pydase` is its ability to automatically start tasks upon initialization of the service. By specifying the tasks and their arguments in the `_autostart_tasks` dictionary in your service class's `__init__` method, `pydase` will automatically start these tasks when the server is started. Here's an example:
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import pydase
|
import pydase
|
||||||
|
from pydase.task.decorator import task
|
||||||
|
|
||||||
|
|
||||||
class SensorService(pydase.DataService):
|
class SensorService(pydase.DataService):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.readout_frequency = 1.0
|
self.readout_frequency = 1.0
|
||||||
self._autostart_tasks["read_sensor_data"] = ()
|
|
||||||
|
|
||||||
def _process_data(self, data: ...) -> None:
|
def _process_data(self, data: ...) -> None:
|
||||||
...
|
...
|
||||||
@ -22,6 +20,7 @@ class SensorService(pydase.DataService):
|
|||||||
def _read_from_sensor(self) -> Any:
|
def _read_from_sensor(self) -> Any:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
@task(autostart=True)
|
||||||
async def read_sensor_data(self):
|
async def read_sensor_data(self):
|
||||||
while True:
|
while True:
|
||||||
data = self._read_from_sensor()
|
data = self._read_from_sensor()
|
||||||
@ -34,6 +33,6 @@ if __name__ == "__main__":
|
|||||||
pydase.Server(service=service).run()
|
pydase.Server(service=service).run()
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example, `read_sensor_data` is a task that continuously reads data from a sensor. By adding it to the `_autostart_tasks` dictionary, it will automatically start running when `pydase.Server(service).run()` is executed.
|
In this example, `read_sensor_data` is a task that continuously reads data from a sensor. By decorating it with `@task(autostart=True)`, it will automatically start running when `pydase.Server(service).run()` is executed.
|
||||||
As with all tasks, `pydase` will generate `start_read_sensor_data` and `stop_read_sensor_data` methods, which can be called to manually start and stop the data reading task. The readout frequency can be updated using the `readout_frequency` attribute.
|
|
||||||
|
|
||||||
|
The `@task` decorator replaces the function with a task object that has `start()` and `stop()` methods. This means you can control the task execution directly using these methods. For instance, you can manually start or stop the task by calling `service.read_sensor_data.start()` and `service.read_sensor_data.stop()`, respectively.
|
||||||
|
@ -12,7 +12,8 @@ def autostart_service_tasks(
|
|||||||
"""Starts the service tasks defined with the `autostart` keyword argument.
|
"""Starts the service tasks defined with the `autostart` keyword argument.
|
||||||
|
|
||||||
This method goes through the attributes of the passed service and its nested
|
This method goes through the attributes of the passed service and its nested
|
||||||
`pydase.DataService` instances and calls the start method on autostart-tasks.
|
[`DataService`][pydase.DataService] instances and calls the start method on
|
||||||
|
autostart-tasks.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for attr in dir(service):
|
for attr in dir(service):
|
||||||
|
@ -18,6 +18,54 @@ def task(
|
|||||||
],
|
],
|
||||||
Task[R],
|
Task[R],
|
||||||
]:
|
]:
|
||||||
|
"""
|
||||||
|
A decorator to define a function as a task within a
|
||||||
|
[`DataService`][pydase.DataService] class.
|
||||||
|
|
||||||
|
This decorator transforms an asynchronous function into a
|
||||||
|
[`Task`][pydase.task.task.Task] object. The `Task` object provides methods like
|
||||||
|
`start()` and `stop()` to control the execution of the task.
|
||||||
|
|
||||||
|
Tasks are typically used to perform periodic or recurring jobs, such as reading
|
||||||
|
sensor data, updating databases, or other operations that need to be repeated over
|
||||||
|
time.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
autostart:
|
||||||
|
If set to True, the task will automatically start when the service is
|
||||||
|
initialized. Defaults to False.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A decorator that converts an asynchronous function into a
|
||||||
|
[`Task`][pydase.task.task.Task] object.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
import pydase
|
||||||
|
from pydase.task.decorator import task
|
||||||
|
|
||||||
|
|
||||||
|
class MyService(pydase.DataService):
|
||||||
|
@task(autostart=True)
|
||||||
|
async def my_task(self) -> None:
|
||||||
|
while True:
|
||||||
|
# Perform some periodic work
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
service = MyService()
|
||||||
|
pydase.Server(service=service).run()
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example, `my_task` is defined as a task using the `@task` decorator, and
|
||||||
|
it will start automatically when the service is initialized because
|
||||||
|
`autostart=True` is set. You can manually start or stop the task using
|
||||||
|
`service.my_task.start()` and `service.my_task.stop()`, respectively.
|
||||||
|
"""
|
||||||
|
|
||||||
def decorator(
|
def decorator(
|
||||||
func: Callable[[Any], Coroutine[None, None, R]]
|
func: Callable[[Any], Coroutine[None, None, R]]
|
||||||
| Callable[[], Coroutine[None, None, R]],
|
| Callable[[], Coroutine[None, None, R]],
|
||||||
|
@ -36,6 +36,53 @@ def is_bound_method(
|
|||||||
|
|
||||||
|
|
||||||
class Task(pydase.data_service.data_service.DataService, Generic[R]):
|
class Task(pydase.data_service.data_service.DataService, Generic[R]):
|
||||||
|
"""
|
||||||
|
A class representing a task within the `pydase` framework.
|
||||||
|
|
||||||
|
The `Task` class wraps an asynchronous function and provides methods to manage its
|
||||||
|
lifecycle, such as `start()` and `stop()`. It is typically used to perform periodic
|
||||||
|
or recurring jobs in a [`DataService`][pydase.DataService], like reading
|
||||||
|
sensor data, updating databases, or executing other background tasks.
|
||||||
|
|
||||||
|
When a function is decorated with the [`@task`][pydase.task.decorator.task]
|
||||||
|
decorator, it is replaced by a `Task` instance that controls the execution of the
|
||||||
|
original function.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
func:
|
||||||
|
The asynchronous function that this task wraps. It must be a coroutine
|
||||||
|
without arguments.
|
||||||
|
autostart:
|
||||||
|
If set to True, the task will automatically start when the service is
|
||||||
|
initialized. Defaults to False.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
import pydase
|
||||||
|
from pydase.task.decorator import task
|
||||||
|
|
||||||
|
|
||||||
|
class MyService(pydase.DataService):
|
||||||
|
@task(autostart=True)
|
||||||
|
async def my_task(self) -> None:
|
||||||
|
while True:
|
||||||
|
# Perform some periodic work
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
service = MyService()
|
||||||
|
pydase.Server(service=service).run()
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example, `my_task` is defined as a task using the `@task` decorator, and
|
||||||
|
it will start automatically when the service is initialized because
|
||||||
|
`autostart=True` is set. You can manually start or stop the task using
|
||||||
|
`service.my_task.start()` and `service.my_task.stop()`, respectively.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
func: Callable[[Any], Coroutine[None, None, R | None]]
|
func: Callable[[Any], Coroutine[None, None, R | None]]
|
||||||
@ -59,23 +106,26 @@ class Task(pydase.data_service.data_service.DataService, Generic[R]):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def autostart(self) -> bool:
|
def autostart(self) -> bool:
|
||||||
"""Defines if the task should be started automatically when the `pydase.Server`
|
"""Defines if the task should be started automatically when the
|
||||||
starts."""
|
[`Server`][pydase.Server] starts."""
|
||||||
return self._autostart
|
return self._autostart
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def status(self) -> TaskStatus:
|
def status(self) -> TaskStatus:
|
||||||
|
"""Returns the current status of the task."""
|
||||||
return self._status
|
return self._status
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
|
"""Starts the asynchronous task if it is not already running."""
|
||||||
if self._task:
|
if self._task:
|
||||||
return
|
return
|
||||||
|
|
||||||
def task_done_callback(task: asyncio.Task[R | None]) -> None:
|
def task_done_callback(task: asyncio.Task[R | None]) -> None:
|
||||||
"""Handles tasks that have finished.
|
"""Handles tasks that have finished.
|
||||||
|
|
||||||
Update task status, calls the defined callbacks, and logs and re-raises
|
Updates the task status, calls the defined callbacks, and logs and re-raises
|
||||||
exceptions."""
|
exceptions.
|
||||||
|
"""
|
||||||
|
|
||||||
self._task = None
|
self._task = None
|
||||||
self._status = TaskStatus.NOT_RUNNING
|
self._status = TaskStatus.NOT_RUNNING
|
||||||
@ -113,6 +163,8 @@ class Task(pydase.data_service.data_service.DataService, Generic[R]):
|
|||||||
self._task.add_done_callback(task_done_callback)
|
self._task.add_done_callback(task_done_callback)
|
||||||
|
|
||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
|
"""Stops the running asynchronous task by cancelling it."""
|
||||||
|
|
||||||
if self._task:
|
if self._task:
|
||||||
self._task.cancel()
|
self._task.cancel()
|
||||||
|
|
||||||
@ -120,11 +172,11 @@ class Task(pydase.data_service.data_service.DataService, Generic[R]):
|
|||||||
"""Descriptor method used to correctly set up the task.
|
"""Descriptor method used to correctly set up the task.
|
||||||
|
|
||||||
This descriptor method is called by the class instance containing the task.
|
This descriptor method is called by the class instance containing the task.
|
||||||
We need to use this descriptor to bind the task function to that class instance.
|
It binds the task function to that class instance.
|
||||||
|
|
||||||
As the __init__ function is called when a function is decorated with
|
Since the `__init__` function is called when a function is decorated with
|
||||||
@pydase.task.task, we should delay some of the setup until this descriptor
|
[`@task`][pydase.task.decorator.task], some setup is delayed until this
|
||||||
function is called.
|
descriptor function is called.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if instance and not self._set_up:
|
if instance and not self._set_up:
|
||||||
|
@ -2,5 +2,7 @@ import enum
|
|||||||
|
|
||||||
|
|
||||||
class TaskStatus(enum.Enum):
|
class TaskStatus(enum.Enum):
|
||||||
|
"""Possible statuses of a [`Task`][pydase.task.task.Task]."""
|
||||||
|
|
||||||
RUNNING = "running"
|
RUNNING = "running"
|
||||||
NOT_RUNNING = "not_running"
|
NOT_RUNNING = "not_running"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user