prefect-client 3.4.5.dev5__py3-none-any.whl → 3.4.6.dev2__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 CHANGED
@@ -1,5 +1,5 @@
1
1
  # Generated by versioningit
2
- __version__ = "3.4.5.dev5"
3
- __build_date__ = "2025-06-06 08:09:21.189765+00:00"
4
- __git_commit__ = "6fd843adf3aa002e8907b0ba1b96be0af8259b2d"
2
+ __version__ = "3.4.6.dev2"
3
+ __build_date__ = "2025-06-11 08:09:26.576114+00:00"
4
+ __git_commit__ = "4e82c8d6b8d529a8d8804a58781a68023465c7d4"
5
5
  __dirty__ = False
prefect/assets/core.py CHANGED
@@ -47,6 +47,9 @@ class Asset(PrefectBaseModel):
47
47
  def __repr__(self) -> str:
48
48
  return f"Asset(key={self.key!r})"
49
49
 
50
+ def __hash__(self) -> int:
51
+ return hash(self.key)
52
+
50
53
  def add_metadata(self, metadata: dict[str, Any]) -> None:
51
54
  from prefect.context import AssetContext
52
55
 
@@ -59,7 +62,7 @@ class Asset(PrefectBaseModel):
59
62
  asset_ctx.add_asset_metadata(self.key, metadata)
60
63
 
61
64
 
62
- def add_asset_metadata(asset_key: str, metadata: dict[str, Any]) -> None:
65
+ def add_asset_metadata(asset: str | Asset, metadata: dict[str, Any]) -> None:
63
66
  from prefect.context import AssetContext
64
67
 
65
68
  asset_ctx = AssetContext.get()
@@ -68,4 +71,5 @@ def add_asset_metadata(asset_key: str, metadata: dict[str, Any]) -> None:
68
71
  "Unable to call `add_asset_metadata` when not inside of an AssetContext"
69
72
  )
70
73
 
74
+ asset_key = asset if isinstance(asset, str) else asset.key
71
75
  asset_ctx.add_asset_metadata(asset_key, metadata)
prefect/context.py CHANGED
@@ -400,7 +400,7 @@ class EngineContext(RunContext):
400
400
 
401
401
  # Tracking information needed to track asset linage between
402
402
  # tasks and materialization
403
- task_run_assets: dict[UUID, list[Asset]] = Field(default_factory=dict)
403
+ task_run_assets: dict[UUID, set[Asset]] = Field(default_factory=dict)
404
404
 
405
405
  # Events worker to emit events
406
406
  events: Optional[EventsWorker] = None
@@ -486,12 +486,13 @@ class AssetContext(ContextModel):
486
486
  materialization_metadata: Metadata for materialized assets
487
487
  """
488
488
 
489
- direct_asset_dependencies: list[Asset] = Field(default_factory=list)
490
- downstream_assets: list[Asset] = Field(default_factory=list)
491
- upstream_assets: list[Asset] = Field(default_factory=list)
489
+ direct_asset_dependencies: set[Asset] = Field(default_factory=set)
490
+ downstream_assets: set[Asset] = Field(default_factory=set)
491
+ upstream_assets: set[Asset] = Field(default_factory=set)
492
492
  materialized_by: Optional[str] = None
493
493
  task_run_id: Optional[UUID] = None
494
494
  materialization_metadata: dict[str, dict[str, Any]] = Field(default_factory=dict)
495
+ copy_to_child_ctx: bool = False
495
496
 
496
497
  __var__: ClassVar[ContextVar[Self]] = ContextVar("asset_context")
497
498
 
@@ -501,6 +502,7 @@ class AssetContext(ContextModel):
501
502
  task: "Task[Any, Any]",
502
503
  task_run_id: UUID,
503
504
  task_inputs: Optional[dict[str, set[Any]]] = None,
505
+ copy_to_child_ctx: bool = False,
504
506
  ) -> "AssetContext":
505
507
  """
506
508
  Create an AssetContext from a task and its resolved inputs.
@@ -509,6 +511,7 @@ class AssetContext(ContextModel):
509
511
  task: The task instance
510
512
  task_run_id: The task run ID
511
513
  task_inputs: The resolved task inputs (TaskRunResult objects)
514
+ copy_to_child_ctx: Whether this context should be copied on a child AssetContext
512
515
 
513
516
  Returns:
514
517
  Configured AssetContext
@@ -516,29 +519,35 @@ class AssetContext(ContextModel):
516
519
  from prefect.client.schemas import TaskRunResult
517
520
  from prefect.tasks import MaterializingTask
518
521
 
519
- upstream_assets: list[Asset] = []
522
+ upstream_assets: set[Asset] = set()
520
523
 
521
- # Get upstream assets from engine context instead of TaskRunResult.assets
522
524
  flow_ctx = FlowRunContext.get()
523
525
  if task_inputs and flow_ctx:
524
- for inputs in task_inputs.values():
526
+ for name, inputs in task_inputs.items():
527
+ # Parent task runs are not dependencies
528
+ # that we want to track
529
+ if name == "__parents__":
530
+ continue
531
+
525
532
  for task_input in inputs:
526
533
  if isinstance(task_input, TaskRunResult):
527
- # Look up assets in the engine context
528
534
  task_assets = flow_ctx.task_run_assets.get(task_input.id)
529
535
  if task_assets:
530
- upstream_assets.extend(task_assets)
536
+ upstream_assets.update(task_assets)
531
537
 
532
538
  ctx = cls(
533
- direct_asset_dependencies=task.asset_deps[:] if task.asset_deps else [],
534
- downstream_assets=task.assets[:]
539
+ direct_asset_dependencies=set(task.asset_deps)
540
+ if task.asset_deps
541
+ else set(),
542
+ downstream_assets=set(task.assets)
535
543
  if isinstance(task, MaterializingTask) and task.assets
536
- else [],
544
+ else set(),
537
545
  upstream_assets=upstream_assets,
538
546
  materialized_by=task.materialized_by
539
547
  if isinstance(task, MaterializingTask)
540
548
  else None,
541
549
  task_run_id=task_run_id,
550
+ copy_to_child_ctx=copy_to_child_ctx,
542
551
  )
543
552
  ctx.update_tracked_assets()
544
553
 
@@ -551,7 +560,15 @@ class AssetContext(ContextModel):
551
560
  Args:
552
561
  asset_key: The asset key
553
562
  metadata: Metadata dictionary to add
563
+
564
+ Raises:
565
+ ValueError: If asset_key is not in downstream_assets
554
566
  """
567
+ downstream_keys = {asset.key for asset in self.downstream_assets}
568
+ if asset_key not in downstream_keys:
569
+ raise ValueError(
570
+ "Can only add metadata to assets that are arguments to @materialize"
571
+ )
555
572
 
556
573
  existing = self.materialization_metadata.get(asset_key, {})
557
574
  self.materialization_metadata[asset_key] = existing | metadata
@@ -596,46 +613,46 @@ class AssetContext(ContextModel):
596
613
 
597
614
  def emit_events(self, state: State) -> None:
598
615
  """
599
- Emit asset reference and materialization events based on task completion.
616
+ Emit asset events
600
617
  """
601
618
 
602
619
  from prefect.events import emit_event
603
620
 
604
621
  if state.name == "Cached":
605
622
  return
606
- if state.is_failed():
623
+ elif state.is_failed():
607
624
  event_status = "failed"
608
625
  elif state.is_completed():
609
626
  event_status = "succeeded"
610
627
  else:
611
628
  return
612
629
 
613
- asset_deps_related: list[Asset] = []
630
+ # If we have no downstream assets, this not a materialization
631
+ if not self.downstream_assets:
632
+ return
614
633
 
615
- # Emit reference events for direct asset dependencies
616
- for asset in self.direct_asset_dependencies:
634
+ # Emit reference events for all upstream assets (direct + inherited)
635
+ all_upstream_assets = self.upstream_assets | self.direct_asset_dependencies
636
+ for asset in all_upstream_assets:
617
637
  emit_event(
618
- event=f"prefect.asset.reference.{event_status}",
638
+ event="prefect.asset.referenced",
619
639
  resource=self.asset_as_resource(asset),
620
640
  related=[],
621
641
  )
622
- asset_deps_related.append(self.asset_as_related(asset))
623
642
 
624
643
  # Emit materialization events for downstream assets
625
- if self.downstream_assets:
626
- upstream_related = [self.asset_as_related(a) for a in self.upstream_assets]
627
- all_related = upstream_related + asset_deps_related
628
-
629
- if self.materialized_by:
630
- all_related.append(self.related_materialized_by(self.materialized_by))
631
-
632
- for asset in self.downstream_assets:
633
- emit_event(
634
- event=f"prefect.asset.materialization.{event_status}",
635
- resource=self.asset_as_resource(asset),
636
- related=all_related,
637
- payload=self.materialization_metadata.get(asset.key),
638
- )
644
+ upstream_related = [self.asset_as_related(a) for a in all_upstream_assets]
645
+
646
+ if self.materialized_by:
647
+ upstream_related.append(self.related_materialized_by(self.materialized_by))
648
+
649
+ for asset in self.downstream_assets:
650
+ emit_event(
651
+ event=f"prefect.asset.materialization.{event_status}",
652
+ resource=self.asset_as_resource(asset),
653
+ related=upstream_related,
654
+ payload=self.materialization_metadata.get(asset.key),
655
+ )
639
656
 
640
657
  def update_tracked_assets(self) -> None:
641
658
  """
@@ -649,11 +666,11 @@ class AssetContext(ContextModel):
649
666
 
650
667
  if self.downstream_assets:
651
668
  # MaterializingTask: propagate the downstream assets (what we create)
652
- assets_for_downstream = self.downstream_assets[:]
669
+ assets_for_downstream = set(self.downstream_assets)
653
670
  else:
654
671
  # Regular task: propagate upstream assets + direct dependencies
655
- assets_for_downstream = (
656
- list(self.upstream_assets) + self.direct_asset_dependencies
672
+ assets_for_downstream = set(
673
+ self.upstream_assets | self.direct_asset_dependencies
657
674
  )
658
675
 
659
676
  flow_run_context.task_run_assets[self.task_run_id] = assets_for_downstream
@@ -661,6 +678,9 @@ class AssetContext(ContextModel):
661
678
  def serialize(self: Self, include_secrets: bool = True) -> dict[str, Any]:
662
679
  """Serialize the AssetContext for distributed execution."""
663
680
  return self.model_dump(
681
+ # use json serialization so fields that are
682
+ # sets of pydantic models are serialized
683
+ mode="json",
664
684
  exclude_unset=True,
665
685
  serialize_as_any=True,
666
686
  context={"include_secrets": include_secrets},
prefect/events/clients.py CHANGED
@@ -628,7 +628,7 @@ class PrefectEventSubscriber:
628
628
  try:
629
629
  await self._reconnect()
630
630
  finally:
631
- EVENT_WEBSOCKET_CONNECTIONS.labels(self.client_name, "out", "initial")
631
+ EVENT_WEBSOCKET_CONNECTIONS.labels(self.client_name, "out", "initial").inc()
632
632
  return self
633
633
 
634
634
  async def _reconnect(self) -> None:
@@ -709,7 +709,7 @@ class PrefectEventSubscriber:
709
709
  finally:
710
710
  EVENT_WEBSOCKET_CONNECTIONS.labels(
711
711
  self.client_name, "out", "reconnect"
712
- )
712
+ ).inc()
713
713
  assert self._websocket
714
714
 
715
715
  while True:
@@ -1,17 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
- import inspect
4
3
  import warnings
5
4
  from pathlib import Path
6
5
  from typing import (
7
6
  Annotated,
8
7
  Any,
9
8
  ClassVar,
10
- Dict,
11
9
  Iterable,
12
10
  Iterator,
13
11
  Optional,
14
- Union,
15
12
  )
16
13
 
17
14
  import toml
@@ -20,16 +17,15 @@ from pydantic import (
20
17
  BeforeValidator,
21
18
  ConfigDict,
22
19
  Field,
23
- TypeAdapter,
24
20
  ValidationError,
25
21
  )
26
- from pydantic_settings import BaseSettings
27
22
 
28
23
  from prefect.exceptions import ProfileSettingsValidationError
29
24
  from prefect.settings.constants import DEFAULT_PROFILES_PATH
30
25
  from prefect.settings.context import get_current_settings
31
26
  from prefect.settings.legacy import Setting, _get_settings_fields
32
27
  from prefect.settings.models.root import Settings
28
+ from prefect.utilities.collections import set_in_dict
33
29
 
34
30
 
35
31
  def _cast_settings(
@@ -69,7 +65,7 @@ class Profile(BaseModel):
69
65
  )
70
66
  source: Optional[Path] = None
71
67
 
72
- def to_environment_variables(self) -> Dict[str, str]:
68
+ def to_environment_variables(self) -> dict[str, str]:
73
69
  """Convert the profile settings to a dictionary of environment variables."""
74
70
  return {
75
71
  setting.name: str(value)
@@ -78,23 +74,40 @@ class Profile(BaseModel):
78
74
  }
79
75
 
80
76
  def validate_settings(self) -> None:
81
- errors: list[tuple[Setting, ValidationError]] = []
77
+ """
78
+ Validate all settings in this profile by creating a partial Settings object
79
+ with the nested structure properly constructed using accessor paths.
80
+ """
81
+ if not self.settings:
82
+ return
83
+
84
+ nested_settings: dict[str, Any] = {}
85
+
82
86
  for setting, value in self.settings.items():
83
- try:
84
- model_fields = Settings.model_fields
85
- annotation = None
86
- for section in setting.accessor.split("."):
87
- annotation = model_fields[section].annotation
88
- if inspect.isclass(annotation) and issubclass(
89
- annotation, BaseSettings
90
- ):
91
- model_fields = annotation.model_fields
92
-
93
- TypeAdapter(annotation).validate_python(value)
94
- except ValidationError as e:
95
- errors.append((setting, e))
96
- if errors:
97
- raise ProfileSettingsValidationError(errors)
87
+ set_in_dict(nested_settings, setting.accessor, value)
88
+
89
+ try:
90
+ Settings.model_validate(nested_settings)
91
+ except ValidationError as e:
92
+ errors: list[tuple[Setting, ValidationError]] = []
93
+
94
+ for error in e.errors():
95
+ error_path = ".".join(str(loc) for loc in error["loc"])
96
+
97
+ for setting in self.settings.keys():
98
+ if setting.accessor == error_path:
99
+ errors.append(
100
+ (
101
+ setting,
102
+ ValidationError.from_exception_data(
103
+ "ValidationError", [error]
104
+ ),
105
+ )
106
+ )
107
+ break
108
+
109
+ if errors:
110
+ raise ProfileSettingsValidationError(errors)
98
111
 
99
112
 
100
113
  class ProfilesCollection:
@@ -106,9 +119,7 @@ class ProfilesCollection:
106
119
  The collection may store the name of the active profile.
107
120
  """
108
121
 
109
- def __init__(
110
- self, profiles: Iterable[Profile], active: Optional[str] = None
111
- ) -> None:
122
+ def __init__(self, profiles: Iterable[Profile], active: str | None = None) -> None:
112
123
  self.profiles_by_name: dict[str, Profile] = {
113
124
  profile.name: profile for profile in profiles
114
125
  }
@@ -122,7 +133,7 @@ class ProfilesCollection:
122
133
  return set(self.profiles_by_name.keys())
123
134
 
124
135
  @property
125
- def active_profile(self) -> Optional[Profile]:
136
+ def active_profile(self) -> Profile | None:
126
137
  """
127
138
  Retrieve the active profile in this collection.
128
139
  """
@@ -130,7 +141,7 @@ class ProfilesCollection:
130
141
  return None
131
142
  return self[self.active_name]
132
143
 
133
- def set_active(self, name: Optional[str], check: bool = True) -> None:
144
+ def set_active(self, name: str | None, check: bool = True) -> None:
134
145
  """
135
146
  Set the active profile name in the collection.
136
147
 
@@ -145,7 +156,7 @@ class ProfilesCollection:
145
156
  self,
146
157
  name: str,
147
158
  settings: dict[Setting, Any],
148
- source: Optional[Path] = None,
159
+ source: Path | None = None,
149
160
  ) -> Profile:
150
161
  """
151
162
  Add a profile to the collection or update the existing on if the name is already
@@ -201,7 +212,7 @@ class ProfilesCollection:
201
212
  """
202
213
  self.profiles_by_name.pop(name)
203
214
 
204
- def without_profile_source(self, path: Optional[Path]) -> "ProfilesCollection":
215
+ def without_profile_source(self, path: Path | None) -> "ProfilesCollection":
205
216
  """
206
217
  Remove profiles that were loaded from a given path.
207
218
 
@@ -367,7 +378,7 @@ def load_profile(name: str) -> Profile:
367
378
 
368
379
 
369
380
  def update_current_profile(
370
- settings: Dict[Union[str, Setting], Any],
381
+ settings: dict[str | Setting, Any],
371
382
  ) -> Profile:
372
383
  """
373
384
  Update the persisted data for the profile currently in-use.
prefect/task_engine.py CHANGED
@@ -454,8 +454,6 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
454
454
  result = state.data
455
455
 
456
456
  link_state_to_result(new_state, result)
457
- if asset_context := AssetContext.get():
458
- asset_context.emit_events(new_state)
459
457
 
460
458
  # emit a state change event
461
459
  self._last_event = emit_task_run_state_change_event(
@@ -641,15 +639,6 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
641
639
  else:
642
640
  persist_result = should_persist_result()
643
641
 
644
- asset_context = AssetContext.get()
645
- if not asset_context:
646
- asset_context = AssetContext.from_task_and_inputs(
647
- task=self.task,
648
- task_run_id=self.task_run.id,
649
- task_inputs=self.task_run.task_inputs,
650
- )
651
- stack.enter_context(asset_context)
652
-
653
642
  stack.enter_context(
654
643
  TaskRunContext(
655
644
  task=self.task,
@@ -672,6 +661,24 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
672
661
 
673
662
  yield
674
663
 
664
+ @contextmanager
665
+ def asset_context(self):
666
+ parent_asset_ctx = AssetContext.get()
667
+
668
+ if parent_asset_ctx and parent_asset_ctx.copy_to_child_ctx:
669
+ asset_ctx = parent_asset_ctx.model_copy()
670
+ asset_ctx.copy_to_child_ctx = False
671
+ else:
672
+ asset_ctx = AssetContext.from_task_and_inputs(
673
+ self.task, self.task_run.id, self.task_run.task_inputs
674
+ )
675
+
676
+ with asset_ctx as ctx:
677
+ try:
678
+ yield
679
+ finally:
680
+ ctx.emit_events(self.state)
681
+
675
682
  @contextmanager
676
683
  def initialize_run(
677
684
  self,
@@ -1032,8 +1039,6 @@ class AsyncTaskRunEngine(BaseTaskRunEngine[P, R]):
1032
1039
  result = new_state.data
1033
1040
 
1034
1041
  link_state_to_result(new_state, result)
1035
- if asset_context := AssetContext.get():
1036
- asset_context.emit_events(new_state)
1037
1042
 
1038
1043
  # emit a state change event
1039
1044
  self._last_event = emit_task_run_state_change_event(
@@ -1219,15 +1224,6 @@ class AsyncTaskRunEngine(BaseTaskRunEngine[P, R]):
1219
1224
  else:
1220
1225
  persist_result = should_persist_result()
1221
1226
 
1222
- asset_context = AssetContext.get()
1223
- if not asset_context:
1224
- asset_context = AssetContext.from_task_and_inputs(
1225
- task=self.task,
1226
- task_run_id=self.task_run.id,
1227
- task_inputs=self.task_run.task_inputs,
1228
- )
1229
- stack.enter_context(asset_context)
1230
-
1231
1227
  stack.enter_context(
1232
1228
  TaskRunContext(
1233
1229
  task=self.task,
@@ -1249,6 +1245,24 @@ class AsyncTaskRunEngine(BaseTaskRunEngine[P, R]):
1249
1245
 
1250
1246
  yield
1251
1247
 
1248
+ @asynccontextmanager
1249
+ async def asset_context(self):
1250
+ parent_asset_ctx = AssetContext.get()
1251
+
1252
+ if parent_asset_ctx and parent_asset_ctx.copy_to_child_ctx:
1253
+ asset_ctx = parent_asset_ctx.model_copy()
1254
+ asset_ctx.copy_to_child_ctx = False
1255
+ else:
1256
+ asset_ctx = AssetContext.from_task_and_inputs(
1257
+ self.task, self.task_run.id, self.task_run.task_inputs
1258
+ )
1259
+
1260
+ with asset_ctx as ctx:
1261
+ try:
1262
+ yield
1263
+ finally:
1264
+ ctx.emit_events(self.state)
1265
+
1252
1266
  @asynccontextmanager
1253
1267
  async def initialize_run(
1254
1268
  self,
@@ -1465,7 +1479,11 @@ def run_task_sync(
1465
1479
  with engine.start(task_run_id=task_run_id, dependencies=dependencies):
1466
1480
  while engine.is_running():
1467
1481
  run_coro_as_sync(engine.wait_until_ready())
1468
- with engine.run_context(), engine.transaction_context() as txn:
1482
+ with (
1483
+ engine.asset_context(),
1484
+ engine.run_context(),
1485
+ engine.transaction_context() as txn,
1486
+ ):
1469
1487
  engine.call_task_fn(txn)
1470
1488
 
1471
1489
  return engine.state if return_type == "state" else engine.result()
@@ -1492,7 +1510,11 @@ async def run_task_async(
1492
1510
  async with engine.start(task_run_id=task_run_id, dependencies=dependencies):
1493
1511
  while engine.is_running():
1494
1512
  await engine.wait_until_ready()
1495
- async with engine.run_context(), engine.transaction_context() as txn:
1513
+ async with (
1514
+ engine.asset_context(),
1515
+ engine.run_context(),
1516
+ engine.transaction_context() as txn,
1517
+ ):
1496
1518
  await engine.call_task_fn(txn)
1497
1519
 
1498
1520
  return engine.state if return_type == "state" else await engine.result()
@@ -1522,7 +1544,11 @@ def run_generator_task_sync(
1522
1544
  with engine.start(task_run_id=task_run_id, dependencies=dependencies):
1523
1545
  while engine.is_running():
1524
1546
  run_coro_as_sync(engine.wait_until_ready())
1525
- with engine.run_context(), engine.transaction_context() as txn:
1547
+ with (
1548
+ engine.asset_context(),
1549
+ engine.run_context(),
1550
+ engine.transaction_context() as txn,
1551
+ ):
1526
1552
  # TODO: generators should default to commit_mode=OFF
1527
1553
  # because they are dynamic by definition
1528
1554
  # for now we just prevent this branch explicitly
@@ -1576,7 +1602,11 @@ async def run_generator_task_async(
1576
1602
  async with engine.start(task_run_id=task_run_id, dependencies=dependencies):
1577
1603
  while engine.is_running():
1578
1604
  await engine.wait_until_ready()
1579
- async with engine.run_context(), engine.transaction_context() as txn:
1605
+ async with (
1606
+ engine.asset_context(),
1607
+ engine.run_context(),
1608
+ engine.transaction_context() as txn,
1609
+ ):
1580
1610
  # TODO: generators should default to commit_mode=OFF
1581
1611
  # because they are dynamic by definition
1582
1612
  # for now we just prevent this branch explicitly
prefect/tasks.py CHANGED
@@ -100,6 +100,26 @@ OneOrManyFutureOrResult: TypeAlias = Union[
100
100
  ]
101
101
 
102
102
 
103
+ class TaskRunNameCallbackWithParameters(Protocol):
104
+ @classmethod
105
+ def is_callback_with_parameters(cls, callable: Callable[..., str]) -> TypeIs[Self]:
106
+ sig = inspect.signature(callable)
107
+ return "parameters" in sig.parameters
108
+
109
+ def __call__(self, parameters: dict[str, Any]) -> str: ...
110
+
111
+
112
+ StateHookCallable: TypeAlias = Callable[
113
+ ["Task[..., Any]", TaskRun, State], Union[Awaitable[None], None]
114
+ ]
115
+ RetryConditionCallable: TypeAlias = Callable[
116
+ ["Task[..., Any]", TaskRun, State], Union[Awaitable[bool], bool]
117
+ ]
118
+ TaskRunNameValueOrCallable: TypeAlias = Union[
119
+ Callable[[], str], TaskRunNameCallbackWithParameters, str
120
+ ]
121
+
122
+
103
123
  class TaskOptions(TypedDict, total=False):
104
124
  """
105
125
  A TypedDict representing all available task configuration options.
@@ -134,7 +154,7 @@ class TaskOptions(TypedDict, total=False):
134
154
  on_failure: Optional[list[StateHookCallable]]
135
155
  on_rollback: Optional[list[Callable[["Transaction"], None]]]
136
156
  on_commit: Optional[list[Callable[["Transaction"], None]]]
137
- retry_condition_fn: Optional[Callable[["Task[..., Any]", TaskRun, State], bool]]
157
+ retry_condition_fn: Optional[RetryConditionCallable]
138
158
  viz_return_value: Any
139
159
  asset_deps: Optional[list[Union[Asset, str]]]
140
160
 
@@ -272,23 +292,6 @@ def _generate_task_key(fn: Callable[..., Any]) -> str:
272
292
  return f"{qualname}-{code_hash}"
273
293
 
274
294
 
275
- class TaskRunNameCallbackWithParameters(Protocol):
276
- @classmethod
277
- def is_callback_with_parameters(cls, callable: Callable[..., str]) -> TypeIs[Self]:
278
- sig = inspect.signature(callable)
279
- return "parameters" in sig.parameters
280
-
281
- def __call__(self, parameters: dict[str, Any]) -> str: ...
282
-
283
-
284
- StateHookCallable: TypeAlias = Callable[
285
- ["Task[..., Any]", TaskRun, State], Union[Awaitable[None], None]
286
- ]
287
- TaskRunNameValueOrCallable: TypeAlias = Union[
288
- Callable[[], str], TaskRunNameCallbackWithParameters, str
289
- ]
290
-
291
-
292
295
  class Task(Generic[P, R]):
293
296
  """
294
297
  A Prefect task definition.
@@ -400,9 +403,7 @@ class Task(Generic[P, R]):
400
403
  on_failure: Optional[list[StateHookCallable]] = None,
401
404
  on_rollback: Optional[list[Callable[["Transaction"], None]]] = None,
402
405
  on_commit: Optional[list[Callable[["Transaction"], None]]] = None,
403
- retry_condition_fn: Optional[
404
- Callable[["Task[..., Any]", TaskRun, State], bool]
405
- ] = None,
406
+ retry_condition_fn: Optional[RetryConditionCallable] = None,
406
407
  viz_return_value: Optional[Any] = None,
407
408
  asset_deps: Optional[list[Union[str, Asset]]] = None,
408
409
  ):
@@ -672,9 +673,7 @@ class Task(Generic[P, R]):
672
673
  refresh_cache: Union[bool, type[NotSet]] = NotSet,
673
674
  on_completion: Optional[list[StateHookCallable]] = None,
674
675
  on_failure: Optional[list[StateHookCallable]] = None,
675
- retry_condition_fn: Optional[
676
- Callable[["Task[..., Any]", TaskRun, State], bool]
677
- ] = None,
676
+ retry_condition_fn: Optional[RetryConditionCallable] = None,
678
677
  viz_return_value: Optional[Any] = None,
679
678
  asset_deps: Optional[list[Union[str, Asset]]] = None,
680
679
  ) -> "Task[P, R]":
@@ -1728,7 +1727,7 @@ def task(
1728
1727
  refresh_cache: Optional[bool] = None,
1729
1728
  on_completion: Optional[list[StateHookCallable]] = None,
1730
1729
  on_failure: Optional[list[StateHookCallable]] = None,
1731
- retry_condition_fn: Literal[None] = None,
1730
+ retry_condition_fn: Optional[RetryConditionCallable] = None,
1732
1731
  viz_return_value: Any = None,
1733
1732
  asset_deps: Optional[list[Union[str, Asset]]] = None,
1734
1733
  ) -> Callable[[Callable[P, R]], Task[P, R]]: ...
@@ -1764,7 +1763,7 @@ def task(
1764
1763
  refresh_cache: Optional[bool] = None,
1765
1764
  on_completion: Optional[list[StateHookCallable]] = None,
1766
1765
  on_failure: Optional[list[StateHookCallable]] = None,
1767
- retry_condition_fn: Optional[Callable[[Task[P, R], TaskRun, State], bool]] = None,
1766
+ retry_condition_fn: Optional[RetryConditionCallable] = None,
1768
1767
  viz_return_value: Any = None,
1769
1768
  asset_deps: Optional[list[Union[str, Asset]]] = None,
1770
1769
  ) -> Callable[[Callable[P, R]], Task[P, R]]: ...
@@ -1801,7 +1800,7 @@ def task(
1801
1800
  refresh_cache: Optional[bool] = None,
1802
1801
  on_completion: Optional[list[StateHookCallable]] = None,
1803
1802
  on_failure: Optional[list[StateHookCallable]] = None,
1804
- retry_condition_fn: Optional[Callable[[Task[P, Any], TaskRun, State], bool]] = None,
1803
+ retry_condition_fn: Optional[RetryConditionCallable] = None,
1805
1804
  viz_return_value: Any = None,
1806
1805
  asset_deps: Optional[list[Union[str, Asset]]] = None,
1807
1806
  ) -> Callable[[Callable[P, R]], Task[P, R]]: ...
@@ -1835,7 +1834,7 @@ def task(
1835
1834
  refresh_cache: Optional[bool] = None,
1836
1835
  on_completion: Optional[list[StateHookCallable]] = None,
1837
1836
  on_failure: Optional[list[StateHookCallable]] = None,
1838
- retry_condition_fn: Optional[Callable[[Task[P, Any], TaskRun, State], bool]] = None,
1837
+ retry_condition_fn: Optional[RetryConditionCallable] = None,
1839
1838
  viz_return_value: Any = None,
1840
1839
  asset_deps: Optional[list[Union[str, Asset]]] = None,
1841
1840
  ):
@@ -223,10 +223,10 @@ def travel_to(dt: Any):
223
223
 
224
224
  def in_local_tz(dt: datetime.datetime) -> datetime.datetime:
225
225
  if sys.version_info >= (3, 13):
226
- from whenever import LocalDateTime, ZonedDateTime
226
+ from whenever import PlainDateTime, ZonedDateTime
227
227
 
228
228
  if dt.tzinfo is None:
229
- wdt = LocalDateTime.from_py_datetime(dt)
229
+ wdt = PlainDateTime.from_py_datetime(dt)
230
230
  else:
231
231
  if not isinstance(dt.tzinfo, ZoneInfo):
232
232
  if key := getattr(dt.tzinfo, "key", None):
@@ -286,8 +286,10 @@ def process_v1_params(
286
286
  docstrings: dict[str, str],
287
287
  aliases: dict[str, str],
288
288
  ) -> tuple[str, Any, Any]:
289
+ import pydantic.v1 as pydantic_v1
290
+
289
291
  # Pydantic model creation will fail if names collide with the BaseModel type
290
- if hasattr(pydantic.BaseModel, param.name):
292
+ if hasattr(pydantic_v1.BaseModel, param.name):
291
293
  name = param.name + "__"
292
294
  aliases[name] = param.name
293
295
  else:
@@ -296,10 +298,9 @@ def process_v1_params(
296
298
  type_ = Any if param.annotation is inspect.Parameter.empty else param.annotation
297
299
 
298
300
  with warnings.catch_warnings():
299
- warnings.filterwarnings(
300
- "ignore", category=pydantic.warnings.PydanticDeprecatedSince20
301
- )
302
- field: Any = pydantic.Field( # type: ignore # this uses the v1 signature, not v2
301
+ # Note: pydantic.v1 doesn't have the warnings module, so we can't suppress them
302
+ warnings.filterwarnings("ignore", category=DeprecationWarning)
303
+ field: Any = pydantic_v1.Field(
303
304
  default=... if param.default is param.empty else param.default,
304
305
  title=param.name,
305
306
  description=docstrings.get(param.name, None),
@@ -312,18 +313,19 @@ def process_v1_params(
312
313
  def create_v1_schema(
313
314
  name_: str, model_cfg: type[Any], model_fields: Optional[dict[str, Any]] = None
314
315
  ) -> dict[str, Any]:
316
+ import pydantic.v1 as pydantic_v1
317
+
315
318
  with warnings.catch_warnings():
316
- warnings.filterwarnings(
317
- "ignore", category=pydantic.warnings.PydanticDeprecatedSince20
318
- )
319
+ # Note: pydantic.v1 doesn't have the warnings module, so we can't suppress them
320
+ warnings.filterwarnings("ignore", category=DeprecationWarning)
319
321
 
320
322
  model_fields = model_fields or {}
321
- model: type[pydantic.BaseModel] = pydantic.create_model( # type: ignore # this uses the v1 signature, not v2
323
+ model: type[pydantic_v1.BaseModel] = pydantic_v1.create_model(
322
324
  name_,
323
- __config__=model_cfg, # type: ignore # this uses the v1 signature, not v2
325
+ __config__=model_cfg,
324
326
  **model_fields,
325
327
  )
326
- return model.schema(by_alias=True) # type: ignore # this uses the v1 signature, not v2
328
+ return model.schema(by_alias=True)
327
329
 
328
330
 
329
331
  def parameter_schema(fn: Callable[..., Any]) -> ParameterSchema:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prefect-client
3
- Version: 3.4.5.dev5
3
+ Version: 3.4.6.dev2
4
4
  Summary: Workflow orchestration and management.
5
5
  Project-URL: Changelog, https://github.com/PrefectHQ/prefect/releases
6
6
  Project-URL: Documentation, https://docs.prefect.io
@@ -44,7 +44,7 @@ Requires-Dist: packaging<25.1,>=21.3
44
44
  Requires-Dist: pathspec>=0.8.0
45
45
  Requires-Dist: pendulum<4,>=3.0.0; python_version < '3.13'
46
46
  Requires-Dist: prometheus-client>=0.20.0
47
- Requires-Dist: pydantic!=2.10.0,<3.0.0,>=2.9
47
+ Requires-Dist: pydantic!=2.10.0,!=2.11.0,!=2.11.1,!=2.11.2,!=2.11.3,!=2.11.4,<3.0.0,>=2.9
48
48
  Requires-Dist: pydantic-core<3.0.0,>=2.12.0
49
49
  Requires-Dist: pydantic-extra-types<3.0.0,>=2.8.2
50
50
  Requires-Dist: pydantic-settings!=2.9.0,<3.0.0,>2.2.1
@@ -1,7 +1,7 @@
1
1
  prefect/.prefectignore,sha256=awSprvKT0vI8a64mEOLrMxhxqcO-b0ERQeYpA2rNKVQ,390
2
2
  prefect/__init__.py,sha256=iCdcC5ZmeewikCdnPEP6YBAjPNV5dvfxpYCTpw30Hkw,3685
3
3
  prefect/__main__.py,sha256=WFjw3kaYJY6pOTA7WDOgqjsz8zUEUZHCcj3P5wyVa-g,66
4
- prefect/_build_info.py,sha256=7KSOuV8CcpDnGddMRTZieQOUtWaRj9WomC01JHoAT-U,185
4
+ prefect/_build_info.py,sha256=V3_F4_IM1pb_b1odsYUDPji-Zd3GoGOPgeSAHUGdDeE,185
5
5
  prefect/_result_records.py,sha256=S6QmsODkehGVSzbMm6ig022PYbI6gNKz671p_8kBYx4,7789
6
6
  prefect/_versioning.py,sha256=YqR5cxXrY4P6LM1Pmhd8iMo7v_G2KJpGNdsf4EvDFQ0,14132
7
7
  prefect/_waiters.py,sha256=Ia2ITaXdHzevtyWIgJoOg95lrEXQqNEOquHvw3T33UQ,9026
@@ -9,7 +9,7 @@ prefect/agent.py,sha256=dPvG1jDGD5HSH7aM2utwtk6RaJ9qg13XjkA0lAIgQmY,287
9
9
  prefect/artifacts.py,sha256=dMBUOAWnUamzjb5HSqwB5-GR2Qb-Gxee26XG5NDCUuw,22720
10
10
  prefect/automations.py,sha256=ZzPxn2tINdlXTQo805V4rIlbXuNWxd7cdb3gTJxZIeY,12567
11
11
  prefect/cache_policies.py,sha256=jH1aDW6vItTcsEytuTCrNYyjbq87IQPwdOgF0yxiUts,12749
12
- prefect/context.py,sha256=yNM-ATYOuYT80xbsK0NLEWJhv-dtScSepkNuUUZxkrM,31946
12
+ prefect/context.py,sha256=9IbfzBrhd6nqVicgcqeAqihRxKUYYZDg-psTByCYcZo,32589
13
13
  prefect/engine.py,sha256=uB5JN4l045i5JTlRQNT1x7MwlSiGQ5Bop2Q6jHHOgxY,3699
14
14
  prefect/exceptions.py,sha256=wZLQQMRB_DyiYkeEdIC5OKwbba5A94Dlnics-lrWI7A,11581
15
15
  prefect/filesystems.py,sha256=v5YqGB4uXf9Ew2VuB9VCSkawvYMMVvEtZf7w1VmAmr8,18036
@@ -24,11 +24,11 @@ prefect/results.py,sha256=Amm3TQu8U_oakSn__tCogIJ5DsTj0w_kLzuENWsxK6A,36824
24
24
  prefect/schedules.py,sha256=dhq4OhImRvcmtxF7UH1m8RbwYdHT5RQsp_FrxVXfODE,7289
25
25
  prefect/serializers.py,sha256=lU9A1rGEfAfhr8nTl3rf-K7ED78QNShXOrmRBhgNk3Y,9566
26
26
  prefect/states.py,sha256=rh7l1bnIYpTXdlXt5nnpz66y9KLjBWAJrN9Eo5RwgQs,26023
27
- prefect/task_engine.py,sha256=fOaEgusqNX0kqjOqG46nLUJc2prqVHvjFmqum0DTrHA,64956
27
+ prefect/task_engine.py,sha256=j7i_UiLvijV4Vut1Bw5-72kSlOqAPxqeS7-3cMVEBPA,65509
28
28
  prefect/task_runners.py,sha256=ptgE5wuXg_IVHM0j7d6l7ELAVg3SXSy4vggnoHRF8dA,17040
29
29
  prefect/task_runs.py,sha256=7LIzfo3fondCyEUpU05sYFN5IfpZigBDXrhG5yc-8t0,9039
30
30
  prefect/task_worker.py,sha256=RifZ3bOl6ppoYPiOAd4TQp2_GEw9eDQoW483rq1q52Q,20805
31
- prefect/tasks.py,sha256=SKEETA99SOeVlZW4Cw0TOruPNAb4iFIk8xYqkwU5IeI,78282
31
+ prefect/tasks.py,sha256=d4bJUiyNAtXqUotU7cHdk2IFV0OZQDdJ9jkkno8HdIs,78220
32
32
  prefect/transactions.py,sha256=uIoPNudzJzH6NrMJhrgr5lyh6JxOJQqT1GvrXt69yNw,26068
33
33
  prefect/variables.py,sha256=dCK3vX7TbkqXZhnNT_v7rcGh3ISRqoR6pJVLpoll3Js,8342
34
34
  prefect/_experimental/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -72,7 +72,7 @@ prefect/_internal/schemas/validators.py,sha256=h5LL6WuXf4rMmLHsYFflmJBlwqi5c7y0t
72
72
  prefect/_vendor/croniter/__init__.py,sha256=NUFzdbyPcTQhIOFtzmFM0nbClAvBbKh2mlnTBa6NfHU,523
73
73
  prefect/_vendor/croniter/croniter.py,sha256=eJ2HzStNAYV-vNiLOgDXl4sYWWHOsSA0dgwbkQoguhY,53009
74
74
  prefect/assets/__init__.py,sha256=-BAzycfydjD0eKRdpTiGXKxU66-yZX7CUh3Hot__PY4,203
75
- prefect/assets/core.py,sha256=STeN02bYU1DkcmLn1FfFyqrwfUqW2PGGdiAcj86vjP4,2050
75
+ prefect/assets/core.py,sha256=9iGsGqZ74UdCwZcLqUogPgVscBROIVeczv-TpB9fnYA,2179
76
76
  prefect/assets/materialize.py,sha256=GcHn1HEbCpExka0IOOz2b_2ZsJFROIo5y7DCP5GjpI8,1143
77
77
  prefect/blocks/__init__.py,sha256=D0hB72qMfgqnBB2EMZRxUxlX9yLfkab5zDChOwJZmkY,220
78
78
  prefect/blocks/abstract.py,sha256=mpOAWopSR_RrzdxeurBTXVSKisP8ne-k8LYos-tp7go,17021
@@ -153,7 +153,7 @@ prefect/docker/__init__.py,sha256=z6wdc6UFfiBG2jb9Jk64uCWVM04JKVWeVyDWwuuon8M,52
153
153
  prefect/docker/docker_image.py,sha256=bR_pEq5-FDxlwTj8CP_7nwZ_MiGK6KxIi8v7DRjy1Kg,3138
154
154
  prefect/events/__init__.py,sha256=GtKl2bE--pJduTxelH2xy7SadlLJmmis8WR1EYixhuA,2094
155
155
  prefect/events/actions.py,sha256=A7jS8bo4zWGnrt3QfSoQs0uYC1xfKXio3IfU0XtTb5s,9129
156
- prefect/events/clients.py,sha256=e3A6cKxi-fG2TkFedaRuC472hIM3VgaVxI6mcPP41kY,27613
156
+ prefect/events/clients.py,sha256=r_C3ZevVYUzIW53CpmFbEtR2DwhYeYB4budtB3GaYl0,27625
157
157
  prefect/events/filters.py,sha256=tnAbA4Z0Npem8Jbin-qqe38K_4a-4YdpU-Oc4u8Y95Q,8697
158
158
  prefect/events/related.py,sha256=CTeexYUmmA93V4gsR33GIFmw-SS-X_ouOpRg-oeq-BU,6672
159
159
  prefect/events/utilities.py,sha256=ww34bTMENCNwcp6RhhgzG0KgXOvKGe0MKmBdSJ8NpZY,3043
@@ -244,7 +244,7 @@ prefect/settings/base.py,sha256=VtBSwBLowLvtBVDq3ZY5oKAwosMqsDMt2gcXLAiFf5k,9682
244
244
  prefect/settings/constants.py,sha256=5NjVLG1Km9J9I-a6wrq-qmi_dTkPdwEk3IrY9bSxWvw,281
245
245
  prefect/settings/context.py,sha256=yKxnaDJHX8e2jmAVtw1RF9o7X4V3AOcz61sVeQyPX2c,2195
246
246
  prefect/settings/legacy.py,sha256=KG00GwaURl1zbwfCKAjwNRdJjB2UdTyo80gYF7U60jk,5693
247
- prefect/settings/profiles.py,sha256=it5xBTL3Y8cWoMXYbtUupdpl4lk-QFCfu0_taa5yf-A,12407
247
+ prefect/settings/profiles.py,sha256=Mk-fcfDUuJx5zIpp87Ar8d9jLFTgCOM83vEJWgmECBc,12795
248
248
  prefect/settings/profiles.toml,sha256=kTvqDNMzjH3fsm5OEI-NKY4dMmipor5EvQXRB6rPEjY,522
249
249
  prefect/settings/sources.py,sha256=x-yJT9aENh32wGVxe9WZg6KLLCZOZOMV0h5dDHuR6FA,13545
250
250
  prefect/settings/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -282,7 +282,7 @@ prefect/telemetry/processors.py,sha256=jw6j6LviOVxw3IBJe7cSjsxFk0zzY43jUmy6C9pcf
282
282
  prefect/telemetry/run_telemetry.py,sha256=_FbjiPqPemu4xvZuI2YBPwXeRJ2BcKRJ6qgO4UMzKKE,8571
283
283
  prefect/telemetry/services.py,sha256=DxgNNDTeWNtHBtioX8cjua4IrCbTiJJdYecx-gugg-w,2358
284
284
  prefect/types/__init__.py,sha256=iJzZLnK1qQuZUExF4_4xRLr0TMgb3uKfR5HQ1cjQ_0w,6066
285
- prefect/types/_datetime.py,sha256=ZE-4YK5XJuyxnp5pqldZwtIjkxCpxDGnCSfZiTl7-TU,7566
285
+ prefect/types/_datetime.py,sha256=_N3eAMhYlwSEubMQlfeTGxLJHn2jRFPrNPxkod21B_s,7566
286
286
  prefect/types/entrypoint.py,sha256=2FF03-wLPgtnqR_bKJDB2BsXXINPdu8ptY9ZYEZnXg8,328
287
287
  prefect/types/names.py,sha256=dGXNrP9nibQTm4hOBOpaQebKm3Avf3OGM5MH4M5BUKc,4013
288
288
  prefect/utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -292,7 +292,7 @@ prefect/utilities/_engine.py,sha256=9GW4X1lyAbmPwCuXXIubVJ7Z0DMT3dykkEUtp9tm5hI,
292
292
  prefect/utilities/_git.py,sha256=bPYWQdr9xvH0BqxR1ll1RkaSb3x0vhwylhYD5EilkKU,863
293
293
  prefect/utilities/annotations.py,sha256=0Elqgq6LR7pQqezNqT5wb6U_0e2pDO_zx6VseVL6kL8,4396
294
294
  prefect/utilities/asyncutils.py,sha256=xcfeNym2j3WH4gKXznON2hI1PpUTcwr_BGc16IQS3C4,19789
295
- prefect/utilities/callables.py,sha256=IaL-RBlrxmP7_zwwLNb2-F_Ht3o4KXUr6Pi2eVCH4rk,25973
295
+ prefect/utilities/callables.py,sha256=HcXA3_Stb8CBtp074SuFKuMy-ge2KW89X5towbzGjaY,25925
296
296
  prefect/utilities/collections.py,sha256=c3nPLPWqIZQQdNuHs_nrbQJwuhQSX4ivUl-h9LtzXto,23243
297
297
  prefect/utilities/compat.py,sha256=nnPA3lf2f4Y-l645tYFFNmj5NDPaYvjqa9pbGKZ3WKE,582
298
298
  prefect/utilities/context.py,sha256=23SDMgdt07SjmB1qShiykHfGgiv55NBzdbMXM3fE9CI,1447
@@ -325,7 +325,7 @@ prefect/workers/cloud.py,sha256=dPvG1jDGD5HSH7aM2utwtk6RaJ9qg13XjkA0lAIgQmY,287
325
325
  prefect/workers/process.py,sha256=Yi5D0U5AQ51wHT86GdwtImXSefe0gJf3LGq4r4z9zwM,11090
326
326
  prefect/workers/server.py,sha256=2pmVeJZiVbEK02SO6BEZaBIvHMsn6G8LzjW8BXyiTtk,1952
327
327
  prefect/workers/utilities.py,sha256=VfPfAlGtTuDj0-Kb8WlMgAuOfgXCdrGAnKMapPSBrwc,2483
328
- prefect_client-3.4.5.dev5.dist-info/METADATA,sha256=9z_e5eh2YeZ0eSZy3INLBLkvYZSC9D-qbfrnJJKht2g,7472
329
- prefect_client-3.4.5.dev5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
330
- prefect_client-3.4.5.dev5.dist-info/licenses/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
331
- prefect_client-3.4.5.dev5.dist-info/RECORD,,
328
+ prefect_client-3.4.6.dev2.dist-info/METADATA,sha256=g3s7EUAokVYJwBVf_kHkf5Qz8_6Q_bF-a5ooMROAONQ,7517
329
+ prefect_client-3.4.6.dev2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
330
+ prefect_client-3.4.6.dev2.dist-info/licenses/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
331
+ prefect_client-3.4.6.dev2.dist-info/RECORD,,