scalable-pypeline 2.1.1__py2.py3-none-any.whl → 2.1.2__py2.py3-none-any.whl

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.
pypeline/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "2.1.1"
1
+ __version__ = "2.1.2"
pypeline/dramatiq.py CHANGED
@@ -34,6 +34,9 @@ from pypeline.constants import (
34
34
  )
35
35
  from pypeline.pipelines.middleware.parallel_pipeline_middleware import ParallelPipeline
36
36
  from pypeline.pipelines.middleware.pypeline_middleware import PypelineMiddleware
37
+ from pypeline.pipelines.middleware.deduplication_middleware import (
38
+ DeduplicationMiddleware,
39
+ )
37
40
  from pypeline.utils.config_utils import (
38
41
  retrieve_latest_schedule_config,
39
42
  get_service_config_for_worker,
@@ -72,6 +75,7 @@ def configure_default_broker(broker: Broker = None):
72
75
  rabbit_broker.add_middleware(PypelineMiddleware(redis_url=REDIS_URL))
73
76
  rabbit_broker.add_middleware(CurrentMessage())
74
77
  register_actors_for_workers(rabbit_broker)
78
+ rabbit_broker.add_middleware(DeduplicationMiddleware(redis_url=REDIS_URL))
75
79
  set_broker(rabbit_broker)
76
80
 
77
81
 
@@ -0,0 +1,91 @@
1
+ import dramatiq
2
+ import signal
3
+ from dramatiq.middleware import Middleware
4
+ from pypeline.barrier import LockingParallelBarrier
5
+ from pypeline.constants import DEFAULT_TASK_TTL
6
+ import logging
7
+
8
+ logging.basicConfig(level=logging.INFO)
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class DeduplicationMiddleware(Middleware):
13
+ def __init__(self, redis_url="redis://localhost:6379/0"):
14
+ self.redis_url = redis_url
15
+ self.active_locks = {}
16
+
17
+ def before_process_message(self, broker, message):
18
+ task_id = message.message_id
19
+ task_key = f"dramatiq:task_counter:{task_id}"
20
+ lock_key = f"dramatiq:lock:{task_id}"
21
+ try:
22
+ # Try to acquire a lock for the task
23
+ locking_parallel_barrier = LockingParallelBarrier(
24
+ self.redis_url,
25
+ task_key=task_key,
26
+ lock_key=lock_key,
27
+ )
28
+ if (
29
+ locking_parallel_barrier.get_task_count() > 0
30
+ or not locking_parallel_barrier.acquire_lock(timeout=DEFAULT_TASK_TTL)
31
+ ):
32
+ raise dramatiq.middleware.SkipMessage(
33
+ f"Task {task_id} is already being processed."
34
+ )
35
+
36
+ locking_parallel_barrier.set_task_count(1)
37
+ # Store the lock reference in the message and track it globally
38
+ message.options["dedupe_task_key"] = task_key
39
+ message.options["dedupe_lock_key"] = lock_key
40
+ self.active_locks[lock_key] = locking_parallel_barrier
41
+ except dramatiq.middleware.SkipMessage:
42
+ raise dramatiq.middleware.SkipMessage(
43
+ f"Task {task_id} is already being processed."
44
+ )
45
+ except Exception as e:
46
+ logger.exception(e)
47
+ raise e
48
+
49
+ def after_process_message(self, broker, message, *, result=None, exception=None):
50
+ """Releases lock for the message that just finished."""
51
+ dedupe_task_key = message.options.get("dedupe_task_key", None)
52
+ dedupe_lock_key = message.options.get("dedupe_lock_key", None)
53
+ if not dedupe_lock_key or not dedupe_task_key:
54
+ logger.warning("unexpected in after_process_message: dedupe task or lock key not in message")
55
+ return
56
+ if dedupe_lock_key in self.active_locks:
57
+ try:
58
+ lock = self.active_locks[dedupe_lock_key]
59
+ lock.decrement_task_count()
60
+ lock.release_lock()
61
+ del self.active_locks[dedupe_lock_key]
62
+ except Exception as e:
63
+ logger.info(
64
+ f"Exception while trying to release lock {dedupe_lock_key}: {e}"
65
+ )
66
+ raise e
67
+ else:
68
+ lock = LockingParallelBarrier(
69
+ self.redis_url,
70
+ task_key=dedupe_task_key,
71
+ lock_key=dedupe_lock_key,
72
+ )
73
+ lock.decrement_task_count()
74
+ lock.release_lock()
75
+
76
+ def before_worker_shutdown(self, *args):
77
+ self.release_all_locks()
78
+
79
+ def before_worker_thread_shutdown(self, *args):
80
+ self.release_all_locks()
81
+
82
+ def release_all_locks(self, *args):
83
+ """Release all locks when the worker shuts down."""
84
+ for lock_key, lock in self.active_locks.items():
85
+ try:
86
+ lock.decrement_task_count()
87
+ lock.release_lock()
88
+ except Exception as e:
89
+ logger.info(f"Exception while trying to release lock {lock_key}: {e}")
90
+ raise e
91
+ self.active_locks.clear()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: scalable-pypeline
3
- Version: 2.1.1
3
+ Version: 2.1.2
4
4
  Summary: PypeLine - Python pipelines for the Real World
5
5
  Home-page: https://gitlab.com/bravos2/pypeline
6
6
  Author: Bravos Power Corporation
@@ -1,7 +1,7 @@
1
- pypeline/__init__.py,sha256=zPJIgPGcoSNiD0qme18OnYJYE3A9VVytlhO-V5DaAW0,22
1
+ pypeline/__init__.py,sha256=UiuBcRXPtXxPUBDdp0ZDvWl0U9Db1kMNfT3oAfhxqLg,22
2
2
  pypeline/barrier.py,sha256=oO964l9qOCOibweOHyNivmAvufdXOke9nz2tdgclouo,1172
3
3
  pypeline/constants.py,sha256=coiF8dMP25qIwoNYSnS7oy7hCd4-5yqPFmdPsN93Q1A,2892
4
- pypeline/dramatiq.py,sha256=LWsl0o0t5FdxewIl87ARZKrNK0ENoYJEJAEVDSNFa40,12272
4
+ pypeline/dramatiq.py,sha256=TGwXWSInpCPFtYC5C-Omc2sEk5ecpsOG8xHH_mx9WTo,12451
5
5
  pypeline/extensions.py,sha256=BzOTnXhNxap3N7uIUUh_hO6dDwx08Vc_RJDE93_K0Lo,610
6
6
  pypeline/pipeline_config_schema.py,sha256=hK2_egtg-YFx_XJDs_NyrOTGKkel7W83X-G0sic52sM,10592
7
7
  pypeline/pipeline_settings_schema.py,sha256=84AuNFYsOUpoADsjEo_n9T6Ica-c21oK_V9s15I4lCg,20212
@@ -19,6 +19,7 @@ pypeline/pipelines/composition/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5
19
19
  pypeline/pipelines/composition/parallel_pipeline_composition.py,sha256=pTw9Xb9h4JnV4siFc3JStm5lB-i9djUADo3Kh5K3s7g,12976
20
20
  pypeline/pipelines/composition/pypeline_composition.py,sha256=ieTuQZ8zxTtvmPEkrWFbItjGtvO3JUotXcR-Jim2mss,7204
21
21
  pypeline/pipelines/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ pypeline/pipelines/middleware/deduplication_middleware.py,sha256=IGO9Kc6NPMUaw1ytT3_ud2ITSZuh1-_WaU5_onazeh8,3556
22
23
  pypeline/pipelines/middleware/parallel_pipeline_middleware.py,sha256=kTp6niYoe2nXIiN6EGRfdpxrJyioo0GPxDkfefbGlEk,2821
23
24
  pypeline/pipelines/middleware/pypeline_middleware.py,sha256=kvt5A9OxDwpIo0PsH11Im62tH6VquUc6OFoZDw2Gxsk,8036
24
25
  pypeline/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -28,9 +29,9 @@ pypeline/utils/module_utils.py,sha256=-yEJIukDCoXnmlZVXB6Dww25tH6GdPE5SoFqv6pfdV
28
29
  pypeline/utils/pipeline_utils.py,sha256=kGP1QwCJikGC5QNRtzRXCDVewyRMpWIqERTNnxGLlSY,4795
29
30
  pypeline/utils/schema_utils.py,sha256=Fgl0y9Cuo_TZeEx_S3gaSVnLjn6467LTkjb2ek7Ms98,851
30
31
  tests/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
- scalable_pypeline-2.1.1.dist-info/LICENSE,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
32
- scalable_pypeline-2.1.1.dist-info/METADATA,sha256=wgJ50QCkHF7mvEqD1XN4X6x3XVbb7Ne2dso-NWe7Qsc,5926
33
- scalable_pypeline-2.1.1.dist-info/WHEEL,sha256=bb2Ot9scclHKMOLDEHY6B2sicWOgugjFKaJsT7vwMQo,110
34
- scalable_pypeline-2.1.1.dist-info/entry_points.txt,sha256=uWs10ODfHSBKo2Cx_QaUjPHQTpZ3e77j9VlAdRRmMyg,119
35
- scalable_pypeline-2.1.1.dist-info/top_level.txt,sha256=C7dpkEOc_-nnsAQb28BfQknjD6XHRyS9ZrvVeoIbV7s,15
36
- scalable_pypeline-2.1.1.dist-info/RECORD,,
32
+ scalable_pypeline-2.1.2.dist-info/LICENSE,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
33
+ scalable_pypeline-2.1.2.dist-info/METADATA,sha256=M9cP1_2S3B289LGum8QtlruOBUfdnH7x4IDtcM3Swpk,5926
34
+ scalable_pypeline-2.1.2.dist-info/WHEEL,sha256=bb2Ot9scclHKMOLDEHY6B2sicWOgugjFKaJsT7vwMQo,110
35
+ scalable_pypeline-2.1.2.dist-info/entry_points.txt,sha256=uWs10ODfHSBKo2Cx_QaUjPHQTpZ3e77j9VlAdRRmMyg,119
36
+ scalable_pypeline-2.1.2.dist-info/top_level.txt,sha256=C7dpkEOc_-nnsAQb28BfQknjD6XHRyS9ZrvVeoIbV7s,15
37
+ scalable_pypeline-2.1.2.dist-info/RECORD,,