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 +1 -1
- pypeline/dramatiq.py +8 -0
- pypeline/pipelines/composition/pypeline_composition.py +6 -0
- pypeline/pipelines/factory.py +7 -4
- pypeline/pipelines/middleware/graceful_shutdown_middleware.py +50 -0
- pypeline/pipelines/middleware/pypeline_middleware.py +5 -0
- pypeline/utils/graceful_shutdown_util.py +36 -0
- {scalable_pypeline-2.1.12.dist-info → scalable_pypeline-2.1.14.dist-info}/METADATA +2 -1
- {scalable_pypeline-2.1.12.dist-info → scalable_pypeline-2.1.14.dist-info}/RECORD +13 -11
- {scalable_pypeline-2.1.12.dist-info → scalable_pypeline-2.1.14.dist-info}/LICENSE +0 -0
- {scalable_pypeline-2.1.12.dist-info → scalable_pypeline-2.1.14.dist-info}/WHEEL +0 -0
- {scalable_pypeline-2.1.12.dist-info → scalable_pypeline-2.1.14.dist-info}/entry_points.txt +0 -0
- {scalable_pypeline-2.1.12.dist-info → scalable_pypeline-2.1.14.dist-info}/top_level.txt +0 -0
pypeline/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "2.1.
|
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
|
pypeline/pipelines/factory.py
CHANGED
@@ -70,13 +70,16 @@ def dag_generator(
|
|
70
70
|
)
|
71
71
|
registered_actors[task] = lazy_actor
|
72
72
|
if args and not kwargs:
|
73
|
-
|
73
|
+
msg = registered_actors[task].message(*args)
|
74
74
|
elif kwargs and not args:
|
75
|
-
|
75
|
+
msg = registered_actors[task].message(**kwargs)
|
76
76
|
elif args and kwargs:
|
77
|
-
|
77
|
+
msg = registered_actors[task].message(*args, **kwargs)
|
78
78
|
else:
|
79
|
-
|
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.
|
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=
|
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=
|
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=
|
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=
|
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=
|
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.
|
33
|
-
scalable_pypeline-2.1.
|
34
|
-
scalable_pypeline-2.1.
|
35
|
-
scalable_pypeline-2.1.
|
36
|
-
scalable_pypeline-2.1.
|
37
|
-
scalable_pypeline-2.1.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|