p1-taskqueue 0.1.6__tar.gz → 0.1.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.

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.6
3
+ Version: 0.1.7
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.6"
8
+ version = "0.1.7"
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.6
3
+ Version: 0.1.7
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
@@ -7,6 +7,7 @@ __author__ = "Chalvin"
7
7
  __email__ = "engineering@dekoruma.com"
8
8
 
9
9
  from .cmanager import cm
10
+ from .cmanager import taskqueue_class
10
11
  from .celery_app import celery_app
11
12
 
12
- __all__ = ["cm", "celery_app"]
13
+ __all__ = ["cm", "celery_app", "taskqueue_class"]
@@ -28,6 +28,26 @@ def _is_class_method(func: Any) -> bool:
28
28
  )
29
29
 
30
30
 
31
+ def taskqueue_class(cls):
32
+ """Decorator to automatically capture init arguments for taskqueue."""
33
+ original_init = cls.__init__
34
+
35
+ def wrapped_init(self, *args, **kwargs):
36
+ self._taskqueue_init_args = list(args)
37
+ self._taskqueue_init_kwargs = dict(kwargs)
38
+ original_init(self, *args, **kwargs)
39
+
40
+ cls.__init__ = wrapped_init
41
+ return cls
42
+
43
+
44
+ def _extract_init_args_from_instance(instance: Any) -> Tuple[list, dict]:
45
+ """Extract init arguments from instance."""
46
+ init_args = getattr(instance, '_taskqueue_init_args', [])
47
+ init_kwargs = getattr(instance, '_taskqueue_init_kwargs', {})
48
+ return init_args, init_kwargs
49
+
50
+
31
51
  def _split_function_and_queue_kwargs(kwargs: Dict[str, Any]) -> Tuple[Dict[str, Any], Dict[str, Any]]:
32
52
  # To prevent confusion whether a kwargs is for function or queue kwargs(i.e celery options and on_commit),
33
53
  # ignore confusing kwargs while give warning
@@ -62,10 +82,20 @@ def _build_dynamic_task_call(func: Any, *args: Any, **func_kwargs: Any) -> Tuple
62
82
  module_path = klass.__module__
63
83
  class_name = klass.__name__
64
84
  method_name = func.__name__
85
+
86
+ init_args, init_kwargs = _extract_init_args_from_instance(instance)
87
+
65
88
  task_name = "taskqueue.cmanager.dynamic_class_method_executor"
66
- task_args = [module_path, class_name,
67
- method_name, list(args), dict(func_kwargs)]
68
- task_kwargs: Dict[str, Any] = {}
89
+ task_args = []
90
+ task_kwargs: Dict[str, Any] = {
91
+ "module_path": module_path,
92
+ "class_name": class_name,
93
+ "method_name": method_name,
94
+ "args": list(args),
95
+ "kwargs": dict(func_kwargs),
96
+ "init_args": init_args,
97
+ "init_kwargs": init_kwargs,
98
+ }
69
99
  return task_name, task_args, task_kwargs
70
100
 
71
101
  module_path = getattr(func, "__module__", None)
@@ -75,8 +105,13 @@ def _build_dynamic_task_call(func: Any, *args: Any, **func_kwargs: Any) -> Tuple
75
105
  "Unsupported callable type for Celery enqueue. Provide a module-level function or a class method.")
76
106
 
77
107
  task_name = "taskqueue.cmanager.dynamic_function_executor"
78
- task_args = [module_path, function_name, list(args), dict(func_kwargs)]
79
- task_kwargs = {}
108
+ task_args = []
109
+ task_kwargs = {
110
+ "module_path": module_path,
111
+ "function_name": function_name,
112
+ "args": list(args),
113
+ "kwargs": dict(func_kwargs),
114
+ }
80
115
  return task_name, task_args, task_kwargs
81
116
 
82
117
 
@@ -205,7 +240,7 @@ cm = CManager()
205
240
 
206
241
  # Dynamic task executors - handle function and class method execution
207
242
  @shared_task(bind=True, max_retries=K_MAX_RETRY_COUNT)
208
- def dynamic_function_executor(self, module_path, function_name, args=None, kwargs=None, retry=None):
243
+ def dynamic_function_executor(self, module_path=None, function_name=None, args=None, kwargs=None, retry=None):
209
244
  job_id = self.request.id
210
245
  try:
211
246
  module = importlib.import_module(module_path)
@@ -237,12 +272,14 @@ def dynamic_function_executor(self, module_path, function_name, args=None, kwarg
237
272
 
238
273
 
239
274
  @shared_task(bind=True, max_retries=K_MAX_RETRY_COUNT)
240
- def dynamic_class_method_executor(self, module_path, class_name, method_name, args=None, kwargs=None, retry=None):
275
+ def dynamic_class_method_executor(self, module_path=None, class_name=None, method_name=None, args=None, kwargs=None, init_args=None, init_kwargs=None, retry=None):
241
276
  job_id = self.request.id
242
277
  try:
243
278
  module = importlib.import_module(module_path)
244
279
  class_obj = getattr(module, class_name)
245
- instance = class_obj()
280
+ init_args = init_args or []
281
+ init_kwargs = init_kwargs or {}
282
+ instance = class_obj(*init_args, **init_kwargs)
246
283
  method = getattr(instance, method_name)
247
284
  args = args or []
248
285
  kwargs = kwargs or {}
@@ -10,7 +10,7 @@ from taskqueue.cmanager import _split_function_and_queue_kwargs
10
10
  from taskqueue.cmanager import CManager
11
11
  from taskqueue.cmanager import K_DEFAULT_RETRY_COUNTDOWN
12
12
  from taskqueue.cmanager import K_MAX_RETRY_COUNT
13
- # Import the functions and classes to test
13
+ from taskqueue.cmanager import taskqueue_class
14
14
 
15
15
 
16
16
  class TestClass:
@@ -27,6 +27,56 @@ class TestClass:
27
27
  pass
28
28
 
29
29
 
30
+ @taskqueue_class
31
+ class TestClassWithInit:
32
+
33
+ def __init__(self, name, age=0, **kwargs):
34
+ self.name = name
35
+ self.age = age
36
+ self.kwargs = kwargs
37
+
38
+ def process(self):
39
+ return f"Processing {self.name}, age {self.age}"
40
+
41
+ def process_with_args(self, message):
42
+ return f"{message}: {self.name}, age {self.age}"
43
+
44
+
45
+ @taskqueue_class
46
+ class TestClassWithVarArgs:
47
+
48
+ def __init__(self, name, *args, **kwargs):
49
+ self.name = name
50
+ self.args = args
51
+ self.kwargs = kwargs
52
+
53
+ def process(self):
54
+ return f"Processing {self.name} with {len(self.args)} args"
55
+
56
+
57
+ @taskqueue_class
58
+ class TestClassWithComplexInit:
59
+
60
+ def __init__(self, required, optional=10, *extra, **options):
61
+ self.required = required
62
+ self.optional = optional
63
+ self.extra = extra
64
+ self.options = options
65
+
66
+ def calculate(self):
67
+ return sum([self.required, self.optional] + list(self.extra))
68
+
69
+
70
+ @taskqueue_class
71
+ class TestClassWithDifferentParamNames:
72
+
73
+ def __init__(self, cognito_form_reimbursement_dict):
74
+ self.data = cognito_form_reimbursement_dict
75
+
76
+ def process(self):
77
+ return f"Processing data: {self.data}"
78
+
79
+
30
80
  def test_function():
31
81
  """Test function for testing function detection."""
32
82
 
@@ -117,9 +167,13 @@ class TestBuildDynamicTaskCall:
117
167
  )
118
168
 
119
169
  assert task_name == "taskqueue.cmanager.dynamic_function_executor"
120
- assert task_args == ['tests.test_cmanager',
121
- 'test_function', [1, 2], {'key': 'value'}]
122
- assert task_kwargs == {}
170
+ assert task_args == []
171
+ assert task_kwargs == {
172
+ 'module_path': 'tests.test_cmanager',
173
+ 'function_name': 'test_function',
174
+ 'args': [1, 2],
175
+ 'kwargs': {'key': 'value'}
176
+ }
123
177
 
124
178
  def test__build_dynamic_task_call_given_bound_method_expect_class_method_executor_task(self):
125
179
  instance = TestClass()
@@ -128,9 +182,16 @@ class TestBuildDynamicTaskCall:
128
182
  )
129
183
 
130
184
  assert task_name == "taskqueue.cmanager.dynamic_class_method_executor"
131
- assert task_args == ['tests.test_cmanager',
132
- 'TestClass', 'test_method', [1, 2], {'key': 'value'}]
133
- assert task_kwargs == {}
185
+ assert task_args == []
186
+ assert task_kwargs == {
187
+ 'module_path': 'tests.test_cmanager',
188
+ 'class_name': 'TestClass',
189
+ 'method_name': 'test_method',
190
+ 'args': [1, 2],
191
+ 'kwargs': {'key': 'value'},
192
+ 'init_args': [],
193
+ 'init_kwargs': {}
194
+ }
134
195
 
135
196
  def test__build_dynamic_task_call_given_function_without_module_expect_raise_value_error(self):
136
197
  mock_func = Mock()
@@ -306,3 +367,176 @@ class TestDynamicTaskExecutors:
306
367
  mock_self, "invalid_module", "test_function",
307
368
  retry={"max_retries": K_MAX_RETRY_COUNT}
308
369
  )
370
+
371
+
372
+ class TestExtractInitArgs:
373
+
374
+ def test_extract_init_args_from_instance_given_instance_with_init_args_expect_args_extracted(self):
375
+ from taskqueue.cmanager import _extract_init_args_from_instance
376
+ instance = TestClassWithInit("John", age=25)
377
+
378
+ init_args, init_kwargs = _extract_init_args_from_instance(instance)
379
+ assert init_args == ["John"]
380
+ assert init_kwargs == {"age": 25}
381
+
382
+ def test_extract_init_args_from_instance_given_kwargs_only_expect_extracted(self):
383
+ from taskqueue.cmanager import _extract_init_args_from_instance
384
+ instance = TestClassWithInit("Jane", age=30)
385
+
386
+ init_args, init_kwargs = _extract_init_args_from_instance(instance)
387
+ assert init_args == ["Jane"]
388
+ assert init_kwargs == {"age": 30}
389
+
390
+ def test_extract_init_args_from_instance_given_no_init_expect_empty(self):
391
+ from taskqueue.cmanager import _extract_init_args_from_instance
392
+ instance = TestClass()
393
+
394
+ init_args, init_kwargs = _extract_init_args_from_instance(instance)
395
+ assert init_args == []
396
+ assert init_kwargs == {}
397
+
398
+ def test_extract_init_args_from_instance_given_var_args_expect_captured(self):
399
+ from taskqueue.cmanager import _extract_init_args_from_instance
400
+ instance = TestClassWithVarArgs("Alice", "extra1", "extra2", city="NYC")
401
+
402
+ init_args, init_kwargs = _extract_init_args_from_instance(instance)
403
+ assert init_args == ["Alice", "extra1", "extra2"]
404
+ assert init_kwargs == {"city": "NYC"}
405
+
406
+ def test_extract_init_args_from_instance_given_var_kwargs_expect_captured(self):
407
+ from taskqueue.cmanager import _extract_init_args_from_instance
408
+ instance = TestClassWithInit("Bob", age=30, city="LA", country="USA")
409
+
410
+ init_args, init_kwargs = _extract_init_args_from_instance(instance)
411
+ assert init_args == ["Bob"]
412
+ assert init_kwargs == {"age": 30, "city": "LA", "country": "USA"}
413
+
414
+ def test_extract_init_args_from_instance_given_complex_init_expect_all_captured(self):
415
+ from taskqueue.cmanager import _extract_init_args_from_instance
416
+ instance = TestClassWithComplexInit(5, 15, 20, 25, multiplier=2, debug=True)
417
+
418
+ init_args, init_kwargs = _extract_init_args_from_instance(instance)
419
+ assert init_args == [5, 15, 20, 25]
420
+ assert init_kwargs == {"multiplier": 2, "debug": True}
421
+
422
+ def test_extract_init_args_from_instance_given_different_param_names_expect_works(self):
423
+ from taskqueue.cmanager import _extract_init_args_from_instance
424
+ from collections import OrderedDict
425
+
426
+ test_dict = OrderedDict([("key1", "value1"), ("key2", "value2")])
427
+ instance = TestClassWithDifferentParamNames(test_dict)
428
+
429
+ init_args, init_kwargs = _extract_init_args_from_instance(instance)
430
+ assert init_args == [test_dict]
431
+ assert init_kwargs == {}
432
+
433
+
434
+ class TestTaskqueueClassDecorator:
435
+
436
+ def test_taskqueue_class_decorator_given_class_expect_init_args_captured(self):
437
+ instance = TestClassWithInit("John", age=25)
438
+
439
+ assert hasattr(instance, '_taskqueue_init_args')
440
+ assert hasattr(instance, '_taskqueue_init_kwargs')
441
+ assert instance._taskqueue_init_args == ["John"]
442
+ assert instance._taskqueue_init_kwargs == {"age": 25}
443
+
444
+ def test_taskqueue_class_decorator_given_different_param_names_expect_captured(self):
445
+ from collections import OrderedDict
446
+ test_dict = OrderedDict([("key1", "value1")])
447
+ instance = TestClassWithDifferentParamNames(test_dict)
448
+
449
+ assert instance._taskqueue_init_args == [test_dict]
450
+ assert instance._taskqueue_init_kwargs == {}
451
+ assert instance.data == test_dict
452
+
453
+
454
+ class TestBuildDynamicTaskCallWithInitArgs:
455
+
456
+ def test__build_dynamic_task_call_given_instance_with_init_args_expect_init_args_passed(self):
457
+ instance = TestClassWithInit("John", age=25)
458
+ task_name, task_args, task_kwargs = _build_dynamic_task_call(
459
+ instance.process, "arg1", key='value'
460
+ )
461
+
462
+ assert task_name == "taskqueue.cmanager.dynamic_class_method_executor"
463
+ assert task_args == []
464
+ assert task_kwargs == {
465
+ 'module_path': 'tests.test_cmanager',
466
+ 'class_name': 'TestClassWithInit',
467
+ 'method_name': 'process',
468
+ 'args': ['arg1'],
469
+ 'kwargs': {'key': 'value'},
470
+ 'init_args': ['John'],
471
+ 'init_kwargs': {'age': 25}
472
+ }
473
+
474
+ def test__build_dynamic_task_call_given_instance_without_decorator_expect_empty_defaults(self):
475
+ instance = TestClass()
476
+ task_name, task_args, task_kwargs = _build_dynamic_task_call(
477
+ instance.test_method, 1, 2, key='value'
478
+ )
479
+
480
+ assert task_name == "taskqueue.cmanager.dynamic_class_method_executor"
481
+ assert task_args == []
482
+ assert task_kwargs == {
483
+ 'module_path': 'tests.test_cmanager',
484
+ 'class_name': 'TestClass',
485
+ 'method_name': 'test_method',
486
+ 'args': [1, 2],
487
+ 'kwargs': {'key': 'value'},
488
+ 'init_args': [],
489
+ 'init_kwargs': {}
490
+ }
491
+
492
+
493
+ class TestDynamicClassMethodExecutorWithInitArgs:
494
+ """Tests for dynamic_class_method_executor with init args."""
495
+
496
+ def test_dynamic_class_method_executor_given_init_args_expect_instance_created_with_args(self):
497
+ from taskqueue.cmanager import dynamic_class_method_executor
498
+
499
+ result = dynamic_class_method_executor(
500
+ module_path="tests.test_cmanager",
501
+ class_name="TestClassWithInit",
502
+ method_name="process",
503
+ args=[],
504
+ kwargs={},
505
+ init_args=["Alice"],
506
+ init_kwargs={"age": 35},
507
+ retry=None
508
+ )
509
+
510
+ assert result is None
511
+
512
+ def test_dynamic_class_method_executor_given_no_init_args_expect_backward_compatible(self):
513
+ from taskqueue.cmanager import dynamic_class_method_executor
514
+
515
+ result = dynamic_class_method_executor(
516
+ module_path="tests.test_cmanager",
517
+ class_name="TestClass",
518
+ method_name="test_method",
519
+ args=[],
520
+ kwargs={},
521
+ init_args=None,
522
+ init_kwargs=None,
523
+ retry=None
524
+ )
525
+
526
+ assert result is None
527
+
528
+ def test_dynamic_class_method_executor_given_var_args_and_kwargs_expect_works(self):
529
+ from taskqueue.cmanager import dynamic_class_method_executor
530
+
531
+ result = dynamic_class_method_executor(
532
+ module_path="tests.test_cmanager",
533
+ class_name="TestClassWithVarArgs",
534
+ method_name="process",
535
+ args=[],
536
+ kwargs={},
537
+ init_args=["Bob", "extra1", "extra2"],
538
+ init_kwargs={"city": "NYC", "country": "USA"},
539
+ retry=None
540
+ )
541
+
542
+ assert result is None
File without changes
File without changes