p1-taskqueue 0.1.1__tar.gz → 0.1.3__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.
Potentially problematic release.
This version of p1-taskqueue might be problematic. Click here for more details.
- {p1_taskqueue-0.1.1 → p1_taskqueue-0.1.3}/PKG-INFO +1 -1
- {p1_taskqueue-0.1.1 → p1_taskqueue-0.1.3}/pyproject.toml +1 -1
- {p1_taskqueue-0.1.1 → p1_taskqueue-0.1.3}/src/p1_taskqueue.egg-info/PKG-INFO +1 -1
- {p1_taskqueue-0.1.1 → p1_taskqueue-0.1.3}/src/p1_taskqueue.egg-info/SOURCES.txt +2 -0
- p1_taskqueue-0.1.3/src/taskqueue/libs/__init__.py +0 -0
- p1_taskqueue-0.1.3/src/taskqueue/libs/helper_test.py +88 -0
- {p1_taskqueue-0.1.1 → p1_taskqueue-0.1.3}/tests/test_test_utils.py +21 -21
- {p1_taskqueue-0.1.1 → p1_taskqueue-0.1.3}/README.md +0 -0
- {p1_taskqueue-0.1.1 → p1_taskqueue-0.1.3}/setup.cfg +0 -0
- {p1_taskqueue-0.1.1 → p1_taskqueue-0.1.3}/src/p1_taskqueue.egg-info/dependency_links.txt +0 -0
- {p1_taskqueue-0.1.1 → p1_taskqueue-0.1.3}/src/p1_taskqueue.egg-info/requires.txt +0 -0
- {p1_taskqueue-0.1.1 → p1_taskqueue-0.1.3}/src/p1_taskqueue.egg-info/top_level.txt +0 -0
- {p1_taskqueue-0.1.1 → p1_taskqueue-0.1.3}/src/taskqueue/__init__.py +0 -0
- {p1_taskqueue-0.1.1 → p1_taskqueue-0.1.3}/src/taskqueue/celery_app.py +0 -0
- {p1_taskqueue-0.1.1 → p1_taskqueue-0.1.3}/src/taskqueue/cmanager.py +0 -0
- {p1_taskqueue-0.1.1 → p1_taskqueue-0.1.3}/tests/test_celery_app.py +0 -0
- {p1_taskqueue-0.1.1 → p1_taskqueue-0.1.3}/tests/test_cmanager.py +0 -0
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "p1-taskqueue"
|
|
7
7
|
# DO NOT CHANGE THIS VERSION - it gets automatically replaced by CI/CD with the git tag version
|
|
8
|
-
version = "0.1.
|
|
8
|
+
version = "0.1.3"
|
|
9
9
|
description = "A Task Queue Wrapper for Dekoruma Backend"
|
|
10
10
|
authors = [
|
|
11
11
|
{name = "Chalvin", email = "engineering@dekoruma.com"}
|
|
@@ -8,6 +8,8 @@ src/p1_taskqueue.egg-info/top_level.txt
|
|
|
8
8
|
src/taskqueue/__init__.py
|
|
9
9
|
src/taskqueue/celery_app.py
|
|
10
10
|
src/taskqueue/cmanager.py
|
|
11
|
+
src/taskqueue/libs/__init__.py
|
|
12
|
+
src/taskqueue/libs/helper_test.py
|
|
11
13
|
tests/test_celery_app.py
|
|
12
14
|
tests/test_cmanager.py
|
|
13
15
|
tests/test_test_utils.py
|
|
File without changes
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
from celery import current_app
|
|
5
|
+
from kombu.serialization import loads
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def clear_all_celery_queues():
|
|
11
|
+
app = current_app
|
|
12
|
+
all_queue_names = list(app.amqp.queues.keys())
|
|
13
|
+
with app.connection_for_read() as conn:
|
|
14
|
+
with conn.channel() as chan:
|
|
15
|
+
for queue_name in all_queue_names:
|
|
16
|
+
queue = app.amqp.queues[queue_name](chan)
|
|
17
|
+
queue.purge()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def celery_worker_burst(include_func_names: List[str], channel: str = "default"):
|
|
21
|
+
# This doesn't use celery as celery doesn't support filtering out functions
|
|
22
|
+
# this use kombu to get the message from the queue and then execute the task manually
|
|
23
|
+
app = current_app
|
|
24
|
+
included_set = set(include_func_names)
|
|
25
|
+
processed_count = 0
|
|
26
|
+
executed_count = 0
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
with app.connection_for_read() as conn:
|
|
30
|
+
with conn.channel() as chan:
|
|
31
|
+
queue = app.amqp.queues[channel](chan)
|
|
32
|
+
|
|
33
|
+
while True:
|
|
34
|
+
message = queue.get(no_ack=False)
|
|
35
|
+
if not message:
|
|
36
|
+
break
|
|
37
|
+
|
|
38
|
+
processed_count += 1
|
|
39
|
+
task_name = message.headers.get("task")
|
|
40
|
+
|
|
41
|
+
if not task_name or task_name not in app.tasks:
|
|
42
|
+
# task is not registered in celery
|
|
43
|
+
logger.warning(
|
|
44
|
+
f"Invalid task '{task_name}'. Skipping.")
|
|
45
|
+
message.ack()
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
task_obj = app.tasks[task_name]
|
|
50
|
+
accept = {"application/json",
|
|
51
|
+
"application/x-python-serialize"}
|
|
52
|
+
decoded_body = loads(
|
|
53
|
+
message.body, message.content_type, message.content_encoding, accept=accept
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
task_args = decoded_body[0] if decoded_body else []
|
|
57
|
+
task_kwargs = decoded_body[1] if len(
|
|
58
|
+
decoded_body) > 1 else {}
|
|
59
|
+
|
|
60
|
+
full_func_name = ""
|
|
61
|
+
if task_name.endswith("dynamic_function_executor") and len(task_args) >= 2:
|
|
62
|
+
full_func_name = f"{task_args[0]}.{task_args[1]}"
|
|
63
|
+
elif task_name.endswith("dynamic_class_method_executor") and len(task_args) >= 3:
|
|
64
|
+
full_func_name = f"{task_args[0]}.{task_args[1]}.{task_args[2]}"
|
|
65
|
+
|
|
66
|
+
should_execute = full_func_name in included_set if full_func_name else False
|
|
67
|
+
|
|
68
|
+
if should_execute:
|
|
69
|
+
logger.info(f"Executing task: {full_func_name}")
|
|
70
|
+
message.ack()
|
|
71
|
+
task_obj.apply(args=task_args, kwargs=task_kwargs)
|
|
72
|
+
executed_count += 1
|
|
73
|
+
logger.info(
|
|
74
|
+
f"Successfully executed task: {full_func_name}")
|
|
75
|
+
else:
|
|
76
|
+
logger.debug(
|
|
77
|
+
f"Skipping: {full_func_name or task_name}")
|
|
78
|
+
message.ack()
|
|
79
|
+
|
|
80
|
+
except Exception as e:
|
|
81
|
+
logger.error(
|
|
82
|
+
f"Failed to process task {task_name}: {type(e).__name__}: {e}")
|
|
83
|
+
if message and not message.acknowledged:
|
|
84
|
+
message.ack()
|
|
85
|
+
|
|
86
|
+
except Exception as e:
|
|
87
|
+
logger.error(
|
|
88
|
+
f"Failed to connect to queue {channel}: {type(e).__name__}: {e}")
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
from unittest.mock import MagicMock
|
|
2
2
|
from unittest.mock import patch
|
|
3
3
|
|
|
4
|
-
from libs.
|
|
5
|
-
from libs.
|
|
4
|
+
from taskqueue.libs.helper_test import celery_worker_burst
|
|
5
|
+
from taskqueue.libs.helper_test import clear_all_celery_queues
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class TestClearAllCeleryQueues:
|
|
9
9
|
|
|
10
|
-
@patch('libs.
|
|
10
|
+
@patch('taskqueue.libs.helper_test.current_app')
|
|
11
11
|
def test_clear_all_celery_queues_given_multiple_queues_expect_all_purged(self, mock_current_app):
|
|
12
12
|
mock_queue1 = MagicMock()
|
|
13
13
|
mock_queue2 = MagicMock()
|
|
@@ -32,7 +32,7 @@ class TestClearAllCeleryQueues:
|
|
|
32
32
|
mock_queue1.purge.assert_called_once()
|
|
33
33
|
mock_queue2.purge.assert_called_once()
|
|
34
34
|
|
|
35
|
-
@patch('libs.
|
|
35
|
+
@patch('taskqueue.libs.helper_test.current_app')
|
|
36
36
|
def test_clear_all_celery_queues_given_empty_queues_expect_no_purge_calls(self, mock_current_app):
|
|
37
37
|
mock_current_app.amqp.queues.keys.return_value = []
|
|
38
38
|
mock_current_app.amqp.queues = {}
|
|
@@ -49,8 +49,8 @@ class TestClearAllCeleryQueues:
|
|
|
49
49
|
|
|
50
50
|
class TestCeleryWorkerBurst:
|
|
51
51
|
|
|
52
|
-
@patch('libs.
|
|
53
|
-
@patch('libs.
|
|
52
|
+
@patch('taskqueue.libs.helper_test.current_app')
|
|
53
|
+
@patch('taskqueue.libs.helper_test.loads')
|
|
54
54
|
def test_celery_worker_burst_given_matching_function_executor_expect_execution(self, mock_loads, mock_current_app):
|
|
55
55
|
mock_task = MagicMock()
|
|
56
56
|
mock_current_app.tasks = {
|
|
@@ -77,7 +77,7 @@ class TestCeleryWorkerBurst:
|
|
|
77
77
|
mock_current_app.connection_for_read.return_value.__enter__.return_value = mock_conn
|
|
78
78
|
mock_conn.channel.return_value.__enter__.return_value = mock_chan
|
|
79
79
|
|
|
80
|
-
with patch('libs.
|
|
80
|
+
with patch('taskqueue.libs.helper_test.logger') as mock_logger:
|
|
81
81
|
celery_worker_burst(['module.submodule.test_function'])
|
|
82
82
|
|
|
83
83
|
mock_logger.info.assert_any_call(
|
|
@@ -91,8 +91,8 @@ class TestCeleryWorkerBurst:
|
|
|
91
91
|
kwargs={}
|
|
92
92
|
)
|
|
93
93
|
|
|
94
|
-
@patch('libs.
|
|
95
|
-
@patch('libs.
|
|
94
|
+
@patch('taskqueue.libs.helper_test.current_app')
|
|
95
|
+
@patch('taskqueue.libs.helper_test.loads')
|
|
96
96
|
def test_celery_worker_burst_given_matching_class_method_executor_expect_execution(self, mock_loads, mock_current_app):
|
|
97
97
|
mock_task = MagicMock()
|
|
98
98
|
mock_current_app.tasks = {
|
|
@@ -119,7 +119,7 @@ class TestCeleryWorkerBurst:
|
|
|
119
119
|
mock_current_app.connection_for_read.return_value.__enter__.return_value = mock_conn
|
|
120
120
|
mock_conn.channel.return_value.__enter__.return_value = mock_chan
|
|
121
121
|
|
|
122
|
-
with patch('libs.
|
|
122
|
+
with patch('taskqueue.libs.helper_test.logger') as mock_logger:
|
|
123
123
|
celery_worker_burst(['module.submodule.TestClass.test_method'])
|
|
124
124
|
|
|
125
125
|
mock_logger.info.assert_any_call(
|
|
@@ -133,8 +133,8 @@ class TestCeleryWorkerBurst:
|
|
|
133
133
|
kwargs={}
|
|
134
134
|
)
|
|
135
135
|
|
|
136
|
-
@patch('libs.
|
|
137
|
-
@patch('libs.
|
|
136
|
+
@patch('taskqueue.libs.helper_test.current_app')
|
|
137
|
+
@patch('taskqueue.libs.helper_test.loads')
|
|
138
138
|
def test_celery_worker_burst_given_non_matching_function_expect_skip(self, mock_loads, mock_current_app):
|
|
139
139
|
mock_task = MagicMock()
|
|
140
140
|
mock_current_app.tasks = {
|
|
@@ -166,7 +166,7 @@ class TestCeleryWorkerBurst:
|
|
|
166
166
|
mock_message.ack.assert_called_once()
|
|
167
167
|
mock_task.apply.assert_not_called()
|
|
168
168
|
|
|
169
|
-
@patch('libs.
|
|
169
|
+
@patch('taskqueue.libs.helper_test.current_app')
|
|
170
170
|
def test_celery_worker_burst_given_invalid_task_name_expect_warning_and_skip(self, mock_current_app):
|
|
171
171
|
mock_current_app.tasks = {}
|
|
172
172
|
|
|
@@ -187,14 +187,14 @@ class TestCeleryWorkerBurst:
|
|
|
187
187
|
mock_current_app.connection_for_read.return_value.__enter__.return_value = mock_conn
|
|
188
188
|
mock_conn.channel.return_value.__enter__.return_value = mock_chan
|
|
189
189
|
|
|
190
|
-
with patch('libs.
|
|
190
|
+
with patch('taskqueue.libs.helper_test.logger') as mock_logger:
|
|
191
191
|
celery_worker_burst(['some.function'])
|
|
192
192
|
|
|
193
193
|
mock_logger.warning.assert_called_once_with(
|
|
194
194
|
"Invalid task 'invalid.task.name'. Skipping.")
|
|
195
195
|
mock_message.ack.assert_called_once()
|
|
196
196
|
|
|
197
|
-
@patch('libs.
|
|
197
|
+
@patch('taskqueue.libs.helper_test.current_app')
|
|
198
198
|
def test_celery_worker_burst_given_no_messages_expect_no_processing(self, mock_current_app):
|
|
199
199
|
mock_queue = MagicMock()
|
|
200
200
|
mock_queue.get.return_value = None
|
|
@@ -210,8 +210,8 @@ class TestCeleryWorkerBurst:
|
|
|
210
210
|
|
|
211
211
|
mock_queue.get.assert_called_once_with(no_ack=False)
|
|
212
212
|
|
|
213
|
-
@patch('libs.
|
|
214
|
-
@patch('libs.
|
|
213
|
+
@patch('taskqueue.libs.helper_test.current_app')
|
|
214
|
+
@patch('taskqueue.libs.helper_test.loads')
|
|
215
215
|
def test_celery_worker_burst_given_custom_channel_expect_correct_queue_used(self, mock_loads, mock_current_app):
|
|
216
216
|
mock_task = MagicMock()
|
|
217
217
|
mock_current_app.tasks = {
|
|
@@ -238,7 +238,7 @@ class TestCeleryWorkerBurst:
|
|
|
238
238
|
mock_current_app.connection_for_read.return_value.__enter__.return_value = mock_conn
|
|
239
239
|
mock_conn.channel.return_value.__enter__.return_value = mock_chan
|
|
240
240
|
|
|
241
|
-
with patch('libs.
|
|
241
|
+
with patch('taskqueue.libs.helper_test.logger') as mock_logger:
|
|
242
242
|
celery_worker_burst(
|
|
243
243
|
['module.submodule.test_function'], channel='custom_queue')
|
|
244
244
|
|
|
@@ -249,8 +249,8 @@ class TestCeleryWorkerBurst:
|
|
|
249
249
|
|
|
250
250
|
mock_queue_factory.assert_called_once_with(mock_chan)
|
|
251
251
|
|
|
252
|
-
@patch('libs.
|
|
253
|
-
@patch('libs.
|
|
252
|
+
@patch('taskqueue.libs.helper_test.current_app')
|
|
253
|
+
@patch('taskqueue.libs.helper_test.loads')
|
|
254
254
|
def test_celery_worker_burst_given_task_processing_error_expect_error_logged_and_ack(self, mock_loads, mock_current_app):
|
|
255
255
|
mock_task = MagicMock()
|
|
256
256
|
mock_task.apply.side_effect = Exception("Task execution failed")
|
|
@@ -283,7 +283,7 @@ class TestCeleryWorkerBurst:
|
|
|
283
283
|
mock_current_app.connection_for_read.return_value.__enter__.return_value = mock_conn
|
|
284
284
|
mock_conn.channel.return_value.__enter__.return_value = mock_chan
|
|
285
285
|
|
|
286
|
-
with patch('libs.
|
|
286
|
+
with patch('taskqueue.libs.helper_test.logger') as mock_logger:
|
|
287
287
|
celery_worker_burst(['module.submodule.test_function'])
|
|
288
288
|
|
|
289
289
|
mock_logger.info.assert_any_call(
|
|
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
|