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.
@@ -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-17T08:46:53-0800",
11
+ "date": "2025-01-23T13:22:04-0800",
12
12
  "dirty": true,
13
13
  "error": null,
14
- "full-revisionid": "16e85ce3c281778f5ab6487a73377eed63bcac8b",
15
- "version": "3.1.13"
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
- new = Inputs(exclude=[other])
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["FlowRunResponse"]:
1035
- from prefect.client.schemas.responses import FlowRunResponse
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 FlowRunResponse.model_validate_list(response.json())
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
- flow_run_states: A list of states for flow runs created within this flow run
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
- return self.model_dump(
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 List, Optional, Tuple, cast
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[List[str]] = Field(
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) -> List["EventDataFilter"]:
45
- filters: List["EventDataFilter"] = [
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: cast(
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: cast(DateTime, pendulum.now("UTC")),
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[List[str]] = Field(
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[List[str]] = Field(
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[List[str]] = Field(
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[List[str]] = Field(
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[List[str]] = Field(
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[List[str]] = Field(
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[List[str]] = Field(
152
- None, description="Only include events for related resources with these IDs"
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[List[str]] = Field(
155
- None, description="Only include events for related resources in these roles"
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[List[Tuple[str, str]]] = Field(
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, description="Only include events for related resources with these labels"
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[List[str]] = Field(
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[List[str]] = Field(
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, description="Only include events for related resources with these labels"
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[List[UUID]] = Field(
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: