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
@@ -6,14 +6,17 @@ import ast
6
6
  import importlib.util
7
7
  import inspect
8
8
  import warnings
9
+ from collections import OrderedDict
10
+ from collections.abc import Iterable
9
11
  from functools import partial
12
+ from logging import Logger
10
13
  from pathlib import Path
11
- from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple
14
+ from typing import Any, Callable, Optional, Union, cast
12
15
 
13
- import cloudpickle
16
+ import cloudpickle # type: ignore # no stubs available
14
17
  import pydantic
15
18
  from griffe import Docstring, DocstringSectionKind, Parser, parse
16
- from typing_extensions import Literal
19
+ from typing_extensions import Literal, TypeVar
17
20
 
18
21
  from prefect._internal.pydantic.v1_schema import has_v1_type_as_param
19
22
  from prefect._internal.pydantic.v2_schema import (
@@ -32,15 +35,17 @@ from prefect.utilities.annotations import allow_failure, quote, unmapped
32
35
  from prefect.utilities.collections import isiterable
33
36
  from prefect.utilities.importtools import safe_load_namespace
34
37
 
35
- logger = get_logger(__name__)
38
+ logger: Logger = get_logger(__name__)
39
+
40
+ R = TypeVar("R", infer_variance=True)
36
41
 
37
42
 
38
43
  def get_call_parameters(
39
- fn: Callable,
40
- call_args: Tuple[Any, ...],
41
- call_kwargs: Dict[str, Any],
44
+ fn: Callable[..., Any],
45
+ call_args: tuple[Any, ...],
46
+ call_kwargs: dict[str, Any],
42
47
  apply_defaults: bool = True,
43
- ) -> Dict[str, Any]:
48
+ ) -> dict[str, Any]:
44
49
  """
45
50
  Bind a call to a function to get parameter/value mapping. Default values on
46
51
  the signature will be included if not overridden.
@@ -57,7 +62,7 @@ def get_call_parameters(
57
62
  function
58
63
  """
59
64
  if hasattr(fn, "__prefect_self__"):
60
- call_args = (fn.__prefect_self__,) + call_args
65
+ call_args = (getattr(fn, "__prefect_self__"), *call_args)
61
66
 
62
67
  try:
63
68
  bound_signature = inspect.signature(fn).bind(*call_args, **call_kwargs)
@@ -74,14 +79,14 @@ def get_call_parameters(
74
79
 
75
80
 
76
81
  def get_parameter_defaults(
77
- fn: Callable,
78
- ) -> Dict[str, Any]:
82
+ fn: Callable[..., Any],
83
+ ) -> dict[str, Any]:
79
84
  """
80
85
  Get default parameter values for a callable.
81
86
  """
82
87
  signature = inspect.signature(fn)
83
88
 
84
- parameter_defaults = {}
89
+ parameter_defaults: dict[str, Any] = {}
85
90
 
86
91
  for name, param in signature.parameters.items():
87
92
  if param.default is not signature.empty:
@@ -91,8 +96,8 @@ def get_parameter_defaults(
91
96
 
92
97
 
93
98
  def explode_variadic_parameter(
94
- fn: Callable, parameters: Dict[str, Any]
95
- ) -> Dict[str, Any]:
99
+ fn: Callable[..., Any], parameters: dict[str, Any]
100
+ ) -> dict[str, Any]:
96
101
  """
97
102
  Given a parameter dictionary, move any parameters stored in a variadic keyword
98
103
  argument parameter (i.e. **kwargs) into the top level.
@@ -125,8 +130,8 @@ def explode_variadic_parameter(
125
130
 
126
131
 
127
132
  def collapse_variadic_parameters(
128
- fn: Callable, parameters: Dict[str, Any]
129
- ) -> Dict[str, Any]:
133
+ fn: Callable[..., Any], parameters: dict[str, Any]
134
+ ) -> dict[str, Any]:
130
135
  """
131
136
  Given a parameter dictionary, move any parameters stored not present in the
132
137
  signature into the variadic keyword argument.
@@ -151,50 +156,47 @@ def collapse_variadic_parameters(
151
156
 
152
157
  missing_parameters = set(parameters.keys()) - set(signature_parameters.keys())
153
158
 
154
- if not variadic_key and missing_parameters:
159
+ if not missing_parameters:
160
+ # no missing parameters, return parameters unchanged
161
+ return parameters
162
+
163
+ if not variadic_key:
155
164
  raise ValueError(
156
165
  f"Signature for {fn} does not include any variadic keyword argument "
157
166
  "but parameters were given that are not present in the signature."
158
167
  )
159
168
 
160
- if variadic_key and not missing_parameters:
161
- # variadic key is present but no missing parameters, return parameters unchanged
162
- return parameters
163
-
164
169
  new_parameters = parameters.copy()
165
- if variadic_key:
166
- new_parameters[variadic_key] = {}
167
-
168
- for key in missing_parameters:
169
- new_parameters[variadic_key][key] = new_parameters.pop(key)
170
-
170
+ new_parameters[variadic_key] = {
171
+ key: new_parameters.pop(key) for key in missing_parameters
172
+ }
171
173
  return new_parameters
172
174
 
173
175
 
174
176
  def parameters_to_args_kwargs(
175
- fn: Callable,
176
- parameters: Dict[str, Any],
177
- ) -> Tuple[Tuple[Any, ...], Dict[str, Any]]:
177
+ fn: Callable[..., Any],
178
+ parameters: dict[str, Any],
179
+ ) -> tuple[tuple[Any, ...], dict[str, Any]]:
178
180
  """
179
181
  Convert a `parameters` dictionary to positional and keyword arguments
180
182
 
181
183
  The function _must_ have an identical signature to the original function or this
182
184
  will return an empty tuple and dict.
183
185
  """
184
- function_params = dict(inspect.signature(fn).parameters).keys()
186
+ function_params = inspect.signature(fn).parameters.keys()
185
187
  # Check for parameters that are not present in the function signature
186
188
  unknown_params = parameters.keys() - function_params
187
189
  if unknown_params:
188
190
  raise SignatureMismatchError.from_bad_params(
189
- list(function_params), list(parameters.keys())
191
+ list(function_params), list(parameters)
190
192
  )
191
193
  bound_signature = inspect.signature(fn).bind_partial()
192
- bound_signature.arguments = parameters
194
+ bound_signature.arguments = OrderedDict(parameters)
193
195
 
194
196
  return bound_signature.args, bound_signature.kwargs
195
197
 
196
198
 
197
- def call_with_parameters(fn: Callable, parameters: Dict[str, Any]):
199
+ def call_with_parameters(fn: Callable[..., R], parameters: dict[str, Any]) -> R:
198
200
  """
199
201
  Call a function with parameters extracted with `get_call_parameters`
200
202
 
@@ -207,7 +209,7 @@ def call_with_parameters(fn: Callable, parameters: Dict[str, Any]):
207
209
 
208
210
 
209
211
  def cloudpickle_wrapped_call(
210
- __fn: Callable, *args: Any, **kwargs: Any
212
+ __fn: Callable[..., Any], *args: Any, **kwargs: Any
211
213
  ) -> Callable[[], bytes]:
212
214
  """
213
215
  Serializes a function call using cloudpickle then returns a callable which will
@@ -217,18 +219,18 @@ def cloudpickle_wrapped_call(
217
219
  built-in pickler (e.g. `anyio.to_process` and `multiprocessing`) but may require
218
220
  a wider range of pickling support.
219
221
  """
220
- payload = cloudpickle.dumps((__fn, args, kwargs))
222
+ payload = cloudpickle.dumps((__fn, args, kwargs)) # type: ignore # no stubs available
221
223
  return partial(_run_serialized_call, payload)
222
224
 
223
225
 
224
- def _run_serialized_call(payload) -> bytes:
226
+ def _run_serialized_call(payload: bytes) -> bytes:
225
227
  """
226
228
  Defined at the top-level so it can be pickled by the Python pickler.
227
229
  Used by `cloudpickle_wrapped_call`.
228
230
  """
229
231
  fn, args, kwargs = cloudpickle.loads(payload)
230
232
  retval = fn(*args, **kwargs)
231
- return cloudpickle.dumps(retval)
233
+ return cloudpickle.dumps(retval) # type: ignore # no stubs available
232
234
 
233
235
 
234
236
  class ParameterSchema(pydantic.BaseModel):
@@ -236,18 +238,18 @@ class ParameterSchema(pydantic.BaseModel):
236
238
 
237
239
  title: Literal["Parameters"] = "Parameters"
238
240
  type: Literal["object"] = "object"
239
- properties: Dict[str, Any] = pydantic.Field(default_factory=dict)
240
- required: List[str] = pydantic.Field(default_factory=list)
241
- definitions: Dict[str, Any] = pydantic.Field(default_factory=dict)
241
+ properties: dict[str, Any] = pydantic.Field(default_factory=dict)
242
+ required: list[str] = pydantic.Field(default_factory=list)
243
+ definitions: dict[str, Any] = pydantic.Field(default_factory=dict)
242
244
 
243
- def model_dump_for_openapi(self) -> Dict[str, Any]:
245
+ def model_dump_for_openapi(self) -> dict[str, Any]:
244
246
  result = self.model_dump(mode="python", exclude_none=True)
245
247
  if "required" in result and not result["required"]:
246
248
  del result["required"]
247
249
  return result
248
250
 
249
251
 
250
- def parameter_docstrings(docstring: Optional[str]) -> Dict[str, str]:
252
+ def parameter_docstrings(docstring: Optional[str]) -> dict[str, str]:
251
253
  """
252
254
  Given a docstring in Google docstring format, parse the parameter section
253
255
  and return a dictionary that maps parameter names to docstring.
@@ -258,7 +260,7 @@ def parameter_docstrings(docstring: Optional[str]) -> Dict[str, str]:
258
260
  Returns:
259
261
  Mapping from parameter names to docstrings.
260
262
  """
261
- param_docstrings = {}
263
+ param_docstrings: dict[str, str] = {}
262
264
 
263
265
  if not docstring:
264
266
  return param_docstrings
@@ -279,9 +281,9 @@ def process_v1_params(
279
281
  param: inspect.Parameter,
280
282
  *,
281
283
  position: int,
282
- docstrings: Dict[str, str],
283
- aliases: Dict,
284
- ) -> Tuple[str, Any, "pydantic.Field"]:
284
+ docstrings: dict[str, str],
285
+ aliases: dict[str, str],
286
+ ) -> tuple[str, Any, Any]:
285
287
  # Pydantic model creation will fail if names collide with the BaseModel type
286
288
  if hasattr(pydantic.BaseModel, param.name):
287
289
  name = param.name + "__"
@@ -289,13 +291,13 @@ def process_v1_params(
289
291
  else:
290
292
  name = param.name
291
293
 
292
- type_ = Any if param.annotation is inspect._empty else param.annotation
294
+ type_ = Any if param.annotation is inspect.Parameter.empty else param.annotation
293
295
 
294
296
  with warnings.catch_warnings():
295
297
  warnings.filterwarnings(
296
298
  "ignore", category=pydantic.warnings.PydanticDeprecatedSince20
297
299
  )
298
- field = pydantic.Field(
300
+ field: Any = pydantic.Field( # type: ignore # this uses the v1 signature, not v2
299
301
  default=... if param.default is param.empty else param.default,
300
302
  title=param.name,
301
303
  description=docstrings.get(param.name, None),
@@ -305,19 +307,24 @@ def process_v1_params(
305
307
  return name, type_, field
306
308
 
307
309
 
308
- def create_v1_schema(name_: str, model_cfg, **model_fields):
310
+ def create_v1_schema(
311
+ name_: str, model_cfg: type[Any], model_fields: Optional[dict[str, Any]] = None
312
+ ) -> dict[str, Any]:
309
313
  with warnings.catch_warnings():
310
314
  warnings.filterwarnings(
311
315
  "ignore", category=pydantic.warnings.PydanticDeprecatedSince20
312
316
  )
313
317
 
314
- model: "pydantic.BaseModel" = pydantic.create_model(
315
- name_, __config__=model_cfg, **model_fields
318
+ model_fields = model_fields or {}
319
+ model: type[pydantic.BaseModel] = pydantic.create_model( # type: ignore # this uses the v1 signature, not v2
320
+ name_,
321
+ __config__=model_cfg, # type: ignore # this uses the v1 signature, not v2
322
+ **model_fields,
316
323
  )
317
- return model.schema(by_alias=True)
324
+ return model.schema(by_alias=True) # type: ignore # this uses the v1 signature, not v2
318
325
 
319
326
 
320
- def parameter_schema(fn: Callable) -> ParameterSchema:
327
+ def parameter_schema(fn: Callable[..., Any]) -> ParameterSchema:
321
328
  """Given a function, generates an OpenAPI-compatible description
322
329
  of the function's arguments, including:
323
330
  - name
@@ -378,7 +385,7 @@ def parameter_schema_from_entrypoint(entrypoint: str) -> ParameterSchema:
378
385
 
379
386
 
380
387
  def generate_parameter_schema(
381
- signature: inspect.Signature, docstrings: Dict[str, str]
388
+ signature: inspect.Signature, docstrings: dict[str, str]
382
389
  ) -> ParameterSchema:
383
390
  """
384
391
  Generate a parameter schema from a function signature and docstrings.
@@ -394,22 +401,22 @@ def generate_parameter_schema(
394
401
  ParameterSchema: The parameter schema.
395
402
  """
396
403
 
397
- model_fields = {}
398
- aliases = {}
404
+ model_fields: dict[str, Any] = {}
405
+ aliases: dict[str, str] = {}
399
406
 
400
407
  if not has_v1_type_as_param(signature):
401
- create_schema = create_v2_schema
408
+ config = pydantic.ConfigDict(arbitrary_types_allowed=True)
409
+
410
+ create_schema = partial(create_v2_schema, model_cfg=config)
402
411
  process_params = process_v2_params
403
412
 
404
- config = pydantic.ConfigDict(arbitrary_types_allowed=True)
405
413
  else:
406
- create_schema = create_v1_schema
407
- process_params = process_v1_params
408
414
 
409
415
  class ModelConfig:
410
416
  arbitrary_types_allowed = True
411
417
 
412
- config = ModelConfig
418
+ create_schema = partial(create_v1_schema, model_cfg=ModelConfig)
419
+ process_params = process_v1_params
413
420
 
414
421
  for position, param in enumerate(signature.parameters.values()):
415
422
  name, type_, field = process_params(
@@ -418,24 +425,26 @@ def generate_parameter_schema(
418
425
  # Generate a Pydantic model at each step so we can check if this parameter
419
426
  # type supports schema generation
420
427
  try:
421
- create_schema("CheckParameter", model_cfg=config, **{name: (type_, field)})
428
+ create_schema("CheckParameter", model_fields={name: (type_, field)})
422
429
  except (ValueError, TypeError):
423
430
  # This field's type is not valid for schema creation, update it to `Any`
424
431
  type_ = Any
425
432
  model_fields[name] = (type_, field)
426
433
 
427
434
  # Generate the final model and schema
428
- schema = create_schema("Parameters", model_cfg=config, **model_fields)
435
+ schema = create_schema("Parameters", model_fields=model_fields)
429
436
  return ParameterSchema(**schema)
430
437
 
431
438
 
432
- def raise_for_reserved_arguments(fn: Callable, reserved_arguments: Iterable[str]):
439
+ def raise_for_reserved_arguments(
440
+ fn: Callable[..., Any], reserved_arguments: Iterable[str]
441
+ ) -> None:
433
442
  """Raise a ReservedArgumentError if `fn` has any parameters that conflict
434
443
  with the names contained in `reserved_arguments`."""
435
- function_paremeters = inspect.signature(fn).parameters
444
+ function_parameters = inspect.signature(fn).parameters
436
445
 
437
446
  for argument in reserved_arguments:
438
- if argument in function_paremeters:
447
+ if argument in function_parameters:
439
448
  raise ReservedArgumentError(
440
449
  f"{argument!r} is a reserved argument name and cannot be used."
441
450
  )
@@ -479,7 +488,7 @@ def _generate_signature_from_source(
479
488
  )
480
489
  if func_def is None:
481
490
  raise ValueError(f"Function {func_name} not found in source code")
482
- parameters = []
491
+ parameters: list[inspect.Parameter] = []
483
492
 
484
493
  # Handle annotations for positional only args e.g. def func(a, /, b, c)
485
494
  for arg in func_def.args.posonlyargs:
@@ -642,8 +651,8 @@ def _get_docstring_from_source(source_code: str, func_name: str) -> Optional[str
642
651
 
643
652
 
644
653
  def expand_mapping_parameters(
645
- func: Callable, parameters: Dict[str, Any]
646
- ) -> List[Dict[str, Any]]:
654
+ func: Callable[..., Any], parameters: dict[str, Any]
655
+ ) -> list[dict[str, Any]]:
647
656
  """
648
657
  Generates a list of call parameters to be used for individual calls in a mapping
649
658
  operation.
@@ -653,29 +662,29 @@ def expand_mapping_parameters(
653
662
  parameters: A dictionary of parameters with iterables to be mapped over
654
663
 
655
664
  Returns:
656
- List: A list of dictionaries to be used as parameters for each
665
+ list: A list of dictionaries to be used as parameters for each
657
666
  call in the mapping operation
658
667
  """
659
668
  # Ensure that any parameters in kwargs are expanded before this check
660
669
  parameters = explode_variadic_parameter(func, parameters)
661
670
 
662
- iterable_parameters = {}
663
- static_parameters = {}
664
- annotated_parameters = {}
671
+ iterable_parameters: dict[str, list[Any]] = {}
672
+ static_parameters: dict[str, Any] = {}
673
+ annotated_parameters: dict[str, Union[allow_failure[Any], quote[Any]]] = {}
665
674
  for key, val in parameters.items():
666
675
  if isinstance(val, (allow_failure, quote)):
667
676
  # Unwrap annotated parameters to determine if they are iterable
668
677
  annotated_parameters[key] = val
669
- val = val.unwrap()
678
+ val: Any = val.unwrap()
670
679
 
671
680
  if isinstance(val, unmapped):
672
- static_parameters[key] = val.value
681
+ static_parameters[key] = cast(unmapped[Any], val).value
673
682
  elif isiterable(val):
674
683
  iterable_parameters[key] = list(val)
675
684
  else:
676
685
  static_parameters[key] = val
677
686
 
678
- if not len(iterable_parameters):
687
+ if not iterable_parameters:
679
688
  raise MappingMissingIterable(
680
689
  "No iterable parameters were received. Parameters for map must "
681
690
  f"include at least one iterable. Parameters: {parameters}"
@@ -693,7 +702,7 @@ def expand_mapping_parameters(
693
702
 
694
703
  map_length = list(lengths)[0]
695
704
 
696
- call_parameters_list = []
705
+ call_parameters_list: list[dict[str, Any]] = []
697
706
  for i in range(map_length):
698
707
  call_parameters = {key: value[i] for key, value in iterable_parameters.items()}
699
708
  call_parameters.update({key: value for key, value in static_parameters.items()})