p1-taskqueue 0.1.2__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: p1-taskqueue
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: A Task Queue Wrapper for Dekoruma Backend
5
5
  Author-email: Chalvin <engineering@dekoruma.com>
6
6
  Project-URL: Homepage, https://github.com/Dekoruma/p1-taskqueue
@@ -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.2"
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"}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: p1-taskqueue
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: A Task Queue Wrapper for Dekoruma Backend
5
5
  Author-email: Chalvin <engineering@dekoruma.com>
6
6
  Project-URL: Homepage, https://github.com/Dekoruma/p1-taskqueue
@@ -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.test_utils import celery_worker_burst
5
- from libs.test_utils import clear_all_celery_queues
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.test_utils.current_app')
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.test_utils.current_app')
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.test_utils.current_app')
53
- @patch('libs.test_utils.loads')
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.test_utils.logger') as mock_logger:
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.test_utils.current_app')
95
- @patch('libs.test_utils.loads')
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.test_utils.logger') as mock_logger:
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.test_utils.current_app')
137
- @patch('libs.test_utils.loads')
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.test_utils.current_app')
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.test_utils.logger') as mock_logger:
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.test_utils.current_app')
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.test_utils.current_app')
214
- @patch('libs.test_utils.loads')
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.test_utils.logger') as mock_logger:
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.test_utils.current_app')
253
- @patch('libs.test_utils.loads')
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.test_utils.logger') as mock_logger:
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