Skip to content

Commit 064a707

Browse files
committed
Split the asyncio module
Since more code is coming, and this module is already a bit large, we split it into a util and a service sub-module. Signed-off-by: Leandro Lucarella <[email protected]>
1 parent 5266fa7 commit 064a707

File tree

5 files changed

+122
-88
lines changed

5 files changed

+122
-88
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# License: MIT
2+
# Copyright © 2024 Frequenz Energy-as-a-Service GmbH
3+
4+
"""General purpose async tools.
5+
6+
This module provides general purpose async tools that can be used to simplify the
7+
development of asyncio-based applications.
8+
9+
The module provides the following classes and functions:
10+
11+
- [cancel_and_await][frequenz.core.asyncio.cancel_and_await]: A function that cancels a
12+
task and waits for it to finish, handling `CancelledError` exceptions.
13+
- [Service][frequenz.core.asyncio.Service]: An interface for services running in the
14+
background.
15+
- [ServiceBase][frequenz.core.asyncio.ServiceBase]: A base class for implementing
16+
services running in the background.
17+
- [TaskCreator][frequenz.core.asyncio.TaskCreator]: A protocol for creating tasks.
18+
"""
19+
20+
from ._service import Service, ServiceBase
21+
from ._util import TaskCreator, TaskReturnT, cancel_and_await
22+
23+
__all__ = [
24+
"Service",
25+
"ServiceBase",
26+
"TaskCreator",
27+
"TaskReturnT",
28+
"cancel_and_await",
29+
]
Lines changed: 4 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,7 @@
11
# License: MIT
22
# Copyright © 2024 Frequenz Energy-as-a-Service GmbH
33

4-
"""General purpose async tools.
5-
6-
This module provides general purpose async tools that can be used to simplify the
7-
development of asyncio-based applications.
8-
9-
The module provides the following classes and functions:
10-
11-
- [cancel_and_await][frequenz.core.asyncio.cancel_and_await]: A function that cancels a
12-
task and waits for it to finish, handling `CancelledError` exceptions.
13-
- [Service][frequenz.core.asyncio.Service]: An interface for services running in the
14-
background.
15-
- [ServiceBase][frequenz.core.asyncio.ServiceBase]: A base class for implementing
16-
services running in the background.
17-
- [TaskCreator][frequenz.core.asyncio.TaskCreator]: A protocol for creating tasks.
18-
"""
4+
"""Module implementing the `Service` and `ServiceBase` classes."""
195

206

217
import abc
@@ -24,66 +10,13 @@
2410
import contextvars
2511
import logging
2612
from types import TracebackType
27-
from typing import Any, Protocol, Self, TypeVar, runtime_checkable
13+
from typing import Any, Self
2814

2915
from typing_extensions import override
3016

31-
_logger = logging.getLogger(__name__)
32-
33-
34-
TaskReturnT = TypeVar("TaskReturnT")
35-
"""The type of the return value of a task."""
36-
37-
38-
@runtime_checkable
39-
class TaskCreator(Protocol):
40-
"""A protocol for creating tasks.
41-
42-
Built-in asyncio functions and classes implementing this protocol:
43-
44-
- [`asyncio`][]
45-
- [`asyncio.AbstractEventLoop`][] (returned by [`asyncio.get_event_loop`][] for
46-
example)
47-
- [`asyncio.TaskGroup`][]
48-
"""
49-
50-
def create_task(
51-
self,
52-
coro: collections.abc.Coroutine[Any, Any, TaskReturnT],
53-
*,
54-
name: str | None = None,
55-
context: contextvars.Context | None = None,
56-
) -> asyncio.Task[TaskReturnT]:
57-
"""Create a task.
58-
59-
Args:
60-
coro: The coroutine to be executed.
61-
name: The name of the task.
62-
context: The context to be used for the task.
63-
64-
Returns:
65-
The new task.
66-
"""
67-
... # pylint: disable=unnecessary-ellipsis
68-
69-
70-
async def cancel_and_await(task: asyncio.Task[Any]) -> None:
71-
"""Cancel a task and wait for it to finish.
17+
from ._util import TaskCreator, TaskReturnT
7218

73-
Exits immediately if the task is already done.
74-
75-
The `CancelledError` is suppressed, but any other exception will be propagated.
76-
77-
Args:
78-
task: The task to be cancelled and waited for.
79-
"""
80-
if task.done():
81-
return
82-
task.cancel()
83-
try:
84-
await task
85-
except asyncio.CancelledError:
86-
pass
19+
_logger = logging.getLogger(__name__)
8720

8821

8922
class Service(abc.ABC):

src/frequenz/core/asyncio/_util.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# License: MIT
2+
# Copyright © 2024 Frequenz Energy-as-a-Service GmbH
3+
4+
"""General purpose async utilities."""
5+
6+
7+
import asyncio
8+
import collections.abc
9+
import contextvars
10+
from typing import Any, Protocol, TypeVar, runtime_checkable
11+
12+
TaskReturnT = TypeVar("TaskReturnT")
13+
"""The type of the return value of a task."""
14+
15+
16+
@runtime_checkable
17+
class TaskCreator(Protocol):
18+
"""A protocol for creating tasks.
19+
20+
Built-in asyncio functions and classes implementing this protocol:
21+
22+
- [`asyncio`][]
23+
- [`asyncio.AbstractEventLoop`][] (returned by [`asyncio.get_event_loop`][] for
24+
example)
25+
- [`asyncio.TaskGroup`][]
26+
"""
27+
28+
def create_task(
29+
self,
30+
coro: collections.abc.Coroutine[Any, Any, TaskReturnT],
31+
*,
32+
name: str | None = None,
33+
context: contextvars.Context | None = None,
34+
) -> asyncio.Task[TaskReturnT]:
35+
"""Create a task.
36+
37+
Args:
38+
coro: The coroutine to be executed.
39+
name: The name of the task.
40+
context: The context to be used for the task.
41+
42+
Returns:
43+
The new task.
44+
"""
45+
... # pylint: disable=unnecessary-ellipsis
46+
47+
48+
async def cancel_and_await(task: asyncio.Task[Any]) -> None:
49+
"""Cancel a task and wait for it to finish.
50+
51+
Exits immediately if the task is already done.
52+
53+
The `CancelledError` is suppressed, but any other exception will be propagated.
54+
55+
Args:
56+
task: The task to be cancelled and waited for.
57+
"""
58+
if task.done():
59+
return
60+
task.cancel()
61+
try:
62+
await task
63+
except asyncio.CancelledError:
64+
pass
Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
# License: MIT
22
# Copyright © 2022 Frequenz Energy-as-a-Service GmbH
33

4-
"""Tests for the asyncio module."""
4+
"""Tests for the asyncio service module."""
55

66
import asyncio
77
from typing import Literal, assert_never
88

99
import async_solipsism
1010
import pytest
1111

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

1414

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

149149
assert fake_service.is_running is False
150-
151-
152-
def test_task_creator_asyncio() -> None:
153-
"""Test that the asyncio module is a TaskCreator."""
154-
assert isinstance(asyncio, TaskCreator)
155-
156-
157-
async def test_task_creator_loop() -> None:
158-
"""Test that the asyncio event loop is a TaskCreator."""
159-
assert isinstance(asyncio.get_event_loop(), TaskCreator)
160-
161-
162-
def test_task_creator_task_group() -> None:
163-
"""Test that the asyncio task group is a TaskCreator."""
164-
assert isinstance(asyncio.TaskGroup(), TaskCreator)

tests/asyncio/test_util.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# License: MIT
2+
# Copyright © 2022 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Tests for the asyncio util module."""
5+
6+
import asyncio
7+
8+
from frequenz.core.asyncio import TaskCreator
9+
10+
11+
def test_task_creator_asyncio() -> None:
12+
"""Test that the asyncio module is a TaskCreator."""
13+
assert isinstance(asyncio, TaskCreator)
14+
15+
16+
async def test_task_creator_loop() -> None:
17+
"""Test that the asyncio event loop is a TaskCreator."""
18+
assert isinstance(asyncio.get_event_loop(), TaskCreator)
19+
20+
21+
def test_task_creator_task_group() -> None:
22+
"""Test that the asyncio task group is a TaskCreator."""
23+
assert isinstance(asyncio.TaskGroup(), TaskCreator)

0 commit comments

Comments
 (0)