prefect-client 2.16.6__py3-none-any.whl → 2.16.8__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 (44) hide show
  1. prefect/_internal/pydantic/__init__.py +21 -1
  2. prefect/_internal/pydantic/_base_model.py +16 -0
  3. prefect/_internal/pydantic/_compat.py +325 -74
  4. prefect/_internal/pydantic/_flags.py +15 -0
  5. prefect/_internal/schemas/validators.py +582 -9
  6. prefect/artifacts.py +179 -70
  7. prefect/client/orchestration.py +1 -1
  8. prefect/client/schemas/actions.py +2 -2
  9. prefect/client/schemas/objects.py +13 -24
  10. prefect/client/schemas/schedules.py +18 -80
  11. prefect/deployments/deployments.py +22 -86
  12. prefect/deployments/runner.py +8 -11
  13. prefect/events/__init__.py +40 -1
  14. prefect/events/clients.py +17 -20
  15. prefect/events/filters.py +5 -6
  16. prefect/events/related.py +1 -1
  17. prefect/events/schemas/__init__.py +5 -0
  18. prefect/events/schemas/automations.py +303 -0
  19. prefect/events/{schemas.py → schemas/deployment_triggers.py} +146 -270
  20. prefect/events/schemas/events.py +285 -0
  21. prefect/events/schemas/labelling.py +106 -0
  22. prefect/events/utilities.py +2 -2
  23. prefect/events/worker.py +1 -1
  24. prefect/filesystems.py +8 -37
  25. prefect/flows.py +4 -4
  26. prefect/infrastructure/kubernetes.py +12 -56
  27. prefect/infrastructure/provisioners/__init__.py +1 -0
  28. prefect/pydantic/__init__.py +4 -0
  29. prefect/pydantic/main.py +15 -0
  30. prefect/runner/runner.py +2 -2
  31. prefect/runner/server.py +1 -1
  32. prefect/serializers.py +13 -61
  33. prefect/settings.py +35 -13
  34. prefect/task_server.py +21 -7
  35. prefect/utilities/asyncutils.py +1 -1
  36. prefect/utilities/context.py +33 -1
  37. prefect/workers/base.py +1 -2
  38. prefect/workers/block.py +3 -7
  39. {prefect_client-2.16.6.dist-info → prefect_client-2.16.8.dist-info}/METADATA +2 -2
  40. {prefect_client-2.16.6.dist-info → prefect_client-2.16.8.dist-info}/RECORD +43 -36
  41. prefect/utilities/validation.py +0 -63
  42. {prefect_client-2.16.6.dist-info → prefect_client-2.16.8.dist-info}/LICENSE +0 -0
  43. {prefect_client-2.16.6.dist-info → prefect_client-2.16.8.dist-info}/WHEEL +0 -0
  44. {prefect_client-2.16.6.dist-info → prefect_client-2.16.8.dist-info}/top_level.txt +0 -0
@@ -10,4 +10,24 @@ from pydantic.version import VERSION as PYDANTIC_VERSION
10
10
 
11
11
  HAS_PYDANTIC_V2 = PYDANTIC_VERSION.startswith("2.")
12
12
 
13
- from ._compat import model_dump, model_json_schema, model_validate, IncEx
13
+ from ._compat import (
14
+ model_dump,
15
+ model_json_schema,
16
+ model_validate,
17
+ IncEx,
18
+ model_dump_json,
19
+ model_copy,
20
+ model_validate_json,
21
+ validate_python,
22
+ )
23
+
24
+ __all__ = [
25
+ "model_dump",
26
+ "model_json_schema",
27
+ "model_validate",
28
+ "IncEx",
29
+ "model_dump_json",
30
+ "model_copy",
31
+ "model_validate_json",
32
+ "validate_python",
33
+ ]
@@ -0,0 +1,16 @@
1
+ import typing
2
+
3
+ from prefect._internal.pydantic._flags import (
4
+ HAS_PYDANTIC_V2,
5
+ USE_PYDANTIC_V2,
6
+ )
7
+
8
+ if typing.TYPE_CHECKING:
9
+ from pydantic import BaseModel
10
+
11
+ if HAS_PYDANTIC_V2 and not USE_PYDANTIC_V2:
12
+ from pydantic.v1 import BaseModel
13
+ else:
14
+ from pydantic import BaseModel
15
+
16
+ __all__ = ["BaseModel"]
@@ -1,86 +1,145 @@
1
- from typing import Any, Dict, Literal, Optional, Set, Type, Union
1
+ from typing import Any, Dict, Generic, Literal, Optional, Set, Type, TypeVar, Union
2
2
 
3
- import typing_extensions
4
- from pydantic import BaseModel
3
+ from typing_extensions import Self, TypeAlias
5
4
 
6
- from prefect._internal.pydantic import HAS_PYDANTIC_V2
5
+ from prefect._internal.pydantic._flags import (
6
+ HAS_PYDANTIC_V2,
7
+ USE_PYDANTIC_V2,
8
+ )
7
9
  from prefect.logging.loggers import get_logger
8
- from prefect.settings import PREFECT_EXPERIMENTAL_ENABLE_PYDANTIC_V2_INTERNALS
9
10
 
10
- IncEx: typing_extensions.TypeAlias = (
11
- "Union[Set[int], Set[str], Dict[int, Any], Dict[str, Any], None]"
12
- )
11
+ from ._base_model import BaseModel as PydanticBaseModel
13
12
 
13
+ IncEx: TypeAlias = "Union[Set[int], Set[str], Dict[int, Any], Dict[str, Any], None]"
14
14
  logger = get_logger("prefect._internal.pydantic")
15
15
 
16
+ T = TypeVar("T")
17
+ B = TypeVar("B", bound=PydanticBaseModel)
18
+
16
19
  if HAS_PYDANTIC_V2:
17
- from pydantic.json_schema import GenerateJsonSchema
20
+ from pydantic import (
21
+ TypeAdapter as BaseTypeAdapter,
22
+ )
23
+ from pydantic import (
24
+ parse_obj_as, # type: ignore
25
+ )
26
+ from pydantic.json_schema import GenerateJsonSchema # type: ignore
27
+ else:
28
+ from pydantic import parse_obj_as # type: ignore
18
29
 
30
+ if HAS_PYDANTIC_V2 and USE_PYDANTIC_V2:
31
+ TypeAdapter = BaseTypeAdapter # type: ignore
19
32
 
20
- def is_pydantic_v2_compatible(
21
- model_instance: Optional[BaseModel] = None, fn_name: Optional[str] = None
22
- ) -> bool:
23
- """
24
- Determines if the current environment is compatible with Pydantic V2 features,
25
- based on the presence of Pydantic V2 and a global setting that enables V2 functionalities.
26
-
27
- This function primarily serves to facilitate conditional logic in code that needs to
28
- operate differently depending on the availability of Pydantic V2 features. It checks
29
- two conditions: whether Pydantic V2 is installed, and whether the use of V2 features
30
- is explicitly enabled through a global setting (`PREFECT_EXPERIMENTAL_ENABLE_PYDANTIC_V2_INTERNALS`).
31
-
32
- Parameters:
33
- -----------
34
- model_instance : Optional[BaseModel], optional
35
- An instance of a Pydantic model. This parameter is used to perform a type check
36
- to ensure the passed object is a Pydantic model instance. If not provided or if
37
- the object is not a Pydantic model, a TypeError is raised. Defaults to None.
38
-
39
- fn_name : Optional[str], optional
40
- The name of the function or feature for which V2 compatibility is being checked.
41
- This is used for logging purposes to provide more context in debug messages.
42
- Defaults to None.
33
+ else:
43
34
 
44
- Returns:
45
- --------
46
- bool
47
- True if the current environment supports Pydantic V2 features and if the global
48
- setting for enabling V2 features is set to True. False otherwise.
35
+ class TypeAdapter(Generic[T]):
36
+ def __init__(self, type_: Union[T, Type[T]]) -> None:
37
+ self.type_ = type_
49
38
 
50
- Raises:
51
- -------
52
- TypeError
53
- If `model_instance` is provided but is not an instance of a Pydantic BaseModel.
39
+ def validate_python(
40
+ self,
41
+ __object: Any,
42
+ /,
43
+ *,
44
+ strict: Optional[bool] = None,
45
+ from_attributes: Optional[bool] = None,
46
+ context: Optional[Dict[str, Any]] = None,
47
+ ) -> Any:
48
+ return parse_obj_as(self.type_, __object) # type: ignore
49
+
50
+
51
+ # BaseModel methods and definitions
52
+
53
+
54
+ def model_copy(
55
+ model_instance: PydanticBaseModel,
56
+ *,
57
+ update: Optional[Dict[str, Any]] = None,
58
+ deep: bool = False,
59
+ ) -> PydanticBaseModel:
60
+ """Usage docs: https://docs.pydantic.dev/2.7/concepts/serialization/#model_copy
61
+
62
+ Returns a copy of the model.
63
+
64
+ Args:
65
+ update: Values to change/add in the new model. Note: the data is not validated
66
+ before creating the new model. You should trust this data.
67
+ deep: Set to `True` to make a deep copy of the model.
68
+
69
+ Returns:
70
+ New model instance.
54
71
  """
55
- if model_instance and not isinstance(model_instance, BaseModel):
56
- raise TypeError(
57
- f"Expected a Pydantic model, but got {type(model_instance).__name__}"
58
- )
72
+ if not hasattr(model_instance, "copy") and not hasattr(
73
+ model_instance, "model_copy"
74
+ ):
75
+ raise TypeError("Expected a Pydantic model instance")
59
76
 
60
- should_dump_as_v2_model = (
61
- HAS_PYDANTIC_V2 and PREFECT_EXPERIMENTAL_ENABLE_PYDANTIC_V2_INTERNALS
62
- )
77
+ if HAS_PYDANTIC_V2 and USE_PYDANTIC_V2:
78
+ return model_instance.model_copy(update=update, deep=deep)
63
79
 
64
- if should_dump_as_v2_model:
65
- logger.debug(
66
- f"Using Pydantic v2 compatibility layer for `{fn_name}`. This will be removed in a future release."
67
- )
80
+ return model_instance.copy(update=update, deep=deep) # type: ignore
68
81
 
69
- return True
70
82
 
71
- elif HAS_PYDANTIC_V2:
72
- logger.debug(
73
- "Pydantic v2 compatibility layer is disabled. To enable, set `PREFECT_EXPERIMENTAL_ENABLE_PYDANTIC_V2_INTERNALS` to `True`."
74
- )
83
+ def model_dump_json(
84
+ model_instance: PydanticBaseModel,
85
+ *,
86
+ indent: Optional[int] = None,
87
+ include: IncEx = None,
88
+ exclude: IncEx = None,
89
+ by_alias: bool = False,
90
+ exclude_unset: bool = False,
91
+ exclude_defaults: bool = False,
92
+ exclude_none: bool = False,
93
+ round_trip: bool = False,
94
+ warnings: bool = True,
95
+ ) -> str:
96
+ """
97
+ Generate a JSON representation of the model, optionally specifying which fields to include or exclude.
75
98
 
76
- else:
77
- logger.debug("Pydantic v2 is not installed.")
99
+ Args:
100
+ indent: If provided, the number of spaces to indent the JSON output.
101
+ include: A list of fields to include in the output.
102
+ exclude: A list of fields to exclude from the output.
103
+ by_alias: Whether to use the field's alias in the dictionary key if defined.
104
+ exclude_unset: Whether to exclude fields that have not been explicitly set.
105
+ exclude_defaults: Whether to exclude fields that are set to their default value.
106
+ exclude_none: Whether to exclude fields that have a value of `None`.
107
+ round_trip: If True, dumped values should be valid as input for non-idempotent types such as Json[T].
108
+ warnings: Whether to log warnings when invalid fields are encountered.
78
109
 
79
- return False
110
+ Returns:
111
+ A JSON representation of the model.
112
+ """
113
+ if not hasattr(model_instance, "json") and not hasattr(
114
+ model_instance, "model_dump_json"
115
+ ):
116
+ raise TypeError("Expected a Pydantic model instance")
117
+
118
+ if HAS_PYDANTIC_V2 and USE_PYDANTIC_V2:
119
+ return model_instance.model_dump_json(
120
+ indent=indent,
121
+ include=include,
122
+ exclude=exclude,
123
+ by_alias=by_alias,
124
+ exclude_unset=exclude_unset,
125
+ exclude_defaults=exclude_defaults,
126
+ exclude_none=exclude_none,
127
+ round_trip=round_trip,
128
+ warnings=warnings,
129
+ )
130
+
131
+ return model_instance.json( # type: ignore
132
+ include=include,
133
+ exclude=exclude,
134
+ by_alias=by_alias,
135
+ exclude_unset=exclude_unset,
136
+ exclude_defaults=exclude_defaults,
137
+ exclude_none=exclude_none,
138
+ )
80
139
 
81
140
 
82
141
  def model_dump(
83
- model_instance: BaseModel,
142
+ model_instance: PydanticBaseModel,
84
143
  *,
85
144
  mode: Union[Literal["json", "python"], str] = "python",
86
145
  include: IncEx = None,
@@ -111,7 +170,12 @@ def model_dump(
111
170
  Returns:
112
171
  A dictionary representation of the model.
113
172
  """
114
- if is_pydantic_v2_compatible(model_instance=model_instance, fn_name="model_dump"):
173
+ if not hasattr(model_instance, "dict") and not hasattr(
174
+ model_instance, "model_dump"
175
+ ):
176
+ raise TypeError("Expected a Pydantic model instance")
177
+
178
+ if HAS_PYDANTIC_V2 and USE_PYDANTIC_V2:
115
179
  return model_instance.model_dump(
116
180
  mode=mode,
117
181
  include=include,
@@ -124,7 +188,7 @@ def model_dump(
124
188
  warnings=warnings,
125
189
  )
126
190
 
127
- return model_instance.dict(
191
+ return getattr(model_instance, "dict")(
128
192
  include=include,
129
193
  exclude=exclude,
130
194
  by_alias=by_alias,
@@ -139,11 +203,11 @@ JsonSchemaMode = Literal["validation", "serialization"]
139
203
 
140
204
 
141
205
  def model_json_schema(
142
- model: Type[BaseModel],
206
+ model: Type[PydanticBaseModel],
143
207
  *,
144
208
  by_alias: bool = True,
145
209
  ref_template: str = DEFAULT_REF_TEMPLATE,
146
- schema_generator=None,
210
+ schema_generator: Any = None,
147
211
  mode: JsonSchemaMode = "validation",
148
212
  ) -> Dict[str, Any]:
149
213
  """
@@ -165,8 +229,11 @@ def model_json_schema(
165
229
  dict[str, Any]
166
230
  The JSON schema for the given model class.
167
231
  """
168
- if is_pydantic_v2_compatible(fn_name="model_json_schema"):
169
- schema_generator = GenerateJsonSchema
232
+ if not hasattr(model, "schema") and not hasattr(model, "model_json_schema"):
233
+ raise TypeError("Expected a Pydantic model type")
234
+
235
+ if HAS_PYDANTIC_V2 and USE_PYDANTIC_V2:
236
+ schema_generator = GenerateJsonSchema # type: ignore
170
237
  return model.model_json_schema(
171
238
  by_alias=by_alias,
172
239
  ref_template=ref_template,
@@ -174,20 +241,20 @@ def model_json_schema(
174
241
  mode=mode,
175
242
  )
176
243
 
177
- return model.schema(
244
+ return model.schema( # type: ignore
178
245
  by_alias=by_alias,
179
246
  ref_template=ref_template,
180
247
  )
181
248
 
182
249
 
183
250
  def model_validate(
184
- model: Type[BaseModel],
251
+ model: Type[B],
185
252
  obj: Any,
186
253
  *,
187
- strict: bool = False,
188
- from_attributes: bool = False,
254
+ strict: Optional[bool] = False,
255
+ from_attributes: Optional[bool] = False,
189
256
  context: Optional[Dict[str, Any]] = None,
190
- ) -> Union[BaseModel, Dict[str, Any]]:
257
+ ) -> B:
191
258
  """Validate a pydantic model instance.
192
259
 
193
260
  Args:
@@ -202,7 +269,10 @@ def model_validate(
202
269
  Returns:
203
270
  The validated model instance.
204
271
  """
205
- if is_pydantic_v2_compatible(fn_name="model_validate"):
272
+ if not hasattr(model, "parse_obj") and not hasattr(model, "model_validate"):
273
+ raise TypeError("Expected a Pydantic model type")
274
+
275
+ if HAS_PYDANTIC_V2 and USE_PYDANTIC_V2:
206
276
  return model.model_validate(
207
277
  obj=obj,
208
278
  strict=strict,
@@ -210,4 +280,185 @@ def model_validate(
210
280
  context=context,
211
281
  )
212
282
 
213
- return model.parse_obj(obj)
283
+ return getattr(model, "parse_obj")(obj)
284
+
285
+
286
+ def model_validate_json(
287
+ model: Type[B],
288
+ json_data: Union[str, bytes, bytearray],
289
+ *,
290
+ strict: Optional[bool] = False,
291
+ context: Optional[Dict[str, Any]] = None,
292
+ ) -> B:
293
+ """Validate the given JSON data against the Pydantic model.
294
+
295
+ Args:
296
+ json_data: The JSON data to validate.
297
+ strict: Whether to enforce types strictly.
298
+ context: Extra variables to pass to the validator.
299
+
300
+ Returns:
301
+ The validated Pydantic model.
302
+
303
+ Raises:
304
+ ValueError: If `json_data` is not a JSON string.
305
+ """
306
+ if not hasattr(model, "parse_raw") and not hasattr(model, "model_validate_json"):
307
+ raise TypeError("Expected a Pydantic model type")
308
+
309
+ if HAS_PYDANTIC_V2 and USE_PYDANTIC_V2:
310
+ return model.model_validate_json(
311
+ json_data=json_data,
312
+ strict=strict,
313
+ context=context,
314
+ )
315
+
316
+ return getattr(model, "parse_raw")(json_data)
317
+
318
+
319
+ if HAS_PYDANTIC_V2 and USE_PYDANTIC_V2:
320
+ # In this case, there's no functionality to add, so we just alias the Pydantic v2 BaseModel
321
+ class BaseModel(PydanticBaseModel): # type: ignore
322
+ pass
323
+
324
+ else:
325
+ # In this case, we're working with a Pydantic v1 model, so we need to add Pydantic v2 functionality
326
+ class BaseModel(PydanticBaseModel):
327
+ def model_dump(
328
+ self: "BaseModel",
329
+ *,
330
+ mode: str = "python",
331
+ include: IncEx = None,
332
+ exclude: IncEx = None,
333
+ by_alias: bool = False,
334
+ exclude_unset: bool = False,
335
+ exclude_defaults: bool = False,
336
+ exclude_none: bool = False,
337
+ round_trip: bool = False,
338
+ warnings: bool = True,
339
+ ) -> Dict[str, Any]:
340
+ return model_dump(
341
+ self,
342
+ mode=mode,
343
+ include=include,
344
+ exclude=exclude,
345
+ by_alias=by_alias,
346
+ exclude_unset=exclude_unset,
347
+ exclude_defaults=exclude_defaults,
348
+ exclude_none=exclude_none,
349
+ round_trip=round_trip,
350
+ warnings=warnings,
351
+ )
352
+
353
+ def model_dump_json(
354
+ self,
355
+ *,
356
+ indent: Optional[int] = None,
357
+ include: Optional[IncEx] = None,
358
+ exclude: Optional[IncEx] = None,
359
+ by_alias: bool = False,
360
+ exclude_unset: bool = False,
361
+ exclude_defaults: bool = False,
362
+ exclude_none: bool = False,
363
+ round_trip: bool = False,
364
+ warnings: bool = True,
365
+ ) -> str:
366
+ return model_dump_json(
367
+ model_instance=self,
368
+ indent=indent,
369
+ include=include,
370
+ exclude=exclude,
371
+ by_alias=by_alias,
372
+ exclude_unset=exclude_unset,
373
+ exclude_defaults=exclude_defaults,
374
+ exclude_none=exclude_none,
375
+ round_trip=round_trip,
376
+ warnings=warnings,
377
+ )
378
+
379
+ def model_copy(
380
+ self: "Self",
381
+ *,
382
+ update: Optional[Dict[str, Any]] = None,
383
+ deep: bool = False,
384
+ ) -> "Self":
385
+ return super().model_copy(update=update, deep=deep)
386
+
387
+ @classmethod
388
+ def model_json_schema(
389
+ cls,
390
+ by_alias: bool = True,
391
+ ref_template: str = DEFAULT_REF_TEMPLATE,
392
+ schema_generator: Any = None,
393
+ mode: JsonSchemaMode = "validation",
394
+ ) -> Dict[str, Any]:
395
+ return model_json_schema(
396
+ cls,
397
+ by_alias=by_alias,
398
+ ref_template=ref_template,
399
+ schema_generator=schema_generator,
400
+ mode=mode,
401
+ )
402
+
403
+ @classmethod
404
+ def model_validate(
405
+ cls: Type["Self"],
406
+ obj: Any,
407
+ *,
408
+ strict: Optional[bool] = False,
409
+ from_attributes: Optional[bool] = False,
410
+ context: Optional[Dict[str, Any]] = None,
411
+ ) -> "Self":
412
+ return model_validate(
413
+ cls,
414
+ obj,
415
+ strict=strict,
416
+ from_attributes=from_attributes,
417
+ context=context,
418
+ )
419
+
420
+ @classmethod
421
+ def model_validate_json(
422
+ cls: Type["Self"],
423
+ json_data: Union[str, bytes, bytearray],
424
+ *,
425
+ strict: Optional[bool] = False,
426
+ context: Optional[Dict[str, Any]] = None,
427
+ ) -> "Self":
428
+ return model_validate_json(
429
+ cls,
430
+ json_data,
431
+ strict=strict,
432
+ context=context,
433
+ )
434
+
435
+
436
+ # TypeAdapter methods and definitions
437
+
438
+
439
+ def validate_python(
440
+ type_: Union[T, Type[T]],
441
+ __object: Any,
442
+ /,
443
+ *,
444
+ strict: Optional[bool] = None,
445
+ from_attributes: Optional[bool] = None,
446
+ context: Optional[Dict[str, Any]] = None,
447
+ ) -> T:
448
+ """Validate a Python object against the model.
449
+
450
+ Args:
451
+ type_: The type to validate against.
452
+ __object: The Python object to validate against the model.
453
+ strict: Whether to strictly check types.
454
+ from_attributes: Whether to extract data from object attributes.
455
+ context: Additional context to pass to the validator.
456
+
457
+ !!! note
458
+ When using `TypeAdapter` with a Pydantic `dataclass`, the use of the `from_attributes`
459
+ argument is not supported.
460
+
461
+ Returns:
462
+ The validated object.
463
+ """
464
+ return TypeAdapter(type_).validate_python(__object)
@@ -0,0 +1,15 @@
1
+ import os
2
+
3
+ # Retrieve current version of Pydantic installed in environment
4
+ from pydantic.version import VERSION as PYDANTIC_VERSION
5
+
6
+ # Check if Pydantic version 2 is the installed version
7
+ HAS_PYDANTIC_V2 = PYDANTIC_VERSION.startswith("2.")
8
+
9
+ # Determine if Pydantic v2 internals should be used based on an environment variable.
10
+ USE_PYDANTIC_V2 = os.environ.get(
11
+ "PREFECT_EXPERIMENTAL_ENABLE_PYDANTIC_V2_INTERNALS", False
12
+ ) in {"1", "true", "True"}
13
+
14
+ # Set to True if Pydantic v2 is present but not enabled, indicating deprecation warnings may occur.
15
+ EXPECT_DEPRECATION_WARNINGS = HAS_PYDANTIC_V2 and not USE_PYDANTIC_V2