prefect-client 3.1.5__py3-none-any.whl → 3.1.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. prefect/__init__.py +3 -0
  2. prefect/_internal/compatibility/migration.py +1 -1
  3. prefect/_internal/concurrency/api.py +52 -52
  4. prefect/_internal/concurrency/calls.py +59 -35
  5. prefect/_internal/concurrency/cancellation.py +34 -18
  6. prefect/_internal/concurrency/event_loop.py +7 -6
  7. prefect/_internal/concurrency/threads.py +41 -33
  8. prefect/_internal/concurrency/waiters.py +28 -21
  9. prefect/_internal/pydantic/v1_schema.py +2 -2
  10. prefect/_internal/pydantic/v2_schema.py +10 -9
  11. prefect/_internal/schemas/bases.py +9 -7
  12. prefect/_internal/schemas/validators.py +2 -1
  13. prefect/_version.py +3 -3
  14. prefect/automations.py +53 -47
  15. prefect/blocks/abstract.py +12 -10
  16. prefect/blocks/core.py +4 -2
  17. prefect/cache_policies.py +11 -11
  18. prefect/client/__init__.py +3 -1
  19. prefect/client/base.py +36 -37
  20. prefect/client/cloud.py +26 -19
  21. prefect/client/collections.py +2 -2
  22. prefect/client/orchestration.py +342 -273
  23. prefect/client/schemas/__init__.py +24 -0
  24. prefect/client/schemas/actions.py +123 -116
  25. prefect/client/schemas/objects.py +110 -81
  26. prefect/client/schemas/responses.py +18 -18
  27. prefect/client/schemas/schedules.py +136 -93
  28. prefect/client/subscriptions.py +28 -14
  29. prefect/client/utilities.py +32 -36
  30. prefect/concurrency/asyncio.py +6 -9
  31. prefect/concurrency/sync.py +35 -5
  32. prefect/context.py +39 -31
  33. prefect/deployments/flow_runs.py +3 -5
  34. prefect/docker/__init__.py +1 -1
  35. prefect/events/schemas/events.py +25 -20
  36. prefect/events/utilities.py +1 -2
  37. prefect/filesystems.py +3 -3
  38. prefect/flow_engine.py +61 -21
  39. prefect/flow_runs.py +3 -3
  40. prefect/flows.py +214 -170
  41. prefect/logging/configuration.py +1 -1
  42. prefect/logging/highlighters.py +1 -2
  43. prefect/logging/loggers.py +30 -20
  44. prefect/main.py +17 -24
  45. prefect/runner/runner.py +43 -21
  46. prefect/runner/server.py +30 -32
  47. prefect/runner/submit.py +3 -6
  48. prefect/runner/utils.py +6 -6
  49. prefect/runtime/flow_run.py +7 -0
  50. prefect/settings/constants.py +2 -2
  51. prefect/settings/legacy.py +1 -1
  52. prefect/settings/models/server/events.py +10 -0
  53. prefect/task_engine.py +72 -19
  54. prefect/task_runners.py +2 -2
  55. prefect/tasks.py +46 -33
  56. prefect/telemetry/bootstrap.py +15 -2
  57. prefect/telemetry/run_telemetry.py +107 -0
  58. prefect/transactions.py +14 -14
  59. prefect/types/__init__.py +1 -4
  60. prefect/utilities/_engine.py +96 -0
  61. prefect/utilities/annotations.py +25 -18
  62. prefect/utilities/asyncutils.py +126 -140
  63. prefect/utilities/callables.py +87 -78
  64. prefect/utilities/collections.py +278 -117
  65. prefect/utilities/compat.py +13 -21
  66. prefect/utilities/context.py +6 -5
  67. prefect/utilities/dispatch.py +23 -12
  68. prefect/utilities/dockerutils.py +33 -32
  69. prefect/utilities/engine.py +126 -239
  70. prefect/utilities/filesystem.py +18 -15
  71. prefect/utilities/hashing.py +10 -11
  72. prefect/utilities/importtools.py +40 -27
  73. prefect/utilities/math.py +9 -5
  74. prefect/utilities/names.py +3 -3
  75. prefect/utilities/processutils.py +121 -57
  76. prefect/utilities/pydantic.py +41 -36
  77. prefect/utilities/render_swagger.py +22 -12
  78. prefect/utilities/schema_tools/__init__.py +2 -1
  79. prefect/utilities/schema_tools/hydration.py +50 -43
  80. prefect/utilities/schema_tools/validation.py +52 -42
  81. prefect/utilities/services.py +13 -12
  82. prefect/utilities/templating.py +45 -45
  83. prefect/utilities/text.py +2 -1
  84. prefect/utilities/timeout.py +4 -4
  85. prefect/utilities/urls.py +9 -4
  86. prefect/utilities/visualization.py +46 -24
  87. prefect/variables.py +9 -8
  88. prefect/workers/base.py +15 -8
  89. {prefect_client-3.1.5.dist-info → prefect_client-3.1.6.dist-info}/METADATA +4 -2
  90. {prefect_client-3.1.5.dist-info → prefect_client-3.1.6.dist-info}/RECORD +93 -91
  91. {prefect_client-3.1.5.dist-info → prefect_client-3.1.6.dist-info}/LICENSE +0 -0
  92. {prefect_client-3.1.5.dist-info → prefect_client-3.1.6.dist-info}/WHEEL +0 -0
  93. {prefect_client-3.1.5.dist-info → prefect_client-3.1.6.dist-info}/top_level.txt +0 -0
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, logging.LoggerAdapter] = Field(
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 = _get_hook_name(hook)
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[List] = None,
383
- on_commit_hooks: Optional[List] = None,
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, logging.LoggerAdapter, None] = None,
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
- logger = logger or get_run_logger()
468
+ _logger: Union[logging.Logger, LoggingAdapter] = logger or get_run_logger()
469
469
  except MissingContextError:
470
- logger = get_logger("transactions")
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=logger,
479
+ logger=_logger,
480
480
  ) as txn:
481
481
  yield txn
prefect/types/__init__.py CHANGED
@@ -96,7 +96,7 @@ def cast_none_to_empty_dict(value: Any) -> dict[str, Any]:
96
96
 
97
97
 
98
98
  KeyValueLabels = Annotated[
99
- dict[str, Union[StrictBool, StrictInt, StrictFloat, str]],
99
+ Dict[str, Union[StrictBool, StrictInt, StrictFloat, str]],
100
100
  BeforeValidator(cast_none_to_empty_dict),
101
101
  ]
102
102
 
@@ -149,9 +149,6 @@ LogLevel = Annotated[
149
149
  ]
150
150
 
151
151
 
152
- KeyValueLabels: TypeAlias = dict[str, Union[StrictBool, StrictInt, StrictFloat, str]]
153
-
154
-
155
152
  def convert_none_to_empty_dict(v: Optional[KeyValueLabels]) -> KeyValueLabels:
156
153
  return v or {}
157
154
 
@@ -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
+ )
@@ -1,33 +1,40 @@
1
1
  import warnings
2
- from abc import ABC
3
- from collections import namedtuple
4
- from typing import Generic, TypeVar
2
+ from operator import itemgetter
3
+ from typing import Any, cast
5
4
 
6
- T = TypeVar("T")
5
+ from typing_extensions import Self, TypeVar
7
6
 
7
+ T = TypeVar("T", infer_variance=True)
8
8
 
9
- class BaseAnnotation(
10
- namedtuple("BaseAnnotation", field_names="value"), ABC, Generic[T]
11
- ):
9
+
10
+ class BaseAnnotation(tuple[T]):
12
11
  """
13
12
  Base class for Prefect annotation types.
14
13
 
15
- Inherits from `namedtuple` for unpacking support in another tools.
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.value
26
+ return self[0]
20
27
 
21
- def rewrap(self, value: T) -> "BaseAnnotation[T]":
28
+ def rewrap(self, value: T) -> Self:
22
29
  return type(self)(value)
23
30
 
24
- def __eq__(self, other: "BaseAnnotation[T]") -> bool:
31
+ def __eq__(self, other: Any) -> bool:
25
32
  if type(self) is not type(other):
26
33
  return False
27
- return self.unwrap() == other.unwrap()
34
+ return super().__eq__(other)
28
35
 
29
36
  def __repr__(self) -> str:
30
- return f"{type(self).__name__}({self.value!r})"
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 self.unwrap()
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 __init__(self, expr):
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().__init__(expr)
104
+ return super().__new__(cls, expr)
98
105
 
99
106
 
100
107
  class NotSet: