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.
Files changed (82) hide show
  1. {defstream-0.1.3.5 → defstream-0.1.3.7}/DefStream.egg-info/PKG-INFO +1 -1
  2. {defstream-0.1.3.5 → defstream-0.1.3.7}/DefStream.egg-info/SOURCES.txt +3 -1
  3. {defstream-0.1.3.5 → defstream-0.1.3.7}/PKG-INFO +1 -1
  4. defstream-0.1.3.7/src/Worker/Startup.py +135 -0
  5. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Worker/Worker.py +2 -0
  6. defstream-0.1.3.7/test/test_worker_startup.py +390 -0
  7. {defstream-0.1.3.5 → defstream-0.1.3.7}/.github/workflows/ci.yml +0 -0
  8. {defstream-0.1.3.5 → defstream-0.1.3.7}/.gitignore +0 -0
  9. {defstream-0.1.3.5 → defstream-0.1.3.7}/DefStream.egg-info/dependency_links.txt +0 -0
  10. {defstream-0.1.3.5 → defstream-0.1.3.7}/DefStream.egg-info/requires.txt +0 -0
  11. {defstream-0.1.3.5 → defstream-0.1.3.7}/DefStream.egg-info/top_level.txt +0 -0
  12. {defstream-0.1.3.5 → defstream-0.1.3.7}/LICENSE +0 -0
  13. {defstream-0.1.3.5 → defstream-0.1.3.7}/README.en.md +0 -0
  14. {defstream-0.1.3.5 → defstream-0.1.3.7}/README.md +0 -0
  15. {defstream-0.1.3.5 → defstream-0.1.3.7}/sample/Broker/Sample.py +0 -0
  16. {defstream-0.1.3.5 → defstream-0.1.3.7}/sample/Worker/RabbitMQ/Client.py +0 -0
  17. {defstream-0.1.3.5 → defstream-0.1.3.7}/sample/Worker/RabbitMQ/Tasks.py +0 -0
  18. {defstream-0.1.3.5 → defstream-0.1.3.7}/sample/Worker/RabbitMQ/Worker.py +0 -0
  19. {defstream-0.1.3.5 → defstream-0.1.3.7}/sample/Worker/Redis/Sample.py +0 -0
  20. {defstream-0.1.3.5 → defstream-0.1.3.7}/sample/Worker/Sample.py +0 -0
  21. {defstream-0.1.3.5 → defstream-0.1.3.7}/setup.cfg +0 -0
  22. {defstream-0.1.3.5 → defstream-0.1.3.7}/setup.py +0 -0
  23. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Broker/Configuration.py +0 -0
  24. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Broker/Connection.py +0 -0
  25. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Broker/Consumer.py +0 -0
  26. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Broker/Errors.py +0 -0
  27. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Broker/Event.py +0 -0
  28. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Broker/Implements/__init__.py +0 -0
  29. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Broker/Policies.py +0 -0
  30. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Broker/Protocol.py +0 -0
  31. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Broker/__init__.py +0 -0
  32. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Descriptor/DataObject.py +0 -0
  33. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Descriptor/Types.py +0 -0
  34. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Descriptor/Value.py +0 -0
  35. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Descriptor/__init__.py +0 -0
  36. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Inspector/Extension.py +0 -0
  37. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Inspector/Extensions/Console.html +0 -0
  38. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Inspector/Extensions/Console.py +0 -0
  39. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Inspector/Extensions/Dashboard.html +0 -0
  40. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Inspector/Extensions/Dashboard.py +0 -0
  41. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Inspector/Extensions/HealthCheck.py +0 -0
  42. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Inspector/Extensions/Metrics.py +0 -0
  43. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Inspector/Extensions/Specifications.py +0 -0
  44. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Inspector/Extensions/__init__.py +0 -0
  45. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Inspector/Inspector.py +0 -0
  46. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Inspector/Response.py +0 -0
  47. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Inspector/__init__.py +0 -0
  48. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Pattern.py +0 -0
  49. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Patterns/Array.py +0 -0
  50. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Patterns/Common.py +0 -0
  51. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Patterns/Compare.py +0 -0
  52. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Patterns/Condition.py +0 -0
  53. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Patterns/DataObject.py +0 -0
  54. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Patterns/DateTime.py +0 -0
  55. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Patterns/Number.py +0 -0
  56. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Patterns/Object.py +0 -0
  57. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Patterns/Size.py +0 -0
  58. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Patterns/String.py +0 -0
  59. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Patterns/Type.py +0 -0
  60. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Patterns/__init__.py +0 -0
  61. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Validate.py +0 -0
  62. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/Validator.py +0 -0
  63. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Validator/__init__.py +0 -0
  64. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Worker/Executor.py +0 -0
  65. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Worker/Message.py +0 -0
  66. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Worker/Pool.py +0 -0
  67. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Worker/Run.py +0 -0
  68. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Worker/Specification.py +0 -0
  69. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/Worker/__init__.py +0 -0
  70. {defstream-0.1.3.5 → defstream-0.1.3.7}/src/__init__.py +0 -0
  71. {defstream-0.1.3.5 → defstream-0.1.3.7}/test/test_dashboard.py +0 -0
  72. {defstream-0.1.3.5 → defstream-0.1.3.7}/test/test_dashboard_ui.cjs +0 -0
  73. {defstream-0.1.3.5 → defstream-0.1.3.7}/test/test_inspector.py +0 -0
  74. {defstream-0.1.3.5 → defstream-0.1.3.7}/test/test_inspector_console.py +0 -0
  75. {defstream-0.1.3.5 → defstream-0.1.3.7}/test/test_lifecycle_hooks.py +0 -0
  76. {defstream-0.1.3.5 → defstream-0.1.3.7}/test/test_pool_callbacks.py +0 -0
  77. {defstream-0.1.3.5 → defstream-0.1.3.7}/test/test_process_signals.py +0 -0
  78. {defstream-0.1.3.5 → defstream-0.1.3.7}/test/test_protocol_contracts.py +0 -0
  79. {defstream-0.1.3.5 → defstream-0.1.3.7}/test/test_runner_hooks.py +0 -0
  80. {defstream-0.1.3.5 → defstream-0.1.3.7}/test/test_specifications.py +0 -0
  81. {defstream-0.1.3.5 → defstream-0.1.3.7}/test/test_task_messages.py +0 -0
  82. {defstream-0.1.3.5 → defstream-0.1.3.7}/test/test_worker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: DefStream
3
- Version: 0.1.3.5
3
+ Version: 0.1.3.7
4
4
  Summary: Synchronous message processing with pluggable broker adapters
5
5
  Home-page: https://github.com/yong5eon/DefStream
6
6
  Author: Heo Yongseon
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: DefStream
3
- Version: 0.1.3.5
3
+ Version: 0.1.3.7
4
4
  Summary: Synchronous message processing with pluggable broker adapters
5
5
  Home-page: https://github.com/yong5eon/DefStream
6
6
  Author: Heo Yongseon
@@ -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