prefect-client 3.2.4__py3-none-any.whl → 3.2.6__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.
- prefect/_build_info.py +3 -3
- prefect/_experimental/bundles.py +143 -0
- prefect/_internal/schemas/validators.py +1 -1
- prefect/_vendor/croniter/__init__.py +25 -0
- prefect/_vendor/croniter/croniter.py +1456 -0
- prefect/deployments/runner.py +14 -6
- prefect/engine.py +63 -29
- prefect/flow_engine.py +2 -32
- prefect/runner/runner.py +142 -10
- {prefect_client-3.2.4.dist-info → prefect_client-3.2.6.dist-info}/METADATA +1 -2
- {prefect_client-3.2.4.dist-info → prefect_client-3.2.6.dist-info}/RECORD +13 -10
- {prefect_client-3.2.4.dist-info → prefect_client-3.2.6.dist-info}/WHEEL +0 -0
- {prefect_client-3.2.4.dist-info → prefect_client-3.2.6.dist-info}/licenses/LICENSE +0 -0
prefect/_build_info.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Generated by versioningit
|
2
|
-
__version__ = "3.2.
|
3
|
-
__build_date__ = "2025-02-
|
4
|
-
__git_commit__ = "
|
2
|
+
__version__ = "3.2.6"
|
3
|
+
__build_date__ = "2025-02-19 21:22:47.670607+00:00"
|
4
|
+
__git_commit__ = "5ceb3ada542c5a752bab431af83fe790252d4dc8"
|
5
5
|
__dirty__ = False
|
@@ -0,0 +1,143 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
import base64
|
5
|
+
import gzip
|
6
|
+
import multiprocessing
|
7
|
+
import multiprocessing.context
|
8
|
+
import os
|
9
|
+
from typing import Any, TypedDict
|
10
|
+
|
11
|
+
import cloudpickle
|
12
|
+
|
13
|
+
from prefect.client.schemas.objects import FlowRun
|
14
|
+
from prefect.context import SettingsContext, get_settings_context, serialize_context
|
15
|
+
from prefect.engine import handle_engine_signals
|
16
|
+
from prefect.flow_engine import run_flow
|
17
|
+
from prefect.flows import Flow
|
18
|
+
from prefect.settings.context import get_current_settings
|
19
|
+
from prefect.settings.models.root import Settings
|
20
|
+
|
21
|
+
|
22
|
+
class SerializedBundle(TypedDict):
|
23
|
+
"""
|
24
|
+
A serialized bundle is a serialized function, context, and flow run that can be
|
25
|
+
easily transported for later execution.
|
26
|
+
"""
|
27
|
+
|
28
|
+
function: str
|
29
|
+
context: str
|
30
|
+
flow_run: dict[str, Any]
|
31
|
+
|
32
|
+
|
33
|
+
def _serialize_bundle_object(obj: Any) -> str:
|
34
|
+
"""
|
35
|
+
Serializes an object to a string.
|
36
|
+
"""
|
37
|
+
return base64.b64encode(gzip.compress(cloudpickle.dumps(obj))).decode()
|
38
|
+
|
39
|
+
|
40
|
+
def _deserialize_bundle_object(serialized_obj: str) -> Any:
|
41
|
+
"""
|
42
|
+
Deserializes an object from a string.
|
43
|
+
"""
|
44
|
+
return cloudpickle.loads(gzip.decompress(base64.b64decode(serialized_obj)))
|
45
|
+
|
46
|
+
|
47
|
+
def create_bundle_for_flow_run(
|
48
|
+
flow: Flow[Any, Any],
|
49
|
+
flow_run: FlowRun,
|
50
|
+
context: dict[str, Any] | None = None,
|
51
|
+
) -> SerializedBundle:
|
52
|
+
"""
|
53
|
+
Creates a bundle for a flow run.
|
54
|
+
|
55
|
+
Args:
|
56
|
+
flow: The flow to bundle.
|
57
|
+
flow_run: The flow run to bundle.
|
58
|
+
context: The context to use when running the flow.
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
A serialized bundle.
|
62
|
+
"""
|
63
|
+
context = context or serialize_context()
|
64
|
+
|
65
|
+
return {
|
66
|
+
"function": _serialize_bundle_object(flow),
|
67
|
+
"context": _serialize_bundle_object(context),
|
68
|
+
"flow_run": flow_run.model_dump(mode="json"),
|
69
|
+
}
|
70
|
+
|
71
|
+
|
72
|
+
def extract_flow_from_bundle(bundle: SerializedBundle) -> Flow[Any, Any]:
|
73
|
+
"""
|
74
|
+
Extracts a flow from a bundle.
|
75
|
+
"""
|
76
|
+
return _deserialize_bundle_object(bundle["function"])
|
77
|
+
|
78
|
+
|
79
|
+
def _extract_and_run_flow(
|
80
|
+
bundle: SerializedBundle, env: dict[str, Any] | None = None
|
81
|
+
) -> None:
|
82
|
+
"""
|
83
|
+
Extracts a flow from a bundle and runs it.
|
84
|
+
|
85
|
+
Designed to be run in a subprocess.
|
86
|
+
|
87
|
+
Args:
|
88
|
+
bundle: The bundle to extract and run.
|
89
|
+
env: The environment to use when running the flow.
|
90
|
+
"""
|
91
|
+
|
92
|
+
os.environ.update(env or {})
|
93
|
+
# TODO: make this a thing we can pass directly to the engine
|
94
|
+
os.environ["PREFECT__ENABLE_CANCELLATION_AND_CRASHED_HOOKS"] = "false"
|
95
|
+
settings_context = get_settings_context()
|
96
|
+
|
97
|
+
flow = _deserialize_bundle_object(bundle["function"])
|
98
|
+
context = _deserialize_bundle_object(bundle["context"])
|
99
|
+
flow_run = FlowRun.model_validate(bundle["flow_run"])
|
100
|
+
|
101
|
+
with SettingsContext(
|
102
|
+
profile=settings_context.profile,
|
103
|
+
settings=Settings(),
|
104
|
+
):
|
105
|
+
with handle_engine_signals(flow_run.id):
|
106
|
+
maybe_coro = run_flow(
|
107
|
+
flow=flow,
|
108
|
+
flow_run=flow_run,
|
109
|
+
context=context,
|
110
|
+
)
|
111
|
+
if asyncio.iscoroutine(maybe_coro):
|
112
|
+
# This is running in a brand new process, so there won't be an existing
|
113
|
+
# event loop.
|
114
|
+
asyncio.run(maybe_coro)
|
115
|
+
|
116
|
+
|
117
|
+
def execute_bundle_in_subprocess(
|
118
|
+
bundle: SerializedBundle,
|
119
|
+
) -> multiprocessing.context.SpawnProcess:
|
120
|
+
"""
|
121
|
+
Executes a bundle in a subprocess.
|
122
|
+
|
123
|
+
Args:
|
124
|
+
bundle: The bundle to execute.
|
125
|
+
|
126
|
+
Returns:
|
127
|
+
A multiprocessing.context.SpawnProcess.
|
128
|
+
"""
|
129
|
+
|
130
|
+
ctx = multiprocessing.get_context("spawn")
|
131
|
+
|
132
|
+
process = ctx.Process(
|
133
|
+
target=_extract_and_run_flow,
|
134
|
+
kwargs={
|
135
|
+
"bundle": bundle,
|
136
|
+
"env": get_current_settings().to_environment_variables(exclude_unset=True)
|
137
|
+
| os.environ,
|
138
|
+
},
|
139
|
+
)
|
140
|
+
|
141
|
+
process.start()
|
142
|
+
|
143
|
+
return process
|
@@ -294,7 +294,7 @@ def default_timezone(
|
|
294
294
|
|
295
295
|
|
296
296
|
def validate_cron_string(v: str) -> str:
|
297
|
-
from croniter import croniter
|
297
|
+
from prefect._vendor.croniter import croniter
|
298
298
|
|
299
299
|
# croniter allows "random" and "hashed" expressions
|
300
300
|
# which we do not support https://github.com/kiorky/croniter
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
from __future__ import absolute_import
|
3
|
+
|
4
|
+
from . import croniter as cron_m
|
5
|
+
from .croniter import (
|
6
|
+
DAY_FIELD,
|
7
|
+
HOUR_FIELD,
|
8
|
+
MINUTE_FIELD,
|
9
|
+
MONTH_FIELD,
|
10
|
+
OVERFLOW32B_MODE,
|
11
|
+
SECOND_FIELD,
|
12
|
+
UTC_DT,
|
13
|
+
YEAR_FIELD,
|
14
|
+
CroniterBadCronError,
|
15
|
+
CroniterBadDateError,
|
16
|
+
CroniterBadTypeRangeError,
|
17
|
+
CroniterError,
|
18
|
+
CroniterNotAlphaError,
|
19
|
+
CroniterUnsupportedSyntaxError,
|
20
|
+
croniter,
|
21
|
+
croniter_range,
|
22
|
+
datetime_to_timestamp,
|
23
|
+
)
|
24
|
+
|
25
|
+
croniter.__name__ # make flake8 happy
|