prefect-client 3.1.13__py3-none-any.whl → 3.1.14__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/_experimental/lineage.py +63 -0
- prefect/_version.py +3 -3
- prefect/cache_policies.py +50 -2
- prefect/client/orchestration/_automations/client.py +4 -0
- prefect/client/orchestration/_deployments/client.py +3 -3
- prefect/context.py +16 -6
- prefect/events/filters.py +34 -34
- prefect/flow_engine.py +155 -114
- prefect/flows.py +1 -1
- prefect/logging/configuration.py +2 -5
- prefect/logging/loggers.py +1 -2
- prefect/runner/runner.py +79 -58
- prefect/tasks.py +37 -1
- prefect/types/__init__.py +6 -5
- prefect/types/_datetime.py +19 -0
- {prefect_client-3.1.13.dist-info → prefect_client-3.1.14.dist-info}/METADATA +1 -1
- {prefect_client-3.1.13.dist-info → prefect_client-3.1.14.dist-info}/RECORD +20 -19
- {prefect_client-3.1.13.dist-info → prefect_client-3.1.14.dist-info}/LICENSE +0 -0
- {prefect_client-3.1.13.dist-info → prefect_client-3.1.14.dist-info}/WHEEL +0 -0
- {prefect_client-3.1.13.dist-info → prefect_client-3.1.14.dist-info}/top_level.txt +0 -0
prefect/_experimental/lineage.py
CHANGED
@@ -178,3 +178,66 @@ async def emit_result_write_event(
|
|
178
178
|
downstream_resources=downstream_resources,
|
179
179
|
direction_of_run_from_event="upstream",
|
180
180
|
)
|
181
|
+
|
182
|
+
|
183
|
+
async def emit_external_resource_lineage(
|
184
|
+
upstream_resources: Optional[UpstreamResources] = None,
|
185
|
+
downstream_resources: Optional[DownstreamResources] = None,
|
186
|
+
) -> None:
|
187
|
+
"""Emit lineage events connecting external resources to Prefect context resources.
|
188
|
+
|
189
|
+
This function emits events that place the current Prefect context resources
|
190
|
+
(like flow runs, task runs) as:
|
191
|
+
1. Downstream of any provided upstream external resources
|
192
|
+
2. Upstream of any provided downstream external resources
|
193
|
+
|
194
|
+
Args:
|
195
|
+
upstream_resources: Optional sequence of resources that are upstream of the
|
196
|
+
current Prefect context
|
197
|
+
downstream_resources: Optional sequence of resources that are downstream of
|
198
|
+
the current Prefect context
|
199
|
+
"""
|
200
|
+
from prefect.client.orchestration import get_client
|
201
|
+
|
202
|
+
if not get_current_settings().experiments.lineage_events_enabled:
|
203
|
+
return
|
204
|
+
|
205
|
+
upstream_resources = list(upstream_resources) if upstream_resources else []
|
206
|
+
downstream_resources = list(downstream_resources) if downstream_resources else []
|
207
|
+
|
208
|
+
# Get the current Prefect context resources (flow runs, task runs, etc.)
|
209
|
+
async with get_client() as client:
|
210
|
+
context_resources = await related_resources_from_run_context(client)
|
211
|
+
|
212
|
+
# Add lineage group label to all resources
|
213
|
+
for res in upstream_resources + downstream_resources:
|
214
|
+
if "prefect.resource.lineage-group" not in res:
|
215
|
+
res["prefect.resource.lineage-group"] = "global"
|
216
|
+
|
217
|
+
# For each context resource, emit an event showing it as downstream of upstream resources
|
218
|
+
if upstream_resources:
|
219
|
+
for context_resource in context_resources:
|
220
|
+
emit_kwargs: Dict[str, Any] = {
|
221
|
+
"event": "prefect.resource.consumed",
|
222
|
+
"resource": context_resource,
|
223
|
+
"related": upstream_resources,
|
224
|
+
}
|
225
|
+
emit_event(**emit_kwargs)
|
226
|
+
|
227
|
+
# For each downstream resource, emit an event showing it as downstream of context resources
|
228
|
+
for downstream_resource in downstream_resources:
|
229
|
+
emit_kwargs: Dict[str, Any] = {
|
230
|
+
"event": "prefect.resource.produced",
|
231
|
+
"resource": downstream_resource,
|
232
|
+
"related": context_resources,
|
233
|
+
}
|
234
|
+
emit_event(**emit_kwargs)
|
235
|
+
|
236
|
+
# For each downstream resource, emit an event showing it as downstream of upstream resources
|
237
|
+
if upstream_resources:
|
238
|
+
direct_emit_kwargs = {
|
239
|
+
"event": "prefect.resource.direct-lineage",
|
240
|
+
"resource": downstream_resource,
|
241
|
+
"related": upstream_resources,
|
242
|
+
}
|
243
|
+
emit_event(**direct_emit_kwargs)
|
prefect/_version.py
CHANGED
@@ -8,11 +8,11 @@ import json
|
|
8
8
|
|
9
9
|
version_json = '''
|
10
10
|
{
|
11
|
-
"date": "2025-01-
|
11
|
+
"date": "2025-01-23T13:22:04-0800",
|
12
12
|
"dirty": true,
|
13
13
|
"error": null,
|
14
|
-
"full-revisionid": "
|
15
|
-
"version": "3.1.
|
14
|
+
"full-revisionid": "5f1ebb57222bee1537b4d2c64b6dc9e3791ebafe",
|
15
|
+
"version": "3.1.14"
|
16
16
|
}
|
17
17
|
''' # END VERSION_JSON
|
18
18
|
|
prefect/cache_policies.py
CHANGED
@@ -80,10 +80,12 @@ class CachePolicy:
|
|
80
80
|
raise NotImplementedError
|
81
81
|
|
82
82
|
def __sub__(self, other: str) -> "CachePolicy":
|
83
|
+
"No-op for all policies except Inputs and Compound"
|
84
|
+
|
85
|
+
# for interface compatibility
|
83
86
|
if not isinstance(other, str): # type: ignore[reportUnnecessaryIsInstance]
|
84
87
|
raise TypeError("Can only subtract strings from key policies.")
|
85
|
-
|
86
|
-
return CompoundCachePolicy(policies=[self, new])
|
88
|
+
return self
|
87
89
|
|
88
90
|
def __add__(self, other: "CachePolicy") -> "CachePolicy":
|
89
91
|
# adding _None is a no-op
|
@@ -157,6 +159,23 @@ class CompoundCachePolicy(CachePolicy):
|
|
157
159
|
|
158
160
|
policies: list[CachePolicy] = field(default_factory=list)
|
159
161
|
|
162
|
+
def __post_init__(self) -> None:
|
163
|
+
# flatten any CompoundCachePolicies
|
164
|
+
self.policies = [
|
165
|
+
policy
|
166
|
+
for p in self.policies
|
167
|
+
for policy in (p.policies if isinstance(p, CompoundCachePolicy) else [p])
|
168
|
+
]
|
169
|
+
|
170
|
+
# deduplicate any Inputs policies
|
171
|
+
inputs_policies = [p for p in self.policies if isinstance(p, Inputs)]
|
172
|
+
self.policies = [p for p in self.policies if not isinstance(p, Inputs)]
|
173
|
+
if inputs_policies:
|
174
|
+
all_excludes: set[str] = set()
|
175
|
+
for inputs_policy in inputs_policies:
|
176
|
+
all_excludes.update(inputs_policy.exclude)
|
177
|
+
self.policies.append(Inputs(exclude=sorted(all_excludes)))
|
178
|
+
|
160
179
|
def compute_key(
|
161
180
|
self,
|
162
181
|
task_ctx: TaskRunContext,
|
@@ -178,6 +197,35 @@ class CompoundCachePolicy(CachePolicy):
|
|
178
197
|
return None
|
179
198
|
return hash_objects(*keys, raise_on_failure=True)
|
180
199
|
|
200
|
+
def __add__(self, other: "CachePolicy") -> "CachePolicy":
|
201
|
+
# Call the superclass add method to handle validation
|
202
|
+
super().__add__(other)
|
203
|
+
|
204
|
+
if isinstance(other, CompoundCachePolicy):
|
205
|
+
policies = [*self.policies, *other.policies]
|
206
|
+
else:
|
207
|
+
policies = [*self.policies, other]
|
208
|
+
|
209
|
+
return CompoundCachePolicy(
|
210
|
+
policies=policies,
|
211
|
+
key_storage=self.key_storage or other.key_storage,
|
212
|
+
isolation_level=self.isolation_level or other.isolation_level,
|
213
|
+
lock_manager=self.lock_manager or other.lock_manager,
|
214
|
+
)
|
215
|
+
|
216
|
+
def __sub__(self, other: str) -> "CachePolicy":
|
217
|
+
if not isinstance(other, str): # type: ignore[reportUnnecessaryIsInstance]
|
218
|
+
raise TypeError("Can only subtract strings from key policies.")
|
219
|
+
|
220
|
+
inputs_policies = [p for p in self.policies if isinstance(p, Inputs)]
|
221
|
+
|
222
|
+
if inputs_policies:
|
223
|
+
new = Inputs(exclude=[other])
|
224
|
+
return CompoundCachePolicy(policies=[*self.policies, new])
|
225
|
+
else:
|
226
|
+
# no dependency on inputs already
|
227
|
+
return self
|
228
|
+
|
181
229
|
|
182
230
|
@dataclass
|
183
231
|
class _None(CachePolicy):
|
@@ -45,6 +45,8 @@ class AutomationClient(BaseClient):
|
|
45
45
|
return Automation.model_validate_list(response.json())
|
46
46
|
|
47
47
|
def find_automation(self, id_or_name: "str | UUID") -> "Automation | None":
|
48
|
+
from uuid import UUID
|
49
|
+
|
48
50
|
if isinstance(id_or_name, str):
|
49
51
|
name = id_or_name
|
50
52
|
try:
|
@@ -202,6 +204,8 @@ class AutomationAsyncClient(BaseAsyncClient):
|
|
202
204
|
return Automation.model_validate_list(response.json())
|
203
205
|
|
204
206
|
async def find_automation(self, id_or_name: "str | UUID") -> "Automation | None":
|
207
|
+
from uuid import UUID
|
208
|
+
|
205
209
|
if isinstance(id_or_name, str):
|
206
210
|
name = id_or_name
|
207
211
|
try:
|
@@ -1031,8 +1031,8 @@ class DeploymentAsyncClient(BaseAsyncClient):
|
|
1031
1031
|
deployment_ids: list["UUID"],
|
1032
1032
|
scheduled_before: "datetime.datetime | None" = None,
|
1033
1033
|
limit: int | None = None,
|
1034
|
-
) -> list["
|
1035
|
-
from prefect.client.schemas.
|
1034
|
+
) -> list["FlowRun"]:
|
1035
|
+
from prefect.client.schemas.objects import FlowRun
|
1036
1036
|
|
1037
1037
|
body: dict[str, Any] = dict(deployment_ids=[str(id) for id in deployment_ids])
|
1038
1038
|
if scheduled_before:
|
@@ -1046,7 +1046,7 @@ class DeploymentAsyncClient(BaseAsyncClient):
|
|
1046
1046
|
json=body,
|
1047
1047
|
)
|
1048
1048
|
|
1049
|
-
return
|
1049
|
+
return FlowRun.model_validate_list(response.json())
|
1050
1050
|
|
1051
1051
|
async def create_flow_run_from_deployment(
|
1052
1052
|
self,
|
prefect/context.py
CHANGED
@@ -337,10 +337,15 @@ class EngineContext(RunContext):
|
|
337
337
|
flow: The flow instance associated with the run
|
338
338
|
flow_run: The API metadata for the flow run
|
339
339
|
task_runner: The task runner instance being used for the flow run
|
340
|
-
task_run_futures: A list of futures for task runs submitted within this flow run
|
341
|
-
task_run_states: A list of states for task runs created within this flow run
|
342
340
|
task_run_results: A mapping of result ids to task run states for this flow run
|
343
|
-
|
341
|
+
log_prints: Whether to log print statements from the flow run
|
342
|
+
parameters: The parameters passed to the flow run
|
343
|
+
detached: Flag indicating if context has been serialized and sent to remote infrastructure
|
344
|
+
result_store: The result store used to persist results
|
345
|
+
persist_result: Whether to persist the flow run result
|
346
|
+
task_run_dynamic_keys: Counter for task calls allowing unique keys
|
347
|
+
observed_flow_pauses: Counter for flow pauses
|
348
|
+
events: Events worker to emit events
|
344
349
|
"""
|
345
350
|
|
346
351
|
flow: Optional["Flow[Any, Any]"] = None
|
@@ -373,7 +378,7 @@ class EngineContext(RunContext):
|
|
373
378
|
__var__: ClassVar[ContextVar[Self]] = ContextVar("flow_run")
|
374
379
|
|
375
380
|
def serialize(self: Self, include_secrets: bool = True) -> dict[str, Any]:
|
376
|
-
|
381
|
+
serialized = self.model_dump(
|
377
382
|
include={
|
378
383
|
"flow_run",
|
379
384
|
"flow",
|
@@ -381,13 +386,18 @@ class EngineContext(RunContext):
|
|
381
386
|
"log_prints",
|
382
387
|
"start_time",
|
383
388
|
"input_keyset",
|
384
|
-
"result_store",
|
385
389
|
"persist_result",
|
386
390
|
},
|
387
391
|
exclude_unset=True,
|
388
|
-
serialize_as_any=True,
|
389
392
|
context={"include_secrets": include_secrets},
|
390
393
|
)
|
394
|
+
if self.result_store:
|
395
|
+
serialized["result_store"] = self.result_store.model_dump(
|
396
|
+
serialize_as_any=True,
|
397
|
+
exclude_unset=True,
|
398
|
+
context={"include_secrets": include_secrets},
|
399
|
+
)
|
400
|
+
return serialized
|
391
401
|
|
392
402
|
|
393
403
|
FlowRunContext = EngineContext # for backwards compatibility
|
prefect/events/filters.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
from typing import
|
1
|
+
from typing import Optional
|
2
2
|
from uuid import UUID
|
3
3
|
|
4
|
-
import pendulum
|
5
4
|
from pydantic import Field
|
6
5
|
|
7
6
|
from prefect._internal.schemas.bases import PrefectBaseModel
|
@@ -23,7 +22,7 @@ class AutomationFilterCreated(PrefectBaseModel):
|
|
23
22
|
class AutomationFilterName(PrefectBaseModel):
|
24
23
|
"""Filter by `Automation.created`."""
|
25
24
|
|
26
|
-
any_: Optional[
|
25
|
+
any_: Optional[list[str]] = Field(
|
27
26
|
default=None,
|
28
27
|
description="Only include automations with names that match any of these strings",
|
29
28
|
)
|
@@ -41,8 +40,8 @@ class AutomationFilter(PrefectBaseModel):
|
|
41
40
|
class EventDataFilter(PrefectBaseModel, extra="forbid"): # type: ignore[call-arg]
|
42
41
|
"""A base class for filtering event data."""
|
43
42
|
|
44
|
-
def get_filters(self) ->
|
45
|
-
filters:
|
43
|
+
def get_filters(self) -> list["EventDataFilter"]:
|
44
|
+
filters: list["EventDataFilter"] = [
|
46
45
|
filter
|
47
46
|
for filter in [getattr(self, name) for name in self.model_fields]
|
48
47
|
if isinstance(filter, EventDataFilter)
|
@@ -60,14 +59,11 @@ class EventDataFilter(PrefectBaseModel, extra="forbid"): # type: ignore[call-ar
|
|
60
59
|
|
61
60
|
class EventOccurredFilter(EventDataFilter):
|
62
61
|
since: DateTime = Field(
|
63
|
-
default_factory=lambda:
|
64
|
-
DateTime,
|
65
|
-
pendulum.now("UTC").start_of("day").subtract(days=180),
|
66
|
-
),
|
62
|
+
default_factory=lambda: DateTime.now("UTC").start_of("day").subtract(days=180),
|
67
63
|
description="Only include events after this time (inclusive)",
|
68
64
|
)
|
69
65
|
until: DateTime = Field(
|
70
|
-
default_factory=lambda:
|
66
|
+
default_factory=lambda: DateTime.now("UTC"),
|
71
67
|
description="Only include events prior to this time (inclusive)",
|
72
68
|
)
|
73
69
|
|
@@ -76,18 +72,18 @@ class EventOccurredFilter(EventDataFilter):
|
|
76
72
|
|
77
73
|
|
78
74
|
class EventNameFilter(EventDataFilter):
|
79
|
-
prefix: Optional[
|
75
|
+
prefix: Optional[list[str]] = Field(
|
80
76
|
default=None, description="Only include events matching one of these prefixes"
|
81
77
|
)
|
82
|
-
exclude_prefix: Optional[
|
78
|
+
exclude_prefix: Optional[list[str]] = Field(
|
83
79
|
default=None, description="Exclude events matching one of these prefixes"
|
84
80
|
)
|
85
81
|
|
86
|
-
name: Optional[
|
82
|
+
name: Optional[list[str]] = Field(
|
87
83
|
default=None,
|
88
84
|
description="Only include events matching one of these names exactly",
|
89
85
|
)
|
90
|
-
exclude_name: Optional[
|
86
|
+
exclude_name: Optional[list[str]] = Field(
|
91
87
|
default=None, description="Exclude events matching one of these names exactly"
|
92
88
|
)
|
93
89
|
|
@@ -112,20 +108,20 @@ class EventNameFilter(EventDataFilter):
|
|
112
108
|
|
113
109
|
|
114
110
|
class EventResourceFilter(EventDataFilter):
|
115
|
-
id: Optional[
|
116
|
-
None, description="Only include events for resources with these IDs"
|
111
|
+
id: Optional[list[str]] = Field(
|
112
|
+
default=None, description="Only include events for resources with these IDs"
|
117
113
|
)
|
118
|
-
id_prefix: Optional[
|
119
|
-
None,
|
114
|
+
id_prefix: Optional[list[str]] = Field(
|
115
|
+
default=None,
|
120
116
|
description=(
|
121
117
|
"Only include events for resources with IDs starting with these prefixes."
|
122
118
|
),
|
123
119
|
)
|
124
120
|
labels: Optional[ResourceSpecification] = Field(
|
125
|
-
None, description="Only include events for resources with these labels"
|
121
|
+
default=None, description="Only include events for resources with these labels"
|
126
122
|
)
|
127
123
|
distinct: bool = Field(
|
128
|
-
False,
|
124
|
+
default=False,
|
129
125
|
description="Only include events for distinct resources",
|
130
126
|
)
|
131
127
|
|
@@ -148,35 +144,39 @@ class EventResourceFilter(EventDataFilter):
|
|
148
144
|
|
149
145
|
|
150
146
|
class EventRelatedFilter(EventDataFilter):
|
151
|
-
id: Optional[
|
152
|
-
None,
|
147
|
+
id: Optional[list[str]] = Field(
|
148
|
+
default=None,
|
149
|
+
description="Only include events for related resources with these IDs",
|
153
150
|
)
|
154
|
-
role: Optional[
|
155
|
-
None,
|
151
|
+
role: Optional[list[str]] = Field(
|
152
|
+
default=None,
|
153
|
+
description="Only include events for related resources in these roles",
|
156
154
|
)
|
157
|
-
resources_in_roles: Optional[
|
158
|
-
None,
|
155
|
+
resources_in_roles: Optional[list[tuple[str, str]]] = Field(
|
156
|
+
default=None,
|
159
157
|
description=(
|
160
158
|
"Only include events with specific related resources in specific roles"
|
161
159
|
),
|
162
160
|
)
|
163
161
|
labels: Optional[ResourceSpecification] = Field(
|
164
|
-
None,
|
162
|
+
default=None,
|
163
|
+
description="Only include events for related resources with these labels",
|
165
164
|
)
|
166
165
|
|
167
166
|
|
168
167
|
class EventAnyResourceFilter(EventDataFilter):
|
169
|
-
id: Optional[
|
170
|
-
None, description="Only include events for resources with these IDs"
|
168
|
+
id: Optional[list[str]] = Field(
|
169
|
+
default=None, description="Only include events for resources with these IDs"
|
171
170
|
)
|
172
|
-
id_prefix: Optional[
|
173
|
-
None,
|
171
|
+
id_prefix: Optional[list[str]] = Field(
|
172
|
+
default=None,
|
174
173
|
description=(
|
175
174
|
"Only include events for resources with IDs starting with these prefixes"
|
176
175
|
),
|
177
176
|
)
|
178
177
|
labels: Optional[ResourceSpecification] = Field(
|
179
|
-
None,
|
178
|
+
default=None,
|
179
|
+
description="Only include events for related resources with these labels",
|
180
180
|
)
|
181
181
|
|
182
182
|
def includes(self, event: Event) -> bool:
|
@@ -202,8 +202,8 @@ class EventAnyResourceFilter(EventDataFilter):
|
|
202
202
|
|
203
203
|
|
204
204
|
class EventIDFilter(EventDataFilter):
|
205
|
-
id: Optional[
|
206
|
-
None, description="Only include events with one of these IDs"
|
205
|
+
id: Optional[list[UUID]] = Field(
|
206
|
+
default=None, description="Only include events with one of these IDs"
|
207
207
|
)
|
208
208
|
|
209
209
|
def includes(self, event: Event) -> bool:
|