Skip to content

Commit fe5430c

Browse files
authored
Code Documentation (celery#157)
* Added docformatter pre-commit for automatic doc lint+fixing * Fixed doc lint errors found by docformatter * Documented: src/pytest_celery/api/base.py * Documented: src/pytest_celery/api/backend.py * Documented: src/pytest_celery/api/broker.py * Documented: src/pytest_celery/api/container.py * Documented: src/pytest_celery/api/setup.py * Refactored CeleryTestSetup.ready() * Documented: src/pytest_celery/api/worker.py * Documented: src/pytest_celery/plugin.py * Documented: src/pytest_celery/fixtures/* * Documented: src/pytest_celery/vendors/* * Documented: src/pytest_celery/defaults.py
1 parent 30030a4 commit fe5430c

File tree

29 files changed

+750
-88
lines changed

29 files changed

+750
-88
lines changed

.pre-commit-config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ repos:
3434
"black",
3535
]
3636

37+
- repo: https://github.com/PyCQA/docformatter
38+
rev: v1.7.5
39+
hooks:
40+
- id: docformatter
41+
args: [--in-place]
42+
3743
- repo: https://github.com/pre-commit/pre-commit-hooks
3844
rev: v4.5.0
3945
hooks:

examples/django/proj/settings.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,7 @@
1414
CELERY_ACCEPT_CONTENT = ["json"]
1515
CELERY_RESULT_BACKEND = "db+sqlite:///results.sqlite"
1616
CELERY_TASK_SERIALIZER = "json"
17-
18-
19-
"""
20-
Django settings for proj project.
17+
"""Django settings for proj project.
2118
2219
Generated by 'django-admin startproject' using Django 2.2.1.
2320

examples/django/proj/wsgi.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
"""
2-
WSGI config for proj project.
1+
"""WSGI config for proj project.
32
43
This module contains the WSGI application used by Django's development server
54
and any production WSGI deployments. It should expose a module-level variable
@@ -11,7 +10,6 @@
1110
that later delegates to the Django one. For example, you could introduce WSGI
1211
middleware here, or combine a Django application with an application of another
1312
framework.
14-
1513
"""
1614

1715
import os

src/pytest_celery/__init__.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
"""
2-
pytest-celery a shim pytest plugin to enable celery.contrib.pytest
3-
"""
1+
"""Pytest-celery a shim pytest plugin to enable celery.contrib.pytest."""
42

53
# flake8: noqa
64

src/pytest_celery/api/backend.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77

88

99
class CeleryTestBackend(CeleryTestNode):
10+
"""CeleryTestBackend is specialized node type for handling celery backends
11+
nodes. It is used to encapsulate a backend instance.
12+
13+
Responsibility Scope:
14+
Handling backend specific requirements and configuration.
15+
"""
16+
1017
@classmethod
1118
def default_config(cls) -> dict:
1219
return {
@@ -23,6 +30,14 @@ def restart(self, reload_container: bool = True, force: bool = False) -> None:
2330

2431

2532
class CeleryBackendCluster(CeleryTestCluster):
33+
"""CeleryBackendCluster is a specialized cluster type for handling celery
34+
backends. It is used to define which backend instances are available for
35+
the test.
36+
37+
Responsibility Scope:
38+
Provude useful methods for managing a cluster of celery backends.
39+
"""
40+
2641
def __init__(self, *backends: tuple[CeleryTestBackend | CeleryTestContainer]) -> None:
2742
super().__init__(*backends)
2843

src/pytest_celery/api/base.py

Lines changed: 105 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,55 +14,92 @@
1414

1515

1616
class CeleryTestNode:
17+
"""CeleryTestNode is the logical representation of a container instance. It
18+
is used to provide a common interface for interacting with the container
19+
regardless of the underlying implementation.
20+
21+
Responsibility Scope:
22+
The node's responsibility is to wrap the container and provide
23+
useful methods for interacting with it.
24+
"""
25+
1726
def __init__(self, container: CeleryTestContainer, app: Celery = None) -> None:
27+
"""Setup the base components of a CeleryTestNode.
28+
29+
Args:
30+
container (CeleryTestContainer): Container to use for the node.
31+
app (Celery, optional): Celery app. Defaults to None.
32+
"""
1833
self._container = container
1934
self._app = app
2035

2136
@property
2237
def container(self) -> CeleryTestContainer:
38+
"""Underlying container for the node."""
2339
return self._container
2440

2541
@property
2642
def app(self) -> Celery:
43+
"""Celery app for the node if available."""
2744
return self._app
2845

29-
def __eq__(self, __value: object) -> bool:
30-
if isinstance(__value, CeleryTestNode):
46+
def __eq__(self, other: object) -> bool:
47+
if isinstance(other, CeleryTestNode):
3148
return all(
3249
(
33-
self.container == __value.container,
34-
self.app == __value.app,
50+
self.container == other.container,
51+
self.app == other.app,
3552
)
3653
)
3754
return False
3855

3956
@classmethod
4057
def default_config(cls) -> dict:
58+
"""Default node configurations if not overridden by the user."""
4159
return {}
4260

4361
def ready(self) -> bool:
62+
"""Waits until the node is ready or raise an exception if it fails to
63+
boot up."""
4464
return self.container.ready()
4565

4666
def config(self, *args: tuple, **kwargs: dict) -> dict:
67+
"""Compile the configurations required for Celery from this node."""
4768
return self.container.celeryconfig
4869

4970
def logs(self) -> str:
71+
"""Get the logs of the underlying container."""
5072
return self.container.logs()
5173

5274
def name(self) -> str:
75+
"""Get the name of this node."""
5376
return self.container.name
5477

5578
def hostname(self) -> str:
79+
"""Get the hostname of this node."""
5680
return self.container.id[:12]
5781

5882
def kill(self, signal: str | int = "SIGKILL", reload_container: bool = True) -> None:
83+
"""Kill the underlying container.
84+
85+
Args:
86+
signal (str | int, optional): Signal to send to the container. Defaults to "SIGKILL".
87+
reload_container (bool, optional): Reload the container object after killing it. Defaults to True.
88+
"""
5989
if self.container.status == "running":
6090
self.container.kill(signal=signal)
6191
if reload_container:
6292
self.container.reload()
6393

6494
def restart(self, reload_container: bool = True, force: bool = False) -> None:
95+
"""Restart the underlying container.
96+
97+
Args:
98+
reload_container (bool, optional): Reload the container object after restarting it. Defaults to True.
99+
force (bool, optional): Kill the container before restarting it. Defaults to False.
100+
"""
65101
if force:
102+
# Use SIGTERM to allow the container to gracefully shutdown
66103
self.kill(signal="SIGTERM", reload_container=reload_container)
67104
self.container.restart(timeout=CONTAINER_TIMEOUT)
68105
if reload_container:
@@ -71,19 +108,41 @@ def restart(self, reload_container: bool = True, force: bool = False) -> None:
71108
self.app.set_current()
72109

73110
def teardown(self) -> None:
111+
"""Teardown the node."""
74112
self.container.teardown()
75113

76114
def wait_for_log(self, log: str, message: str = "", timeout: int = RESULT_TIMEOUT) -> None:
115+
"""Wait for a log to appear in the container.
116+
117+
Args:
118+
log (str): Log to wait for.
119+
message (str, optional): Message to display while waiting. Defaults to "".
120+
timeout (int, optional): Timeout in seconds. Defaults to RESULT_TIMEOUT.
121+
"""
77122
message = message or f"Waiting for worker container '{self.name()}' to log -> {log}"
78123
wait_for_callable(message=message, func=lambda: log in self.logs(), timeout=timeout)
79124

80125
def assert_log_exists(self, log: str, message: str = "", timeout: int = RESULT_TIMEOUT) -> None:
126+
"""Assert that a log exists in the container.
127+
128+
Args:
129+
log (str): Log to assert.
130+
message (str, optional): Message to display while waiting. Defaults to "".
131+
timeout (int, optional): Timeout in seconds. Defaults to RESULT_TIMEOUT.
132+
"""
81133
try:
82134
self.wait_for_log(log, message, timeout)
83135
except pytest_docker_tools.exceptions.TimeoutError:
84136
assert False, f"Worker container '{self.name()}' did not log -> {log} within {timeout} seconds"
85137

86138
def assert_log_does_not_exist(self, log: str, message: str = "", timeout: int = 1) -> None:
139+
"""Assert that a log does not exist in the container.
140+
141+
Args:
142+
log (str): Log to assert.
143+
message (str, optional): Message to display while waiting. Defaults to "".
144+
timeout (int, optional): Timeout in seconds. Defaults to 1.
145+
"""
87146
message = message or f"Waiting for worker container '{self.name()}' to not log -> {log}"
88147
try:
89148
self.wait_for_log(log, message, timeout)
@@ -93,7 +152,24 @@ def assert_log_does_not_exist(self, log: str, message: str = "", timeout: int =
93152

94153

95154
class CeleryTestCluster:
155+
"""CeleryTestCluster is a collection of CeleryTestNodes. It is used to
156+
collect the test nodes into a single object for easier management.
157+
158+
Responsibility Scope:
159+
The cluster's responsibility is to define which nodes will be used for
160+
the test.
161+
"""
162+
96163
def __init__(self, *nodes: tuple[CeleryTestNode | CeleryTestContainer]) -> None:
164+
"""Setup the base components of a CeleryTestCluster.
165+
166+
Args:
167+
*nodes (tuple[CeleryTestNode | CeleryTestContainer]): Nodes to use for the cluster.
168+
169+
Raises:
170+
ValueError: At least one node is required.
171+
TypeError: All nodes must be CeleryTestNode or CeleryTestContainer
172+
"""
97173
if not nodes:
98174
raise ValueError("At least one node is required")
99175
if len(nodes) == 1 and isinstance(nodes[0], list):
@@ -105,10 +181,16 @@ def __init__(self, *nodes: tuple[CeleryTestNode | CeleryTestContainer]) -> None:
105181

106182
@property
107183
def nodes(self) -> tuple[CeleryTestNode]:
184+
"""Get the nodes of the cluster."""
108185
return self._nodes
109186

110187
@nodes.setter
111188
def nodes(self, nodes: tuple[CeleryTestNode | CeleryTestContainer]) -> None:
189+
"""Set the nodes of the cluster.
190+
191+
Args:
192+
nodes (tuple[CeleryTestNode | CeleryTestContainer]): Nodes to use for the cluster.
193+
"""
112194
self._nodes = self._set_nodes(*nodes) # type: ignore
113195

114196
def __iter__(self) -> Iterator[CeleryTestNode]:
@@ -120,15 +202,16 @@ def __getitem__(self, index: Any) -> CeleryTestNode:
120202
def __len__(self) -> int:
121203
return len(self.nodes)
122204

123-
def __eq__(self, __value: object) -> bool:
124-
if isinstance(__value, CeleryTestCluster):
205+
def __eq__(self, other: object) -> bool:
206+
if isinstance(other, CeleryTestCluster):
125207
for node in self:
126-
if node not in __value:
208+
if node not in other:
127209
return False
128210
return False
129211

130212
@classmethod
131213
def default_config(cls) -> dict:
214+
"""Default cluster configurations if not overridden by the user."""
132215
return {}
133216

134217
@abstractmethod
@@ -137,6 +220,15 @@ def _set_nodes(
137220
*nodes: tuple[CeleryTestNode | CeleryTestContainer],
138221
node_cls: type[CeleryTestNode] = CeleryTestNode,
139222
) -> tuple[CeleryTestNode]:
223+
"""Set the nodes of the cluster.
224+
225+
Args:
226+
*nodes (tuple[CeleryTestNode | CeleryTestContainer]): Nodes to use for the cluster.
227+
node_cls (type[CeleryTestNode], optional): Node class to use. Defaults to CeleryTestNode.
228+
229+
Returns:
230+
tuple[CeleryTestNode]: Nodes to use for the cluster.
231+
"""
140232
return tuple(
141233
node_cls(node)
142234
if isinstance(
@@ -148,16 +240,19 @@ def _set_nodes(
148240
) # type: ignore
149241

150242
def ready(self) -> bool:
243+
"""Waits until the cluster is ready or raise an exception if any of the
244+
nodes fail to boot up."""
151245
return all(node.ready() for node in self)
152246

153247
def config(self, *args: tuple, **kwargs: dict) -> dict:
248+
"""Compile the configurations required for Celery from this cluster."""
154249
config = [node.container.celeryconfig for node in self]
155250
return {
156251
"urls": [c["url"] for c in config],
157252
"local_urls": [c["local_url"] for c in config],
158253
}
159254

160255
def teardown(self) -> None:
161-
# Do not need to call teardown on the nodes
162-
# but only tear down self
163-
pass
256+
"""Teardown the cluster."""
257+
# Nodes teardown themselves, so we just need to clear the cluster
258+
# if there is any cleanup to do

src/pytest_celery/api/broker.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77

88

99
class CeleryTestBroker(CeleryTestNode):
10+
"""CeleryTestBroker is specialized node type for handling celery brokers
11+
nodes. It is used to encapsulate a broker instance.
12+
13+
Responsibility Scope:
14+
Handling broker specific requirements and configuration.
15+
"""
16+
1017
@classmethod
1118
def default_config(cls) -> dict:
1219
return {
@@ -23,6 +30,14 @@ def restart(self, reload_container: bool = True, force: bool = False) -> None:
2330

2431

2532
class CeleryBrokerCluster(CeleryTestCluster):
33+
"""CeleryBrokerCluster is a specialized cluster type for handling celery
34+
brokers. It is used to define which broker instances are available for the
35+
test.
36+
37+
Responsibility Scope:
38+
Provude useful methods for managing a cluster of celery brokers.
39+
"""
40+
2641
def __init__(self, *brokers: tuple[CeleryTestBroker | CeleryTestContainer]) -> None:
2742
super().__init__(*brokers)
2843

0 commit comments

Comments
 (0)