Skip to content

Commit eb0848f

Browse files
committed
Add a new TaskCreator protocol
This will be used in the future to allow overriding where to create tasks (for example the default `asyncio` loop, a custom `loop` or even a `TaskGroup`). Signed-off-by: Leandro Lucarella <[email protected]>
1 parent a7a7131 commit eb0848f

File tree

2 files changed

+54
-2
lines changed

2 files changed

+54
-2
lines changed

src/frequenz/core/asyncio.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,51 @@
1212
task and waits for it to finish, handling `CancelledError` exceptions.
1313
- [Service][frequenz.core.asyncio.Service]: A base class for
1414
implementing services running in the background that can be started and stopped.
15+
- [TaskCreator][frequenz.core.asyncio.TaskCreator]: A protocol for creating tasks.
1516
"""
1617

1718

1819
import abc
1920
import asyncio
2021
import collections.abc
22+
import contextvars
2123
from types import TracebackType
22-
from typing import Any, Self
24+
from typing import Any, Protocol, Self, TypeVar, runtime_checkable
25+
26+
TaskReturnT = TypeVar("TaskReturnT")
27+
"""The type of the return value of a task."""
28+
29+
30+
@runtime_checkable
31+
class TaskCreator(Protocol):
32+
"""A protocol for creating tasks.
33+
34+
Built-in asyncio functions and classes implementing this protocol:
35+
36+
- [`asyncio`][]
37+
- [`asyncio.AbstractEventLoop`][] (returned by [`asyncio.get_event_loop`][] for
38+
example)
39+
- [`asyncio.TaskGroup`][]
40+
"""
41+
42+
def create_task(
43+
self,
44+
coro: collections.abc.Coroutine[Any, Any, TaskReturnT],
45+
*,
46+
name: str | None = None,
47+
context: contextvars.Context | None = None,
48+
) -> asyncio.Task[TaskReturnT]:
49+
"""Create a task.
50+
51+
Args:
52+
coro: The coroutine to be executed.
53+
name: The name of the task.
54+
context: The context to be used for the task.
55+
56+
Returns:
57+
The new task.
58+
"""
59+
... # pylint: disable=unnecessary-ellipsis
2360

2461

2562
async def cancel_and_await(task: asyncio.Task[Any]) -> None:

tests/test_asyncio.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import async_solipsism
1010
import pytest
1111

12-
from frequenz.core.asyncio import Service
12+
from frequenz.core.asyncio import Service, TaskCreator
1313

1414

1515
# This method replaces the event loop for all tests in the file.
@@ -152,3 +152,18 @@ async def test_async_context_manager() -> None:
152152
assert fake_service.is_running is True
153153

154154
assert fake_service.is_running is False
155+
156+
157+
def test_task_creator_asyncio() -> None:
158+
"""Test that the asyncio module is a TaskCreator."""
159+
assert isinstance(asyncio, TaskCreator)
160+
161+
162+
async def test_task_creator_loop() -> None:
163+
"""Test that the asyncio event loop is a TaskCreator."""
164+
assert isinstance(asyncio.get_event_loop(), TaskCreator)
165+
166+
167+
def test_task_creator_task_group() -> None:
168+
"""Test that the asyncio task group is a TaskCreator."""
169+
assert isinstance(asyncio.TaskGroup(), TaskCreator)

0 commit comments

Comments
 (0)