DefStream 0.1.3.5__tar.gz → 0.1.3.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {defstream-0.1.3.5 → defstream-0.1.3.7}/DefStream.egg-info/PKG-INFO +1 -1
- {defstream-0.1.3.5 → defstream-0.1.3.7}/DefStream.egg-info/SOURCES.txt +3 -1
- {defstream-0.1.3.5 → defstream-0.1.3.7}/PKG-INFO +1 -1
- defstream-0.1.3.7/src/Worker/Startup.py +135 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Worker/Worker.py +2 -0
- defstream-0.1.3.7/test/test_worker_startup.py +390 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/.github/workflows/ci.yml +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/.gitignore +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/DefStream.egg-info/dependency_links.txt +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/DefStream.egg-info/requires.txt +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/DefStream.egg-info/top_level.txt +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/LICENSE +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/README.en.md +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/README.md +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/sample/Broker/Sample.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/sample/Worker/RabbitMQ/Client.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/sample/Worker/RabbitMQ/Tasks.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/sample/Worker/RabbitMQ/Worker.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/sample/Worker/Redis/Sample.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/sample/Worker/Sample.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/setup.cfg +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/setup.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Broker/Configuration.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Broker/Connection.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Broker/Consumer.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Broker/Errors.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Broker/Event.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Broker/Implements/__init__.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Broker/Policies.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Broker/Protocol.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Broker/__init__.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Descriptor/DataObject.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Descriptor/Types.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Descriptor/Value.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Descriptor/__init__.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Inspector/Extension.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Inspector/Extensions/Console.html +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Inspector/Extensions/Console.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Inspector/Extensions/Dashboard.html +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Inspector/Extensions/Dashboard.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Inspector/Extensions/HealthCheck.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Inspector/Extensions/Metrics.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Inspector/Extensions/Specifications.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Inspector/Extensions/__init__.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Inspector/Inspector.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Inspector/Response.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Inspector/__init__.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Pattern.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Patterns/Array.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Patterns/Common.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Patterns/Compare.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Patterns/Condition.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Patterns/DataObject.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Patterns/DateTime.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Patterns/Number.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Patterns/Object.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Patterns/Size.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Patterns/String.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Patterns/Type.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Patterns/__init__.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Validate.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Validator.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/__init__.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Worker/Executor.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Worker/Message.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Worker/Pool.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Worker/Run.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Worker/Specification.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Worker/__init__.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/src/__init__.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/test/test_dashboard.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/test/test_dashboard_ui.cjs +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/test/test_inspector.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/test/test_inspector_console.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/test/test_lifecycle_hooks.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/test/test_pool_callbacks.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/test/test_process_signals.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/test/test_protocol_contracts.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/test/test_runner_hooks.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/test/test_specifications.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/test/test_task_messages.py +0 -0
- {defstream-0.1.3.5 → defstream-0.1.3.7}/test/test_worker.py +0 -0
|
@@ -62,6 +62,7 @@ src/Worker/Message.py
|
|
|
62
62
|
src/Worker/Pool.py
|
|
63
63
|
src/Worker/Run.py
|
|
64
64
|
src/Worker/Specification.py
|
|
65
|
+
src/Worker/Startup.py
|
|
65
66
|
src/Worker/Worker.py
|
|
66
67
|
src/Worker/__init__.py
|
|
67
68
|
test/test_dashboard.py
|
|
@@ -75,4 +76,5 @@ test/test_protocol_contracts.py
|
|
|
75
76
|
test/test_runner_hooks.py
|
|
76
77
|
test/test_specifications.py
|
|
77
78
|
test/test_task_messages.py
|
|
78
|
-
test/test_worker.py
|
|
79
|
+
test/test_worker.py
|
|
80
|
+
test/test_worker_startup.py
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from os import getpid
|
|
3
|
+
from typing import (
|
|
4
|
+
Any,
|
|
5
|
+
TYPE_CHECKING,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from .Worker import Worker
|
|
10
|
+
|
|
11
|
+
__all__ = (
|
|
12
|
+
'startup',
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
LOGO = r"""
|
|
17
|
+
▄▄▄▄▄▄ ▄▄ ▄▄▄▄▄▄▄
|
|
18
|
+
███▀▀██▄ ██ █████▀▀▀ ██
|
|
19
|
+
███ ███ ▄█▀█▄ ▀██▀ ▀████▄ ▀██▀▀ ████▄ ▄█▀█▄ ▀▀█▄ ███▄███▄
|
|
20
|
+
███ ███ ██▄█▀ ██ ▀████ ██ ██ ▀▀ ██▄█▀ ▄█▀██ ██ ██ ██
|
|
21
|
+
██████▀ ▀█▄▄▄ ██ ███████▀ ██ ██ ▀█▄▄▄ ▀█▄██ ██ ██ ██
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def subscription_value(
|
|
26
|
+
parameters: Any,
|
|
27
|
+
name: str,
|
|
28
|
+
) -> Any:
|
|
29
|
+
if isinstance(
|
|
30
|
+
parameters,
|
|
31
|
+
str,
|
|
32
|
+
):
|
|
33
|
+
return parameters if name == 'queue' else None
|
|
34
|
+
if isinstance(
|
|
35
|
+
parameters,
|
|
36
|
+
Mapping,
|
|
37
|
+
):
|
|
38
|
+
return parameters.get(name)
|
|
39
|
+
return getattr(
|
|
40
|
+
parameters,
|
|
41
|
+
name,
|
|
42
|
+
None,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def startup(worker: 'Worker') -> None:
|
|
47
|
+
broker = type(worker.broker)
|
|
48
|
+
broker_name = broker.__qualname__
|
|
49
|
+
for base in broker.__mro__:
|
|
50
|
+
if '.Broker.Implements.' in base.__module__:
|
|
51
|
+
broker_name = base.__module__.split('.Broker.Implements.')[1].split('.')[0]
|
|
52
|
+
break
|
|
53
|
+
pool = type(worker.pool)
|
|
54
|
+
conf = getattr(
|
|
55
|
+
worker.broker,
|
|
56
|
+
'conf',
|
|
57
|
+
None,
|
|
58
|
+
)
|
|
59
|
+
host = getattr(
|
|
60
|
+
conf,
|
|
61
|
+
'host',
|
|
62
|
+
None,
|
|
63
|
+
)
|
|
64
|
+
port = getattr(
|
|
65
|
+
conf,
|
|
66
|
+
'port',
|
|
67
|
+
None,
|
|
68
|
+
)
|
|
69
|
+
if isinstance(
|
|
70
|
+
host,
|
|
71
|
+
str,
|
|
72
|
+
):
|
|
73
|
+
address = f'[{host}]' if ':' in host and not host.startswith('[') else host
|
|
74
|
+
broker_name += f'://{address}'
|
|
75
|
+
if isinstance(
|
|
76
|
+
port,
|
|
77
|
+
(
|
|
78
|
+
str,
|
|
79
|
+
int,
|
|
80
|
+
),
|
|
81
|
+
):
|
|
82
|
+
broker_name += f':{port}'
|
|
83
|
+
vhost = getattr(
|
|
84
|
+
conf,
|
|
85
|
+
'vhost',
|
|
86
|
+
None,
|
|
87
|
+
)
|
|
88
|
+
if isinstance(
|
|
89
|
+
vhost,
|
|
90
|
+
str,
|
|
91
|
+
) and vhost:
|
|
92
|
+
broker_name += '/' + vhost.lstrip('/')
|
|
93
|
+
lines = [
|
|
94
|
+
LOGO.rstrip(),
|
|
95
|
+
'',
|
|
96
|
+
'Starting Worker',
|
|
97
|
+
f' PID: {getpid()}',
|
|
98
|
+
f' Broker: {broker_name}',
|
|
99
|
+
]
|
|
100
|
+
lines.extend((
|
|
101
|
+
f' Pool: {pool.__name__} (concurrency={worker.pool.size()})',
|
|
102
|
+
f' Tasks: {len(worker.tasks)} tasks',
|
|
103
|
+
))
|
|
104
|
+
queues = []
|
|
105
|
+
for task in worker.tasks:
|
|
106
|
+
name = task.__qualname__
|
|
107
|
+
queue = subscription_value(
|
|
108
|
+
task.parameters,
|
|
109
|
+
'queue',
|
|
110
|
+
)
|
|
111
|
+
lines.append(f' - {name}')
|
|
112
|
+
lines.append(f' queue={queue!r}' if queue is not None else ' queue=<adapter-defined>')
|
|
113
|
+
if queue is not None and queue not in queues:
|
|
114
|
+
queues.append(queue)
|
|
115
|
+
for option in (
|
|
116
|
+
'group',
|
|
117
|
+
'qos',
|
|
118
|
+
'offset',
|
|
119
|
+
'error',
|
|
120
|
+
):
|
|
121
|
+
value = subscription_value(
|
|
122
|
+
task.parameters,
|
|
123
|
+
option,
|
|
124
|
+
)
|
|
125
|
+
if value is not None:
|
|
126
|
+
lines.append(f' {option}={value!r}')
|
|
127
|
+
policy = type(task.on_error).__name__ if task.on_error is not None else 'broker default'
|
|
128
|
+
lines.append(f' on_error={policy}')
|
|
129
|
+
lines.append(f' Queues: {len(queues)} queues')
|
|
130
|
+
lines.extend(f' - {queue!r}' for queue in queues)
|
|
131
|
+
lines.append('\n')
|
|
132
|
+
print(
|
|
133
|
+
'\n'.join(lines),
|
|
134
|
+
flush=True,
|
|
135
|
+
)
|
|
@@ -32,6 +32,7 @@ from .Pool import (
|
|
|
32
32
|
)
|
|
33
33
|
from .Run import Run
|
|
34
34
|
from .Executor import run_cleanups
|
|
35
|
+
from .Startup import startup
|
|
35
36
|
|
|
36
37
|
__all__ = (
|
|
37
38
|
'Worker',
|
|
@@ -121,6 +122,7 @@ class Worker:
|
|
|
121
122
|
raise RuntimeError('Register at least one Run before starting')
|
|
122
123
|
self.started = True
|
|
123
124
|
try:
|
|
125
|
+
startup(self)
|
|
124
126
|
if self.inspector is not None:
|
|
125
127
|
self.inspector.start(self)
|
|
126
128
|
self.broker.connect()
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
from contextlib import redirect_stdout
|
|
2
|
+
from io import StringIO
|
|
3
|
+
from unittest import (
|
|
4
|
+
TestCase,
|
|
5
|
+
main,
|
|
6
|
+
)
|
|
7
|
+
from unittest.mock import Mock
|
|
8
|
+
|
|
9
|
+
from DefStream.Broker.Implements.RabbitMQ import (
|
|
10
|
+
Configuration as RabbitConfiguration,
|
|
11
|
+
Connection as RabbitConnection,
|
|
12
|
+
Reject,
|
|
13
|
+
Subscription as RabbitSubscription,
|
|
14
|
+
)
|
|
15
|
+
from DefStream.Broker.Implements.Redis import (
|
|
16
|
+
Configuration as RedisConfiguration,
|
|
17
|
+
Connection as RedisConnection,
|
|
18
|
+
Subscription as RedisSubscription,
|
|
19
|
+
)
|
|
20
|
+
from DefStream.Worker import (
|
|
21
|
+
Run,
|
|
22
|
+
Worker,
|
|
23
|
+
)
|
|
24
|
+
from DefStream.Worker.Startup import (
|
|
25
|
+
LOGO,
|
|
26
|
+
startup,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class StartupTests(TestCase):
|
|
31
|
+
def worker(
|
|
32
|
+
self,
|
|
33
|
+
parameters,
|
|
34
|
+
):
|
|
35
|
+
class Task(
|
|
36
|
+
Run,
|
|
37
|
+
parameters=parameters,
|
|
38
|
+
on_error=Reject(),
|
|
39
|
+
):
|
|
40
|
+
def run(self):
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
pool = Mock()
|
|
44
|
+
pool.size.return_value = 4
|
|
45
|
+
worker = Worker(
|
|
46
|
+
Mock(),
|
|
47
|
+
pool,
|
|
48
|
+
)
|
|
49
|
+
worker.add(Task)
|
|
50
|
+
return worker
|
|
51
|
+
|
|
52
|
+
def test_banner_supports_subscription_formats_and_options(self):
|
|
53
|
+
for parameters, options in (
|
|
54
|
+
(
|
|
55
|
+
RabbitSubscription(
|
|
56
|
+
queue='rabbit.jobs',
|
|
57
|
+
qos=0,
|
|
58
|
+
offset=0,
|
|
59
|
+
error='rabbit.errors',
|
|
60
|
+
),
|
|
61
|
+
(
|
|
62
|
+
"queue='rabbit.jobs'",
|
|
63
|
+
'qos=0',
|
|
64
|
+
'offset=0',
|
|
65
|
+
"error='rabbit.errors'",
|
|
66
|
+
),
|
|
67
|
+
),
|
|
68
|
+
(
|
|
69
|
+
RedisSubscription(
|
|
70
|
+
queue='redis.jobs',
|
|
71
|
+
group='workers',
|
|
72
|
+
),
|
|
73
|
+
(
|
|
74
|
+
"queue='redis.jobs'",
|
|
75
|
+
"group='workers'",
|
|
76
|
+
),
|
|
77
|
+
),
|
|
78
|
+
(
|
|
79
|
+
{
|
|
80
|
+
'queue': 'mapping.jobs',
|
|
81
|
+
'qos': 2,
|
|
82
|
+
'password': 'do-not-print',
|
|
83
|
+
},
|
|
84
|
+
(
|
|
85
|
+
"queue='mapping.jobs'",
|
|
86
|
+
'qos=2',
|
|
87
|
+
),
|
|
88
|
+
),
|
|
89
|
+
(
|
|
90
|
+
'string.jobs',
|
|
91
|
+
("queue='string.jobs'",),
|
|
92
|
+
),
|
|
93
|
+
):
|
|
94
|
+
with self.subTest(parameters=parameters):
|
|
95
|
+
worker = self.worker(parameters)
|
|
96
|
+
stream = StringIO()
|
|
97
|
+
with redirect_stdout(stream):
|
|
98
|
+
worker.run()
|
|
99
|
+
output = stream.getvalue()
|
|
100
|
+
for expected in (
|
|
101
|
+
LOGO.rstrip(),
|
|
102
|
+
'Starting Worker',
|
|
103
|
+
'concurrency=4',
|
|
104
|
+
'Tasks: 1 tasks',
|
|
105
|
+
'StartupTests.worker.<locals>.Task',
|
|
106
|
+
'on_error=Reject',
|
|
107
|
+
'Queues: 1 queues',
|
|
108
|
+
*options,
|
|
109
|
+
):
|
|
110
|
+
self.assertIn(
|
|
111
|
+
expected,
|
|
112
|
+
output,
|
|
113
|
+
)
|
|
114
|
+
self.assertNotIn(
|
|
115
|
+
'do-not-print',
|
|
116
|
+
output,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def test_output_precedes_connection_setup_subscription_and_consumption(self):
|
|
120
|
+
worker = self.worker('jobs')
|
|
121
|
+
stream = StringIO()
|
|
122
|
+
observed = []
|
|
123
|
+
|
|
124
|
+
def check_startup():
|
|
125
|
+
self.assertTrue(stream.getvalue().startswith(LOGO.rstrip() + '\n\nStarting Worker\n'))
|
|
126
|
+
self.assertIn(
|
|
127
|
+
"queue='jobs'",
|
|
128
|
+
stream.getvalue(),
|
|
129
|
+
)
|
|
130
|
+
self.assertFalse(worker.running.is_set())
|
|
131
|
+
observed.append('preparing')
|
|
132
|
+
|
|
133
|
+
def check_consumption():
|
|
134
|
+
self.assertIn(
|
|
135
|
+
" Queues: 1 queues\n - 'jobs'\n",
|
|
136
|
+
stream.getvalue(),
|
|
137
|
+
)
|
|
138
|
+
self.assertTrue(worker.running.is_set())
|
|
139
|
+
observed.append('consuming')
|
|
140
|
+
|
|
141
|
+
worker.broker.connect.side_effect = check_startup
|
|
142
|
+
worker.setups = (lambda current: check_startup(),)
|
|
143
|
+
consumer = worker.broker.consumer.return_value
|
|
144
|
+
consumer.subs.side_effect = lambda *args: check_startup()
|
|
145
|
+
consumer.run.side_effect = check_consumption
|
|
146
|
+
with redirect_stdout(stream):
|
|
147
|
+
worker.run()
|
|
148
|
+
self.assertEqual(
|
|
149
|
+
observed,
|
|
150
|
+
[
|
|
151
|
+
'preparing',
|
|
152
|
+
'preparing',
|
|
153
|
+
'preparing',
|
|
154
|
+
'consuming',
|
|
155
|
+
],
|
|
156
|
+
)
|
|
157
|
+
self.assertEqual(
|
|
158
|
+
stream.getvalue().count('Starting Worker'),
|
|
159
|
+
1,
|
|
160
|
+
)
|
|
161
|
+
with redirect_stdout(stream), self.assertRaises(RuntimeError):
|
|
162
|
+
worker.run()
|
|
163
|
+
self.assertEqual(
|
|
164
|
+
stream.getvalue().count('Starting Worker'),
|
|
165
|
+
1,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def test_failed_preparation_prints_banner_but_does_not_consume(self):
|
|
169
|
+
for stage in (
|
|
170
|
+
'connect',
|
|
171
|
+
'subscribe',
|
|
172
|
+
):
|
|
173
|
+
with self.subTest(stage=stage):
|
|
174
|
+
worker = self.worker('jobs')
|
|
175
|
+
consumer = worker.broker.consumer.return_value
|
|
176
|
+
action = worker.broker.connect if stage == 'connect' else consumer.subs
|
|
177
|
+
action.side_effect = RuntimeError('preparation failed')
|
|
178
|
+
stream = StringIO()
|
|
179
|
+
with redirect_stdout(stream), self.assertRaisesRegex(
|
|
180
|
+
RuntimeError,
|
|
181
|
+
'preparation failed',
|
|
182
|
+
):
|
|
183
|
+
worker.run()
|
|
184
|
+
self.assertIn(
|
|
185
|
+
'Starting Worker',
|
|
186
|
+
stream.getvalue(),
|
|
187
|
+
)
|
|
188
|
+
consumer.run.assert_not_called()
|
|
189
|
+
worker.pool.stop.assert_called_once_with()
|
|
190
|
+
worker.broker.close.assert_called_once_with()
|
|
191
|
+
|
|
192
|
+
def test_stopped_worker_prints_banner_but_does_not_consume(self):
|
|
193
|
+
worker = self.worker('jobs')
|
|
194
|
+
worker.stop()
|
|
195
|
+
stream = StringIO()
|
|
196
|
+
with redirect_stdout(stream):
|
|
197
|
+
worker.run()
|
|
198
|
+
self.assertIn(
|
|
199
|
+
'Starting Worker',
|
|
200
|
+
stream.getvalue(),
|
|
201
|
+
)
|
|
202
|
+
worker.broker.consumer.return_value.run.assert_not_called()
|
|
203
|
+
|
|
204
|
+
def test_custom_subscription_is_not_dumped_or_rejected_by_banner(self):
|
|
205
|
+
worker = self.worker(object())
|
|
206
|
+
stream = StringIO()
|
|
207
|
+
with redirect_stdout(stream):
|
|
208
|
+
worker.run()
|
|
209
|
+
self.assertIn(
|
|
210
|
+
'queue=<adapter-defined>',
|
|
211
|
+
stream.getvalue(),
|
|
212
|
+
)
|
|
213
|
+
worker.broker.consumer.return_value.run.assert_called_once_with()
|
|
214
|
+
|
|
215
|
+
def test_all_tasks_are_listed_and_known_queues_are_deduplicated(self):
|
|
216
|
+
worker = self.worker('jobs')
|
|
217
|
+
|
|
218
|
+
class Shared(
|
|
219
|
+
Run,
|
|
220
|
+
parameters={'queue': 'jobs'},
|
|
221
|
+
):
|
|
222
|
+
def run(self):
|
|
223
|
+
pass
|
|
224
|
+
|
|
225
|
+
class Other(
|
|
226
|
+
Shared,
|
|
227
|
+
parameters={'queue': 'other.jobs'},
|
|
228
|
+
):
|
|
229
|
+
pass
|
|
230
|
+
|
|
231
|
+
worker.add(Shared)
|
|
232
|
+
worker.add(Other)
|
|
233
|
+
stream = StringIO()
|
|
234
|
+
with redirect_stdout(stream):
|
|
235
|
+
worker.run()
|
|
236
|
+
output = stream.getvalue()
|
|
237
|
+
self.assertIn(
|
|
238
|
+
'Tasks: 3 tasks',
|
|
239
|
+
output,
|
|
240
|
+
)
|
|
241
|
+
self.assertIn(
|
|
242
|
+
"Queues: 2 queues\n - 'jobs'\n - 'other.jobs'",
|
|
243
|
+
output,
|
|
244
|
+
)
|
|
245
|
+
for task in worker.tasks:
|
|
246
|
+
self.assertIn(
|
|
247
|
+
task.__qualname__,
|
|
248
|
+
output,
|
|
249
|
+
)
|
|
250
|
+
self.assertNotIn(
|
|
251
|
+
task.__module__ + '.' + task.__qualname__,
|
|
252
|
+
output,
|
|
253
|
+
)
|
|
254
|
+
self.assertIn(
|
|
255
|
+
'on_error=broker default',
|
|
256
|
+
output,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
def test_missing_tasks_do_not_print_a_startup_banner(self):
|
|
260
|
+
worker = Worker(
|
|
261
|
+
Mock(),
|
|
262
|
+
Mock(),
|
|
263
|
+
)
|
|
264
|
+
stream = StringIO()
|
|
265
|
+
with redirect_stdout(stream), self.assertRaisesRegex(
|
|
266
|
+
RuntimeError,
|
|
267
|
+
'Register at least one Run',
|
|
268
|
+
):
|
|
269
|
+
worker.run()
|
|
270
|
+
self.assertEqual(
|
|
271
|
+
stream.getvalue(),
|
|
272
|
+
'',
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
def test_broker_summary_shows_adapter_and_only_endpoint_fields(self):
|
|
276
|
+
for broker, expected in (
|
|
277
|
+
(
|
|
278
|
+
RabbitConnection(RabbitConfiguration(
|
|
279
|
+
host='rabbit.local',
|
|
280
|
+
port=5672,
|
|
281
|
+
vhost='/jobs',
|
|
282
|
+
username='private-user',
|
|
283
|
+
password='private-password',
|
|
284
|
+
)),
|
|
285
|
+
' Broker: RabbitMQ://rabbit.local:5672/jobs\n',
|
|
286
|
+
),
|
|
287
|
+
(
|
|
288
|
+
RedisConnection(RedisConfiguration(
|
|
289
|
+
host='redis.local',
|
|
290
|
+
port=6379,
|
|
291
|
+
username='private-user',
|
|
292
|
+
password='private-password',
|
|
293
|
+
)),
|
|
294
|
+
' Broker: Redis://redis.local:6379',
|
|
295
|
+
),
|
|
296
|
+
(
|
|
297
|
+
RabbitConnection(RabbitConfiguration(
|
|
298
|
+
host='127.0.0.1',
|
|
299
|
+
port=5672,
|
|
300
|
+
)),
|
|
301
|
+
' Broker: RabbitMQ://127.0.0.1:5672/\n',
|
|
302
|
+
),
|
|
303
|
+
(
|
|
304
|
+
RedisConnection(RedisConfiguration(
|
|
305
|
+
host='::1',
|
|
306
|
+
port=6379,
|
|
307
|
+
)),
|
|
308
|
+
' Broker: Redis://[::1]:6379',
|
|
309
|
+
),
|
|
310
|
+
):
|
|
311
|
+
with self.subTest(broker=type(broker)):
|
|
312
|
+
worker = self.worker('jobs')
|
|
313
|
+
worker.broker = broker
|
|
314
|
+
stream = StringIO()
|
|
315
|
+
with redirect_stdout(stream):
|
|
316
|
+
startup(worker)
|
|
317
|
+
output = stream.getvalue()
|
|
318
|
+
self.assertIn(
|
|
319
|
+
expected,
|
|
320
|
+
output,
|
|
321
|
+
)
|
|
322
|
+
for hidden in (
|
|
323
|
+
'private-user',
|
|
324
|
+
'private-password',
|
|
325
|
+
'DefStream.Broker.Implements',
|
|
326
|
+
' HOST:',
|
|
327
|
+
' PORT:',
|
|
328
|
+
'VHOST:',
|
|
329
|
+
):
|
|
330
|
+
self.assertNotIn(
|
|
331
|
+
hidden,
|
|
332
|
+
output,
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
def test_rabbitmq_vhost_is_appended_only_when_present(self):
|
|
336
|
+
for vhost, suffix in (
|
|
337
|
+
(
|
|
338
|
+
'jobs',
|
|
339
|
+
'/jobs',
|
|
340
|
+
),
|
|
341
|
+
(
|
|
342
|
+
'/jobs',
|
|
343
|
+
'/jobs',
|
|
344
|
+
),
|
|
345
|
+
(
|
|
346
|
+
'/',
|
|
347
|
+
'/',
|
|
348
|
+
),
|
|
349
|
+
(
|
|
350
|
+
'',
|
|
351
|
+
'',
|
|
352
|
+
),
|
|
353
|
+
(
|
|
354
|
+
None,
|
|
355
|
+
'',
|
|
356
|
+
),
|
|
357
|
+
):
|
|
358
|
+
with self.subTest(vhost=vhost):
|
|
359
|
+
conf = RabbitConfiguration(
|
|
360
|
+
host='127.0.0.1',
|
|
361
|
+
port=5672,
|
|
362
|
+
)
|
|
363
|
+
worker = self.worker('jobs')
|
|
364
|
+
worker.broker = RabbitConnection(conf)
|
|
365
|
+
conf.vhost = vhost
|
|
366
|
+
stream = StringIO()
|
|
367
|
+
with redirect_stdout(stream):
|
|
368
|
+
startup(worker)
|
|
369
|
+
self.assertIn(
|
|
370
|
+
f' Broker: RabbitMQ://127.0.0.1:5672{suffix}\n Pool:',
|
|
371
|
+
stream.getvalue(),
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
def test_custom_broker_without_configuration_uses_qualname(self):
|
|
375
|
+
class Broker:
|
|
376
|
+
pass
|
|
377
|
+
|
|
378
|
+
worker = self.worker('jobs')
|
|
379
|
+
worker.broker = Broker()
|
|
380
|
+
stream = StringIO()
|
|
381
|
+
with redirect_stdout(stream):
|
|
382
|
+
startup(worker)
|
|
383
|
+
self.assertIn(
|
|
384
|
+
f' Broker: {Broker.__qualname__}\n Pool:',
|
|
385
|
+
stream.getvalue(),
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
if __name__ == '__main__':
|
|
390
|
+
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|