prefect-client 3.1.5__py3-none-any.whl → 3.1.7__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/__init__.py +3 -0
- prefect/_experimental/__init__.py +0 -0
- prefect/_experimental/lineage.py +181 -0
- prefect/_internal/compatibility/async_dispatch.py +38 -9
- prefect/_internal/compatibility/migration.py +1 -1
- prefect/_internal/concurrency/api.py +52 -52
- prefect/_internal/concurrency/calls.py +59 -35
- prefect/_internal/concurrency/cancellation.py +34 -18
- prefect/_internal/concurrency/event_loop.py +7 -6
- prefect/_internal/concurrency/threads.py +41 -33
- prefect/_internal/concurrency/waiters.py +28 -21
- prefect/_internal/pydantic/v1_schema.py +2 -2
- prefect/_internal/pydantic/v2_schema.py +10 -9
- prefect/_internal/pydantic/v2_validated_func.py +15 -10
- prefect/_internal/retries.py +15 -6
- prefect/_internal/schemas/bases.py +11 -8
- prefect/_internal/schemas/validators.py +7 -5
- prefect/_version.py +3 -3
- prefect/automations.py +53 -47
- prefect/blocks/abstract.py +12 -10
- prefect/blocks/core.py +148 -19
- prefect/blocks/system.py +2 -1
- prefect/cache_policies.py +11 -11
- prefect/client/__init__.py +3 -1
- prefect/client/base.py +36 -37
- prefect/client/cloud.py +26 -19
- prefect/client/collections.py +2 -2
- prefect/client/orchestration.py +430 -273
- prefect/client/schemas/__init__.py +24 -0
- prefect/client/schemas/actions.py +128 -121
- prefect/client/schemas/filters.py +1 -1
- prefect/client/schemas/objects.py +114 -85
- prefect/client/schemas/responses.py +19 -20
- prefect/client/schemas/schedules.py +136 -93
- prefect/client/subscriptions.py +30 -15
- prefect/client/utilities.py +46 -36
- prefect/concurrency/asyncio.py +6 -9
- prefect/concurrency/sync.py +35 -5
- prefect/context.py +40 -32
- prefect/deployments/flow_runs.py +6 -8
- prefect/deployments/runner.py +14 -14
- prefect/deployments/steps/core.py +3 -1
- prefect/deployments/steps/pull.py +60 -12
- prefect/docker/__init__.py +1 -1
- prefect/events/clients.py +55 -4
- prefect/events/filters.py +1 -1
- prefect/events/related.py +2 -1
- prefect/events/schemas/events.py +26 -21
- prefect/events/utilities.py +3 -2
- prefect/events/worker.py +8 -0
- prefect/filesystems.py +3 -3
- prefect/flow_engine.py +87 -87
- prefect/flow_runs.py +7 -5
- prefect/flows.py +218 -176
- prefect/logging/configuration.py +1 -1
- prefect/logging/highlighters.py +1 -2
- prefect/logging/loggers.py +30 -20
- prefect/main.py +17 -24
- prefect/results.py +43 -22
- prefect/runner/runner.py +43 -21
- prefect/runner/server.py +30 -32
- prefect/runner/storage.py +3 -3
- prefect/runner/submit.py +3 -6
- prefect/runner/utils.py +6 -6
- prefect/runtime/flow_run.py +7 -0
- prefect/serializers.py +28 -24
- prefect/settings/constants.py +2 -2
- prefect/settings/legacy.py +1 -1
- prefect/settings/models/experiments.py +5 -0
- prefect/settings/models/server/events.py +10 -0
- prefect/task_engine.py +87 -26
- prefect/task_runners.py +2 -2
- prefect/task_worker.py +43 -25
- prefect/tasks.py +148 -142
- prefect/telemetry/bootstrap.py +15 -2
- prefect/telemetry/instrumentation.py +1 -1
- prefect/telemetry/processors.py +10 -7
- prefect/telemetry/run_telemetry.py +231 -0
- prefect/transactions.py +14 -14
- prefect/types/__init__.py +5 -5
- prefect/utilities/_engine.py +96 -0
- prefect/utilities/annotations.py +25 -18
- prefect/utilities/asyncutils.py +126 -140
- prefect/utilities/callables.py +87 -78
- prefect/utilities/collections.py +278 -117
- prefect/utilities/compat.py +13 -21
- prefect/utilities/context.py +6 -5
- prefect/utilities/dispatch.py +23 -12
- prefect/utilities/dockerutils.py +33 -32
- prefect/utilities/engine.py +126 -239
- prefect/utilities/filesystem.py +18 -15
- prefect/utilities/hashing.py +10 -11
- prefect/utilities/importtools.py +40 -27
- prefect/utilities/math.py +9 -5
- prefect/utilities/names.py +3 -3
- prefect/utilities/processutils.py +121 -57
- prefect/utilities/pydantic.py +41 -36
- prefect/utilities/render_swagger.py +22 -12
- prefect/utilities/schema_tools/__init__.py +2 -1
- prefect/utilities/schema_tools/hydration.py +50 -43
- prefect/utilities/schema_tools/validation.py +52 -42
- prefect/utilities/services.py +13 -12
- prefect/utilities/templating.py +45 -45
- prefect/utilities/text.py +2 -1
- prefect/utilities/timeout.py +4 -4
- prefect/utilities/urls.py +9 -4
- prefect/utilities/visualization.py +46 -24
- prefect/variables.py +136 -27
- prefect/workers/base.py +15 -8
- {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/METADATA +5 -2
- {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/RECORD +114 -110
- {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/LICENSE +0 -0
- {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/WHEEL +0 -0
- {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/top_level.txt +0 -0
prefect/telemetry/processors.py
CHANGED
@@ -1,14 +1,17 @@
|
|
1
1
|
import time
|
2
2
|
from threading import Event, Lock, Thread
|
3
|
-
from typing import Dict, Optional
|
3
|
+
from typing import TYPE_CHECKING, Dict, Optional
|
4
4
|
|
5
5
|
from opentelemetry.context import Context
|
6
|
-
from opentelemetry.sdk.trace import
|
7
|
-
|
6
|
+
from opentelemetry.sdk.trace import Span, SpanProcessor
|
7
|
+
|
8
|
+
if TYPE_CHECKING:
|
9
|
+
from opentelemetry.sdk.trace import ReadableSpan, Span
|
10
|
+
from opentelemetry.sdk.trace.export import SpanExporter
|
8
11
|
|
9
12
|
|
10
13
|
class InFlightSpanProcessor(SpanProcessor):
|
11
|
-
def __init__(self, span_exporter: SpanExporter):
|
14
|
+
def __init__(self, span_exporter: "SpanExporter"):
|
12
15
|
self.span_exporter = span_exporter
|
13
16
|
self._in_flight: Dict[int, Span] = {}
|
14
17
|
self._lock = Lock()
|
@@ -26,7 +29,7 @@ class InFlightSpanProcessor(SpanProcessor):
|
|
26
29
|
if to_export:
|
27
30
|
self.span_exporter.export(to_export)
|
28
31
|
|
29
|
-
def _readable_span(self, span: Span) -> ReadableSpan:
|
32
|
+
def _readable_span(self, span: "Span") -> "ReadableSpan":
|
30
33
|
readable = span._readable_span()
|
31
34
|
readable._end_time = time.time_ns()
|
32
35
|
readable._attributes = {
|
@@ -35,13 +38,13 @@ class InFlightSpanProcessor(SpanProcessor):
|
|
35
38
|
}
|
36
39
|
return readable
|
37
40
|
|
38
|
-
def on_start(self, span: Span, parent_context: Optional[Context] = None) -> None:
|
41
|
+
def on_start(self, span: "Span", parent_context: Optional[Context] = None) -> None:
|
39
42
|
if not span.context or not span.context.trace_flags.sampled:
|
40
43
|
return
|
41
44
|
with self._lock:
|
42
45
|
self._in_flight[span.context.span_id] = span
|
43
46
|
|
44
|
-
def on_end(self, span: ReadableSpan) -> None:
|
47
|
+
def on_end(self, span: "ReadableSpan") -> None:
|
45
48
|
if not span.context or not span.context.trace_flags.sampled:
|
46
49
|
return
|
47
50
|
with self._lock:
|
@@ -0,0 +1,231 @@
|
|
1
|
+
import time
|
2
|
+
from dataclasses import dataclass, field
|
3
|
+
from typing import TYPE_CHECKING, Any, Optional, Union
|
4
|
+
|
5
|
+
from opentelemetry import propagate, trace
|
6
|
+
from opentelemetry.context import Context
|
7
|
+
from opentelemetry.propagators.textmap import Setter
|
8
|
+
from opentelemetry.trace import (
|
9
|
+
Span,
|
10
|
+
Status,
|
11
|
+
StatusCode,
|
12
|
+
get_tracer,
|
13
|
+
)
|
14
|
+
from typing_extensions import TypeAlias
|
15
|
+
|
16
|
+
import prefect
|
17
|
+
from prefect.client.orchestration import PrefectClient, SyncPrefectClient
|
18
|
+
from prefect.client.schemas import FlowRun, TaskRun
|
19
|
+
from prefect.client.schemas.objects import State
|
20
|
+
from prefect.context import FlowRunContext, TaskRunContext
|
21
|
+
from prefect.types import KeyValueLabels
|
22
|
+
|
23
|
+
if TYPE_CHECKING:
|
24
|
+
from opentelemetry.trace import Tracer
|
25
|
+
|
26
|
+
LABELS_TRACEPARENT_KEY = "__OTEL_TRACEPARENT"
|
27
|
+
TRACEPARENT_KEY = "traceparent"
|
28
|
+
|
29
|
+
FlowOrTaskRun: TypeAlias = Union[FlowRun, TaskRun]
|
30
|
+
|
31
|
+
|
32
|
+
class OTELSetter(Setter[KeyValueLabels]):
|
33
|
+
"""
|
34
|
+
A setter for OpenTelemetry that supports Prefect's custom labels.
|
35
|
+
"""
|
36
|
+
|
37
|
+
def set(self, carrier: KeyValueLabels, key: str, value: str) -> None:
|
38
|
+
carrier[key] = value
|
39
|
+
|
40
|
+
|
41
|
+
@dataclass
|
42
|
+
class RunTelemetry:
|
43
|
+
"""
|
44
|
+
A class for managing the telemetry of runs.
|
45
|
+
"""
|
46
|
+
|
47
|
+
_tracer: "Tracer" = field(
|
48
|
+
default_factory=lambda: get_tracer("prefect", prefect.__version__)
|
49
|
+
)
|
50
|
+
span: Optional[Span] = None
|
51
|
+
|
52
|
+
async def async_start_span(
|
53
|
+
self,
|
54
|
+
run: FlowOrTaskRun,
|
55
|
+
client: PrefectClient,
|
56
|
+
name: Optional[str] = None,
|
57
|
+
parameters: Optional[dict[str, Any]] = None,
|
58
|
+
):
|
59
|
+
traceparent, span = self._start_span(run, name, parameters)
|
60
|
+
|
61
|
+
if self._run_type(run) == "flow" and traceparent:
|
62
|
+
# Only explicitly update labels if the run is a flow as task runs
|
63
|
+
# are updated via events.
|
64
|
+
await client.update_flow_run_labels(
|
65
|
+
run.id, {LABELS_TRACEPARENT_KEY: traceparent}
|
66
|
+
)
|
67
|
+
|
68
|
+
return span
|
69
|
+
|
70
|
+
def start_span(
|
71
|
+
self,
|
72
|
+
run: FlowOrTaskRun,
|
73
|
+
client: SyncPrefectClient,
|
74
|
+
name: Optional[str] = None,
|
75
|
+
parameters: Optional[dict[str, Any]] = None,
|
76
|
+
):
|
77
|
+
traceparent, span = self._start_span(run, name, parameters)
|
78
|
+
|
79
|
+
if self._run_type(run) == "flow" and traceparent:
|
80
|
+
# Only explicitly update labels if the run is a flow as task runs
|
81
|
+
# are updated via events.
|
82
|
+
client.update_flow_run_labels(run.id, {LABELS_TRACEPARENT_KEY: traceparent})
|
83
|
+
|
84
|
+
return span
|
85
|
+
|
86
|
+
def _start_span(
|
87
|
+
self,
|
88
|
+
run: FlowOrTaskRun,
|
89
|
+
name: Optional[str] = None,
|
90
|
+
parameters: Optional[dict[str, Any]] = None,
|
91
|
+
) -> tuple[Optional[str], Span]:
|
92
|
+
"""
|
93
|
+
Start a span for a run.
|
94
|
+
"""
|
95
|
+
if parameters is None:
|
96
|
+
parameters = {}
|
97
|
+
|
98
|
+
parameter_attributes = {
|
99
|
+
f"prefect.run.parameter.{k}": type(v).__name__
|
100
|
+
for k, v in parameters.items()
|
101
|
+
}
|
102
|
+
|
103
|
+
# Use existing trace context if this run already has one (e.g., from
|
104
|
+
# server operations like Late), otherwise use parent's trace context if
|
105
|
+
# available (e.g., nested flow / task runs). If neither exists, this
|
106
|
+
# will be a root span (e.g., a top-level flow run).
|
107
|
+
if LABELS_TRACEPARENT_KEY in run.labels:
|
108
|
+
context = self._trace_context_from_labels(run.labels)
|
109
|
+
else:
|
110
|
+
parent_run = self._parent_run()
|
111
|
+
parent_labels = parent_run.labels if parent_run else {}
|
112
|
+
if LABELS_TRACEPARENT_KEY in parent_labels:
|
113
|
+
context = self._trace_context_from_labels(parent_labels)
|
114
|
+
else:
|
115
|
+
context = None
|
116
|
+
|
117
|
+
run_type = self._run_type(run)
|
118
|
+
|
119
|
+
self.span = self._tracer.start_span(
|
120
|
+
name=name or run.name,
|
121
|
+
context=context,
|
122
|
+
attributes={
|
123
|
+
"prefect.run.name": name or run.name,
|
124
|
+
"prefect.run.type": run_type,
|
125
|
+
"prefect.run.id": str(run.id),
|
126
|
+
"prefect.tags": run.tags,
|
127
|
+
**parameter_attributes,
|
128
|
+
**{
|
129
|
+
key: value
|
130
|
+
for key, value in run.labels.items()
|
131
|
+
if not key.startswith("__") # exclude internal labels
|
132
|
+
},
|
133
|
+
},
|
134
|
+
)
|
135
|
+
|
136
|
+
if traceparent := self._traceparent_from_span(self.span):
|
137
|
+
run.labels[LABELS_TRACEPARENT_KEY] = traceparent
|
138
|
+
|
139
|
+
return traceparent, self.span
|
140
|
+
|
141
|
+
def _run_type(self, run: FlowOrTaskRun) -> str:
|
142
|
+
return "task" if isinstance(run, TaskRun) else "flow"
|
143
|
+
|
144
|
+
def _trace_context_from_labels(
|
145
|
+
self, labels: Optional[KeyValueLabels]
|
146
|
+
) -> Optional[Context]:
|
147
|
+
"""Get trace context from run labels if it exists."""
|
148
|
+
if not labels or LABELS_TRACEPARENT_KEY not in labels:
|
149
|
+
return None
|
150
|
+
traceparent = labels[LABELS_TRACEPARENT_KEY]
|
151
|
+
carrier = {TRACEPARENT_KEY: traceparent}
|
152
|
+
return propagate.extract(carrier)
|
153
|
+
|
154
|
+
def _traceparent_from_span(self, span: Span) -> Optional[str]:
|
155
|
+
carrier = {}
|
156
|
+
propagate.inject(carrier, context=trace.set_span_in_context(span))
|
157
|
+
return carrier.get(TRACEPARENT_KEY)
|
158
|
+
|
159
|
+
def end_span_on_success(self) -> None:
|
160
|
+
"""
|
161
|
+
End a span for a run on success.
|
162
|
+
"""
|
163
|
+
if self.span:
|
164
|
+
self.span.set_status(Status(StatusCode.OK))
|
165
|
+
self.span.end(time.time_ns())
|
166
|
+
self.span = None
|
167
|
+
|
168
|
+
def end_span_on_failure(self, terminal_message: Optional[str] = None) -> None:
|
169
|
+
"""
|
170
|
+
End a span for a run on failure.
|
171
|
+
"""
|
172
|
+
if self.span:
|
173
|
+
self.span.set_status(
|
174
|
+
Status(StatusCode.ERROR, terminal_message or "Run failed")
|
175
|
+
)
|
176
|
+
self.span.end(time.time_ns())
|
177
|
+
self.span = None
|
178
|
+
|
179
|
+
def record_exception(self, exc: BaseException) -> None:
|
180
|
+
"""
|
181
|
+
Record an exception on a span.
|
182
|
+
"""
|
183
|
+
if self.span:
|
184
|
+
self.span.record_exception(exc)
|
185
|
+
|
186
|
+
def update_state(self, new_state: State) -> None:
|
187
|
+
"""
|
188
|
+
Update a span with the state of a run.
|
189
|
+
"""
|
190
|
+
if self.span:
|
191
|
+
self.span.add_event(
|
192
|
+
new_state.name or new_state.type,
|
193
|
+
{
|
194
|
+
"prefect.state.message": new_state.message or "",
|
195
|
+
"prefect.state.type": new_state.type,
|
196
|
+
"prefect.state.name": new_state.name or new_state.type,
|
197
|
+
"prefect.state.id": str(new_state.id),
|
198
|
+
},
|
199
|
+
)
|
200
|
+
|
201
|
+
def _parent_run(self) -> Union[FlowOrTaskRun, None]:
|
202
|
+
"""
|
203
|
+
Identify the "parent run" for the current execution context.
|
204
|
+
|
205
|
+
Both flows and tasks can be nested "infinitely," and each creates a
|
206
|
+
corresponding context when executed. This method determines the most
|
207
|
+
appropriate parent context (either a task run or a flow run) based on
|
208
|
+
their relationship in the current hierarchy.
|
209
|
+
|
210
|
+
Returns:
|
211
|
+
FlowOrTaskRun: The parent run object (task or flow) if applicable.
|
212
|
+
None: If there is no parent context, implying the current run is the top-level parent.
|
213
|
+
"""
|
214
|
+
parent_flow_run_context = FlowRunContext.get()
|
215
|
+
parent_task_run_context = TaskRunContext.get()
|
216
|
+
|
217
|
+
if parent_task_run_context and parent_flow_run_context:
|
218
|
+
# If both contexts exist, which is common for nested flows or tasks,
|
219
|
+
# check if the task's flow_run_id matches the current flow_run.
|
220
|
+
# If they match, the task is a child of the flow and is the parent of the current run.
|
221
|
+
flow_run_id = getattr(parent_flow_run_context.flow_run, "id", None)
|
222
|
+
if parent_task_run_context.task_run.flow_run_id == flow_run_id:
|
223
|
+
return parent_task_run_context.task_run
|
224
|
+
# Otherwise, assume the flow run is the entry point and is the parent.
|
225
|
+
return parent_flow_run_context.flow_run
|
226
|
+
elif parent_flow_run_context:
|
227
|
+
return parent_flow_run_context.flow_run
|
228
|
+
elif parent_task_run_context:
|
229
|
+
return parent_task_run_context.task_run
|
230
|
+
|
231
|
+
return None
|
prefect/transactions.py
CHANGED
@@ -23,7 +23,7 @@ from prefect.exceptions import (
|
|
23
23
|
MissingContextError,
|
24
24
|
SerializationError,
|
25
25
|
)
|
26
|
-
from prefect.logging.loggers import get_logger, get_run_logger
|
26
|
+
from prefect.logging.loggers import LoggingAdapter, get_logger, get_run_logger
|
27
27
|
from prefect.records import RecordStore
|
28
28
|
from prefect.records.base import TransactionRecord
|
29
29
|
from prefect.results import (
|
@@ -32,9 +32,9 @@ from prefect.results import (
|
|
32
32
|
ResultStore,
|
33
33
|
get_result_store,
|
34
34
|
)
|
35
|
+
from prefect.utilities._engine import get_hook_name
|
35
36
|
from prefect.utilities.annotations import NotSet
|
36
37
|
from prefect.utilities.collections import AutoEnum
|
37
|
-
from prefect.utilities.engine import _get_hook_name
|
38
38
|
|
39
39
|
|
40
40
|
class IsolationLevel(AutoEnum):
|
@@ -72,13 +72,13 @@ class Transaction(ContextModel):
|
|
72
72
|
default_factory=list
|
73
73
|
)
|
74
74
|
overwrite: bool = False
|
75
|
-
logger: Union[logging.Logger,
|
75
|
+
logger: Union[logging.Logger, LoggingAdapter] = Field(
|
76
76
|
default_factory=partial(get_logger, "transactions")
|
77
77
|
)
|
78
78
|
write_on_commit: bool = True
|
79
79
|
_stored_values: Dict[str, Any] = PrivateAttr(default_factory=dict)
|
80
80
|
_staged_value: Any = None
|
81
|
-
__var__: ContextVar = ContextVar("transaction")
|
81
|
+
__var__: ContextVar[Self] = ContextVar("transaction")
|
82
82
|
|
83
83
|
def set(self, name: str, value: Any) -> None:
|
84
84
|
"""
|
@@ -209,7 +209,7 @@ class Transaction(ContextModel):
|
|
209
209
|
self._token = self.__var__.set(self)
|
210
210
|
return self
|
211
211
|
|
212
|
-
def __exit__(self, *exc_info):
|
212
|
+
def __exit__(self, *exc_info: Any):
|
213
213
|
exc_type, exc_val, _ = exc_info
|
214
214
|
if not self._token:
|
215
215
|
raise RuntimeError(
|
@@ -254,7 +254,7 @@ class Transaction(ContextModel):
|
|
254
254
|
):
|
255
255
|
self.state = TransactionState.COMMITTED
|
256
256
|
|
257
|
-
def read(self) -> Union["BaseResult", ResultRecord, None]:
|
257
|
+
def read(self) -> Union["BaseResult[Any]", ResultRecord[Any], None]:
|
258
258
|
if self.store and self.key:
|
259
259
|
record = self.store.read(key=self.key)
|
260
260
|
if isinstance(record, ResultRecord):
|
@@ -354,8 +354,8 @@ class Transaction(ContextModel):
|
|
354
354
|
self.rollback()
|
355
355
|
return False
|
356
356
|
|
357
|
-
def run_hook(self, hook, hook_type: str) -> None:
|
358
|
-
hook_name =
|
357
|
+
def run_hook(self, hook: Callable[..., Any], hook_type: str) -> None:
|
358
|
+
hook_name = get_hook_name(hook)
|
359
359
|
# Undocumented way to disable logging for a hook. Subject to change.
|
360
360
|
should_log = getattr(hook, "log_on_run", True)
|
361
361
|
|
@@ -379,8 +379,8 @@ class Transaction(ContextModel):
|
|
379
379
|
def stage(
|
380
380
|
self,
|
381
381
|
value: Any,
|
382
|
-
on_rollback_hooks: Optional[
|
383
|
-
on_commit_hooks: Optional[
|
382
|
+
on_rollback_hooks: Optional[list[Callable[..., Any]]] = None,
|
383
|
+
on_commit_hooks: Optional[list[Callable[..., Any]]] = None,
|
384
384
|
) -> None:
|
385
385
|
"""
|
386
386
|
Stage a value to be committed later.
|
@@ -441,7 +441,7 @@ def transaction(
|
|
441
441
|
isolation_level: Optional[IsolationLevel] = None,
|
442
442
|
overwrite: bool = False,
|
443
443
|
write_on_commit: bool = True,
|
444
|
-
logger: Union[logging.Logger,
|
444
|
+
logger: Optional[Union[logging.Logger, LoggingAdapter]] = None,
|
445
445
|
) -> Generator[Transaction, None, None]:
|
446
446
|
"""
|
447
447
|
A context manager for opening and managing a transaction.
|
@@ -465,9 +465,9 @@ def transaction(
|
|
465
465
|
store = get_result_store()
|
466
466
|
|
467
467
|
try:
|
468
|
-
|
468
|
+
_logger: Union[logging.Logger, LoggingAdapter] = logger or get_run_logger()
|
469
469
|
except MissingContextError:
|
470
|
-
|
470
|
+
_logger = get_logger("transactions")
|
471
471
|
|
472
472
|
with Transaction(
|
473
473
|
key=key,
|
@@ -476,6 +476,6 @@ def transaction(
|
|
476
476
|
isolation_level=isolation_level,
|
477
477
|
overwrite=overwrite,
|
478
478
|
write_on_commit=write_on_commit,
|
479
|
-
logger=
|
479
|
+
logger=_logger,
|
480
480
|
) as txn:
|
481
481
|
yield txn
|
prefect/types/__init__.py
CHANGED
@@ -3,7 +3,8 @@ from typing import Annotated, Any, Dict, List, Optional, Set, TypeVar, Union
|
|
3
3
|
from typing_extensions import Literal, TypeAlias
|
4
4
|
import orjson
|
5
5
|
import pydantic
|
6
|
-
|
6
|
+
from pydantic_extra_types.pendulum_dt import DateTime as PydanticDateTime
|
7
|
+
from pydantic_extra_types.pendulum_dt import Date as PydanticDate
|
7
8
|
from pydantic import (
|
8
9
|
BeforeValidator,
|
9
10
|
Field,
|
@@ -34,6 +35,8 @@ TimeZone = Annotated[
|
|
34
35
|
),
|
35
36
|
]
|
36
37
|
|
38
|
+
DateTime: TypeAlias = PydanticDateTime
|
39
|
+
Date: TypeAlias = PydanticDate
|
37
40
|
|
38
41
|
BANNED_CHARACTERS = ["/", "%", "&", ">", "<"]
|
39
42
|
|
@@ -96,7 +99,7 @@ def cast_none_to_empty_dict(value: Any) -> dict[str, Any]:
|
|
96
99
|
|
97
100
|
|
98
101
|
KeyValueLabels = Annotated[
|
99
|
-
|
102
|
+
Dict[str, Union[StrictBool, StrictInt, StrictFloat, str]],
|
100
103
|
BeforeValidator(cast_none_to_empty_dict),
|
101
104
|
]
|
102
105
|
|
@@ -149,9 +152,6 @@ LogLevel = Annotated[
|
|
149
152
|
]
|
150
153
|
|
151
154
|
|
152
|
-
KeyValueLabels: TypeAlias = dict[str, Union[StrictBool, StrictInt, StrictFloat, str]]
|
153
|
-
|
154
|
-
|
155
155
|
def convert_none_to_empty_dict(v: Optional[KeyValueLabels]) -> KeyValueLabels:
|
156
156
|
return v or {}
|
157
157
|
|
@@ -0,0 +1,96 @@
|
|
1
|
+
"""Internal engine utilities"""
|
2
|
+
|
3
|
+
|
4
|
+
from collections.abc import Callable
|
5
|
+
from functools import partial
|
6
|
+
from typing import TYPE_CHECKING, Any, Union
|
7
|
+
from uuid import uuid4
|
8
|
+
|
9
|
+
from prefect.context import FlowRunContext
|
10
|
+
from prefect.flows import Flow
|
11
|
+
from prefect.tasks import Task, TaskRunNameCallbackWithParameters
|
12
|
+
|
13
|
+
|
14
|
+
def dynamic_key_for_task_run(
|
15
|
+
context: FlowRunContext, task: "Task[..., Any]", stable: bool = True
|
16
|
+
) -> Union[int, str]:
|
17
|
+
if (
|
18
|
+
stable is False or context.detached
|
19
|
+
): # this task is running on remote infrastructure
|
20
|
+
return str(uuid4())
|
21
|
+
elif context.flow_run is None: # this is an autonomous task run
|
22
|
+
context.task_run_dynamic_keys[task.task_key] = getattr(
|
23
|
+
task, "dynamic_key", str(uuid4())
|
24
|
+
)
|
25
|
+
|
26
|
+
elif task.task_key not in context.task_run_dynamic_keys:
|
27
|
+
context.task_run_dynamic_keys[task.task_key] = 0
|
28
|
+
else:
|
29
|
+
dynamic_key = context.task_run_dynamic_keys[task.task_key]
|
30
|
+
if TYPE_CHECKING:
|
31
|
+
assert isinstance(dynamic_key, int)
|
32
|
+
context.task_run_dynamic_keys[task.task_key] = dynamic_key + 1
|
33
|
+
|
34
|
+
return context.task_run_dynamic_keys[task.task_key]
|
35
|
+
|
36
|
+
|
37
|
+
def resolve_custom_flow_run_name(
|
38
|
+
flow: "Flow[..., Any]", parameters: dict[str, Any]
|
39
|
+
) -> str:
|
40
|
+
if callable(flow.flow_run_name):
|
41
|
+
flow_run_name = flow.flow_run_name()
|
42
|
+
if not TYPE_CHECKING:
|
43
|
+
if not isinstance(flow_run_name, str):
|
44
|
+
raise TypeError(
|
45
|
+
f"Callable {flow.flow_run_name} for 'flow_run_name' returned type"
|
46
|
+
f" {type(flow_run_name).__name__} but a string is required."
|
47
|
+
)
|
48
|
+
elif isinstance(flow.flow_run_name, str):
|
49
|
+
flow_run_name = flow.flow_run_name.format(**parameters)
|
50
|
+
else:
|
51
|
+
raise TypeError(
|
52
|
+
"Expected string or callable for 'flow_run_name'; got"
|
53
|
+
f" {type(flow.flow_run_name).__name__} instead."
|
54
|
+
)
|
55
|
+
|
56
|
+
return flow_run_name
|
57
|
+
|
58
|
+
|
59
|
+
def resolve_custom_task_run_name(
|
60
|
+
task: "Task[..., Any]", parameters: dict[str, Any]
|
61
|
+
) -> str:
|
62
|
+
if callable(task.task_run_name):
|
63
|
+
# If the callable accepts a 'parameters' kwarg, pass the entire parameters dict
|
64
|
+
if TaskRunNameCallbackWithParameters.is_callback_with_parameters(
|
65
|
+
task.task_run_name
|
66
|
+
):
|
67
|
+
task_run_name = task.task_run_name(parameters=parameters)
|
68
|
+
else:
|
69
|
+
# If it doesn't expect parameters, call it without arguments
|
70
|
+
task_run_name = task.task_run_name()
|
71
|
+
|
72
|
+
if not TYPE_CHECKING:
|
73
|
+
if not isinstance(task_run_name, str):
|
74
|
+
raise TypeError(
|
75
|
+
f"Callable {task.task_run_name} for 'task_run_name' returned type"
|
76
|
+
f" {type(task_run_name).__name__} but a string is required."
|
77
|
+
)
|
78
|
+
elif isinstance(task.task_run_name, str):
|
79
|
+
task_run_name = task.task_run_name.format(**parameters)
|
80
|
+
else:
|
81
|
+
raise TypeError(
|
82
|
+
"Expected string or callable for 'task_run_name'; got"
|
83
|
+
f" {type(task.task_run_name).__name__} instead."
|
84
|
+
)
|
85
|
+
|
86
|
+
return task_run_name
|
87
|
+
|
88
|
+
|
89
|
+
def get_hook_name(hook: Callable[..., Any]) -> str:
|
90
|
+
return (
|
91
|
+
hook.__name__
|
92
|
+
if hasattr(hook, "__name__")
|
93
|
+
else (
|
94
|
+
hook.func.__name__ if isinstance(hook, partial) else hook.__class__.__name__
|
95
|
+
)
|
96
|
+
)
|
prefect/utilities/annotations.py
CHANGED
@@ -1,33 +1,40 @@
|
|
1
1
|
import warnings
|
2
|
-
from
|
3
|
-
from
|
4
|
-
from typing import Generic, TypeVar
|
2
|
+
from operator import itemgetter
|
3
|
+
from typing import Any, cast
|
5
4
|
|
6
|
-
|
5
|
+
from typing_extensions import Self, TypeVar
|
7
6
|
|
7
|
+
T = TypeVar("T", infer_variance=True)
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
):
|
9
|
+
|
10
|
+
class BaseAnnotation(tuple[T]):
|
12
11
|
"""
|
13
12
|
Base class for Prefect annotation types.
|
14
13
|
|
15
|
-
Inherits from `
|
14
|
+
Inherits from `tuple` for unpacking support in other tools.
|
16
15
|
"""
|
17
16
|
|
17
|
+
__slots__ = ()
|
18
|
+
|
19
|
+
def __new__(cls, value: T) -> Self:
|
20
|
+
return super().__new__(cls, (value,))
|
21
|
+
|
22
|
+
# use itemgetter to minimise overhead, just like namedtuple generated code would
|
23
|
+
value: T = cast(T, property(itemgetter(0)))
|
24
|
+
|
18
25
|
def unwrap(self) -> T:
|
19
|
-
return self
|
26
|
+
return self[0]
|
20
27
|
|
21
|
-
def rewrap(self, value: T) ->
|
28
|
+
def rewrap(self, value: T) -> Self:
|
22
29
|
return type(self)(value)
|
23
30
|
|
24
|
-
def __eq__(self, other:
|
31
|
+
def __eq__(self, other: Any) -> bool:
|
25
32
|
if type(self) is not type(other):
|
26
33
|
return False
|
27
|
-
return
|
34
|
+
return super().__eq__(other)
|
28
35
|
|
29
36
|
def __repr__(self) -> str:
|
30
|
-
return f"{type(self).__name__}({self
|
37
|
+
return f"{type(self).__name__}({self[0]!r})"
|
31
38
|
|
32
39
|
|
33
40
|
class unmapped(BaseAnnotation[T]):
|
@@ -38,9 +45,9 @@ class unmapped(BaseAnnotation[T]):
|
|
38
45
|
operation instead of being split.
|
39
46
|
"""
|
40
47
|
|
41
|
-
def __getitem__(self, _) -> T:
|
48
|
+
def __getitem__(self, _: object) -> T: # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
|
42
49
|
# Internally, this acts as an infinite array where all items are the same value
|
43
|
-
return
|
50
|
+
return super().__getitem__(0)
|
44
51
|
|
45
52
|
|
46
53
|
class allow_failure(BaseAnnotation[T]):
|
@@ -87,14 +94,14 @@ class quote(BaseAnnotation[T]):
|
|
87
94
|
|
88
95
|
|
89
96
|
# Backwards compatibility stub for `Quote` class
|
90
|
-
class Quote(quote):
|
91
|
-
def
|
97
|
+
class Quote(quote[T]):
|
98
|
+
def __new__(cls, expr: T) -> Self:
|
92
99
|
warnings.warn(
|
93
100
|
"Use of `Quote` is deprecated. Use `quote` instead.",
|
94
101
|
DeprecationWarning,
|
95
102
|
stacklevel=2,
|
96
103
|
)
|
97
|
-
super().
|
104
|
+
return super().__new__(cls, expr)
|
98
105
|
|
99
106
|
|
100
107
|
class NotSet:
|