scalable-pypeline 2.1.12__py2.py3-none-any.whl → 2.1.14__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.12"
1
+ __version__ = "2.1.14"
pypeline/dramatiq.py CHANGED
@@ -2,6 +2,7 @@ import copy
2
2
  import typing
3
3
  import pika
4
4
  import logging
5
+ import os
5
6
 
6
7
  import click
7
8
  from urllib.parse import urlparse
@@ -52,6 +53,7 @@ from pypeline.utils.dramatiq_utils import (
52
53
  register_lazy_actor,
53
54
  LazyActor,
54
55
  )
56
+ from pypeline.utils.graceful_shutdown_util import enable_graceful_shutdown
55
57
  from pypeline.utils.module_utils import get_callable
56
58
 
57
59
 
@@ -92,6 +94,12 @@ def configure_default_broker(broker: Broker = None):
92
94
  broker.add_middleware(PypelineMiddleware(redis_url=REDIS_URL))
93
95
  broker.add_middleware(CurrentMessage())
94
96
  broker.add_middleware(GetActiveWorkerIdMiddleware())
97
+ if (
98
+ os.getenv("RESTRICT_WORKER_SHUTDOWN_WHILE_JOBS_RUNNING", "false").lower()
99
+ == "true"
100
+ ):
101
+ enable_graceful_shutdown(broker=broker, redis_url=REDIS_URL)
102
+
95
103
  register_actors_for_workers(broker)
96
104
  set_broker(broker)
97
105
 
@@ -86,6 +86,10 @@ class Pypeline:
86
86
  )
87
87
  message = lazy_actor.message()
88
88
  message.options["pipeline"] = pipeline
89
+ if pipeline_config["metadata"].get("maxRetry", None) is not None:
90
+ message.options["max_retries"] = pipeline_config["metadata"][
91
+ "maxRetry"
92
+ ]
89
93
  message.options["task_replacements"] = copy(
90
94
  scenario["taskReplacements"]
91
95
  )
@@ -121,6 +125,8 @@ class Pypeline:
121
125
  )
122
126
  message = lazy_actor.message()
123
127
  message.options["pipeline"] = pipeline
128
+ if pipeline_config["metadata"].get("maxRetry", None) is not None:
129
+ message.options["max_retries"] = pipeline_config["metadata"]["maxRetry"]
124
130
  message.options["task_replacements"] = first_scenario_task_replacements
125
131
  message.options["execution_id"] = base_case_execution_id
126
132
  message.options["task_name"] = first_task
@@ -70,13 +70,16 @@ def dag_generator(
70
70
  )
71
71
  registered_actors[task] = lazy_actor
72
72
  if args and not kwargs:
73
- message_group.append(registered_actors[task].message(*args))
73
+ msg = registered_actors[task].message(*args)
74
74
  elif kwargs and not args:
75
- message_group.append(registered_actors[task].message(**kwargs))
75
+ msg = registered_actors[task].message(**kwargs)
76
76
  elif args and kwargs:
77
- message_group.append(registered_actors[task].message(*args, **kwargs))
77
+ msg = registered_actors[task].message(*args, **kwargs)
78
78
  else:
79
- message_group.append(registered_actors[task].message())
79
+ msg = registered_actors[task].message()
80
+ msg.options["task_ttl"] = pipeline_config["metadata"]["maxTtl"]
81
+ message_group.append(msg)
82
+
80
83
  messages.append(message_group)
81
84
  p = parallel_pipeline(messages)
82
85
 
@@ -0,0 +1,50 @@
1
+ import os
2
+ import socket
3
+ import logging
4
+ import redis
5
+
6
+ from dramatiq.middleware import Middleware
7
+ from tenacity import retry, stop_after_attempt, wait_exponential, after_log
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class GraceFulShutdownMiddleware(Middleware):
13
+ def __init__(self, redis_url, key_prefix="busy"):
14
+ self.redis = redis.Redis.from_url(redis_url)
15
+ self.hostname = socket.gethostname()
16
+ self.pid = os.getpid()
17
+ self.key_prefix = key_prefix
18
+ self.key = f"{self.key_prefix}:{self.hostname}-{self.pid}"
19
+
20
+ @retry(
21
+ stop=stop_after_attempt(3),
22
+ wait=wait_exponential(multiplier=2, min=2, max=10),
23
+ after=after_log(logger, logging.WARNING),
24
+ reraise=True,
25
+ )
26
+ def _set_busy_flag(self, message_ttl):
27
+ self.redis.set(self.key, "1", ex=message_ttl)
28
+ logger.debug(f"[GracefulShutdownMiddleware] Set busy flag: {self.key}")
29
+
30
+ @retry(
31
+ stop=stop_after_attempt(3),
32
+ wait=wait_exponential(multiplier=2, min=2, max=10),
33
+ after=after_log(logger, logging.WARNING),
34
+ reraise=True,
35
+ )
36
+ def _clear_busy_flag(self):
37
+ self.redis.delete(self.key)
38
+ logger.debug(f"[GracefulShutdownMiddleware] Cleared busy flag: {self.key}")
39
+
40
+ def before_process_message(self, broker, message):
41
+ try:
42
+ self._set_busy_flag(message_ttl=message.options["task_ttl"])
43
+ except Exception as e:
44
+ logger.error(f"[GracefulShutdownMiddleware] Failed to set busy flag: {e}")
45
+
46
+ def after_process_message(self, broker, message, *, result=None, exception=None):
47
+ try:
48
+ self._clear_busy_flag()
49
+ except Exception as e:
50
+ logger.error(f"[GracefulShutdownMiddleware] Failed to clear busy flag: {e}")
@@ -24,6 +24,7 @@ class PypelineMiddleware(Middleware):
24
24
  return
25
25
 
26
26
  pipeline = message.options["pipeline"]
27
+ max_retries = message.options.get("max_retries", None)
27
28
  pipeline_config = pipeline["config"]
28
29
  task_replacements = message.options["task_replacements"]
29
30
  execution_id = message.options["execution_id"]
@@ -138,6 +139,8 @@ class PypelineMiddleware(Middleware):
138
139
  )
139
140
  scenario_message = lazy_actor.message()
140
141
  scenario_message.options["pipeline"] = pipeline
142
+ if max_retries is not None:
143
+ scenario_message.options["max_retries"] = max_retries
141
144
  scenario_message.options["task_replacements"] = (
142
145
  task_replacements
143
146
  )
@@ -182,6 +185,8 @@ class PypelineMiddleware(Middleware):
182
185
 
183
186
  child_message = lazy_actor.message()
184
187
  child_message.options["pipeline"] = pipeline
188
+ if max_retries is not None:
189
+ child_message.options["max_retries"] = max_retries
185
190
  child_message.options["task_replacements"] = task_replacements
186
191
  child_message.options["execution_id"] = execution_id
187
192
  child_message.options["task_name"] = child
@@ -0,0 +1,36 @@
1
+ import threading
2
+ import signal
3
+ import os
4
+ import redis
5
+ import socket
6
+ import sys
7
+ import time
8
+ import logging
9
+ from pypeline.pipelines.middleware.graceful_shutdown_middleware import (
10
+ GraceFulShutdownMiddleware,
11
+ )
12
+
13
+ logging.basicConfig(level=logging.INFO)
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ def enable_graceful_shutdown(broker, redis_url):
18
+ """Attach GracefulShutdownMiddleware and a SIGTERM handler to the current process."""
19
+ broker.add_middleware(GraceFulShutdownMiddleware(redis_url=redis_url))
20
+
21
+ if threading.current_thread().name == "MainThread":
22
+ key_prefix = "busy"
23
+ hostname = socket.gethostname()
24
+ pid = os.getpid()
25
+ busy_key = f"{key_prefix}:{hostname}-{pid}"
26
+ r = redis.Redis.from_url(redis_url)
27
+
28
+ def shutdown_handler(signum, frame):
29
+ logger.info(f"[Signal Handler] Received signal {signum}")
30
+ while r.get(busy_key):
31
+ logger.info(f"[Signal Handler] Busy ({busy_key}), waiting...")
32
+ time.sleep(30)
33
+ logger.info(f"[Signal Handler] Done. Exiting.")
34
+ sys.exit(0)
35
+
36
+ signal.signal(signal.SIGTERM, shutdown_handler)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: scalable-pypeline
3
- Version: 2.1.12
3
+ Version: 2.1.14
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
@@ -33,6 +33,7 @@ Provides-Extra: workers
33
33
  Requires-Dist: networkx (>=2.4) ; extra == 'workers'
34
34
  Requires-Dist: dramatiq[rabbitmq] (==1.17.0) ; extra == 'workers'
35
35
  Requires-Dist: apscheduler (<4,>=3.10.4) ; extra == 'workers'
36
+ Requires-Dist: tenacity (==9.1.2) ; extra == 'workers'
36
37
 
37
38
  ```
38
39
  ______ __ ________ _____ _ _____ _ _ _____
@@ -1,7 +1,7 @@
1
- pypeline/__init__.py,sha256=2ZypnXz9pLMxwCwfHNJV9M24HPgZwt56lxlCb2BYeEA,23
1
+ pypeline/__init__.py,sha256=otJNk418s7IUoP3b0ZQy4k9fA8qhpkQusp3zKCHD2bQ,23
2
2
  pypeline/barrier.py,sha256=oO964l9qOCOibweOHyNivmAvufdXOke9nz2tdgclouo,1172
3
3
  pypeline/constants.py,sha256=EGSuLq4KhZ4bxrbtnUgKclELRyya5ipvv0WeybCzNAs,3049
4
- pypeline/dramatiq.py,sha256=nnNxm2akL6hjqfjwQglK76LspZrdrL_2E_rDN5ThwZ8,13432
4
+ pypeline/dramatiq.py,sha256=D-E6_oJc9he-F2rvze-DCq4eBVY3Hq7V0pSC5crHrrU,13703
5
5
  pypeline/extensions.py,sha256=BzOTnXhNxap3N7uIUUh_hO6dDwx08Vc_RJDE93_K0Lo,610
6
6
  pypeline/pipeline_config_schema.py,sha256=kprvmfPfmNVP5MOqtWzDm4ON5NJWIKQC8GWbIy5EIuk,11183
7
7
  pypeline/pipeline_settings_schema.py,sha256=VgbMvTxQSqnvLX593RNoD9gTsZnJwzCc4wGpczV5CEI,20403
@@ -14,24 +14,26 @@ pypeline/flask/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
14
14
  pypeline/flask/api/pipelines.py,sha256=lw1ggsjp_Iha5MhyQGHtVW0akpVJnxIk0hn6NkC3c8s,9314
15
15
  pypeline/flask/api/schedules.py,sha256=8PKCMdPucaer8opchNlI5aDssK2UqT79hHpeg5BMtTA,1210
16
16
  pypeline/pipelines/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- pypeline/pipelines/factory.py,sha256=p2b1XhhMYt_f5h2FAr6Zze1FfWz8SCyO9DEIS_EDDKE,3258
17
+ pypeline/pipelines/factory.py,sha256=ftm6AwXIdkC8LEzy3c4CLPzXtoQonBKUo3yojbYTsUE,3309
18
18
  pypeline/pipelines/composition/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  pypeline/pipelines/composition/parallel_pipeline_composition.py,sha256=pTw9Xb9h4JnV4siFc3JStm5lB-i9djUADo3Kh5K3s7g,12976
20
- pypeline/pipelines/composition/pypeline_composition.py,sha256=G6hptAN3LaVGm6DE7yhjGs-LDbw7m94-z08pLZCqNXA,8154
20
+ pypeline/pipelines/composition/pypeline_composition.py,sha256=lY1RO7luLqDBLfoX99YbBatx1Qov1cxGoS5lRAjB_DQ,8534
21
21
  pypeline/pipelines/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  pypeline/pipelines/middleware/get_active_worker_id_middleware.py,sha256=X4ZfRk3L8MD00DTsGHth7oOdy-W7LQV96T8vu5UC42A,755
23
+ pypeline/pipelines/middleware/graceful_shutdown_middleware.py,sha256=k37zmFk9dOye05BoQP7KcB9MEQgvodI16kOJyYhRyAc,1764
23
24
  pypeline/pipelines/middleware/parallel_pipeline_middleware.py,sha256=kTp6niYoe2nXIiN6EGRfdpxrJyioo0GPxDkfefbGlEk,2821
24
- pypeline/pipelines/middleware/pypeline_middleware.py,sha256=wdqcnya9SrcBs2VOfBOIXY03VC0uadMcS5SD4hGOk8U,8843
25
+ pypeline/pipelines/middleware/pypeline_middleware.py,sha256=ciXZD2rLMCLGI9wP5zB8g_5_zazt08LetQXuTJkedms,9147
25
26
  pypeline/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
27
  pypeline/utils/config_utils.py,sha256=rAIATyoW7kGETZ_Z2DqiXtGd7bJp5uPfcLtfNPOYsNs,2167
27
28
  pypeline/utils/dramatiq_utils.py,sha256=DUdgVywm1182A4i69XzH9EIh1EJ9zAHmJLtOaVSW7pw,3844
29
+ pypeline/utils/graceful_shutdown_util.py,sha256=d3p8ZQ9-xasd_H0YUA5ZsjCQ9PVliSO7_4c1OYzhx-Q,1156
28
30
  pypeline/utils/module_utils.py,sha256=-yEJIukDCoXnmlZVXB6Dww25tH6GdPE5SoFqv6pfdVU,3682
29
31
  pypeline/utils/pipeline_utils.py,sha256=kGP1QwCJikGC5QNRtzRXCDVewyRMpWIqERTNnxGLlSY,4795
30
32
  pypeline/utils/schema_utils.py,sha256=Fgl0y9Cuo_TZeEx_S3gaSVnLjn6467LTkjb2ek7Ms98,851
31
33
  tests/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
- scalable_pypeline-2.1.12.dist-info/LICENSE,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
33
- scalable_pypeline-2.1.12.dist-info/METADATA,sha256=_4e8M2zofZzICdZZp0arkXFIfyLV7hocyqoTEoxbEts,5927
34
- scalable_pypeline-2.1.12.dist-info/WHEEL,sha256=bb2Ot9scclHKMOLDEHY6B2sicWOgugjFKaJsT7vwMQo,110
35
- scalable_pypeline-2.1.12.dist-info/entry_points.txt,sha256=uWs10ODfHSBKo2Cx_QaUjPHQTpZ3e77j9VlAdRRmMyg,119
36
- scalable_pypeline-2.1.12.dist-info/top_level.txt,sha256=C7dpkEOc_-nnsAQb28BfQknjD6XHRyS9ZrvVeoIbV7s,15
37
- scalable_pypeline-2.1.12.dist-info/RECORD,,
34
+ scalable_pypeline-2.1.14.dist-info/LICENSE,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
35
+ scalable_pypeline-2.1.14.dist-info/METADATA,sha256=6MPVldbryAie6UIDSDF3-9EJffhMyEqsiehraoaC2ZA,5982
36
+ scalable_pypeline-2.1.14.dist-info/WHEEL,sha256=bb2Ot9scclHKMOLDEHY6B2sicWOgugjFKaJsT7vwMQo,110
37
+ scalable_pypeline-2.1.14.dist-info/entry_points.txt,sha256=uWs10ODfHSBKo2Cx_QaUjPHQTpZ3e77j9VlAdRRmMyg,119
38
+ scalable_pypeline-2.1.14.dist-info/top_level.txt,sha256=C7dpkEOc_-nnsAQb28BfQknjD6XHRyS9ZrvVeoIbV7s,15
39
+ scalable_pypeline-2.1.14.dist-info/RECORD,,