prefect-client 2.16.9__py3-none-any.whl → 2.17.0__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 (50) hide show
  1. prefect/__init__.py +0 -18
  2. prefect/_internal/compatibility/deprecated.py +108 -5
  3. prefect/_internal/pydantic/__init__.py +4 -0
  4. prefect/_internal/pydantic/_base_model.py +36 -4
  5. prefect/_internal/pydantic/_compat.py +33 -2
  6. prefect/_internal/pydantic/_flags.py +3 -0
  7. prefect/_internal/pydantic/utilities/config_dict.py +72 -0
  8. prefect/_internal/pydantic/utilities/field_validator.py +135 -0
  9. prefect/_internal/pydantic/utilities/model_fields_set.py +29 -0
  10. prefect/_internal/pydantic/utilities/model_validator.py +79 -0
  11. prefect/agent.py +1 -1
  12. prefect/blocks/notifications.py +18 -18
  13. prefect/blocks/webhook.py +1 -1
  14. prefect/client/base.py +7 -0
  15. prefect/client/orchestration.py +44 -4
  16. prefect/client/schemas/actions.py +27 -20
  17. prefect/client/schemas/filters.py +28 -28
  18. prefect/client/schemas/objects.py +31 -21
  19. prefect/client/schemas/responses.py +17 -11
  20. prefect/client/schemas/schedules.py +6 -8
  21. prefect/context.py +2 -1
  22. prefect/deployments/base.py +2 -10
  23. prefect/deployments/deployments.py +34 -9
  24. prefect/deployments/runner.py +2 -2
  25. prefect/engine.py +32 -596
  26. prefect/events/clients.py +45 -13
  27. prefect/events/filters.py +19 -2
  28. prefect/events/utilities.py +12 -4
  29. prefect/events/worker.py +26 -8
  30. prefect/exceptions.py +3 -8
  31. prefect/filesystems.py +7 -7
  32. prefect/flows.py +4 -3
  33. prefect/manifests.py +1 -8
  34. prefect/profiles.toml +1 -1
  35. prefect/pydantic/__init__.py +27 -1
  36. prefect/pydantic/main.py +26 -2
  37. prefect/settings.py +33 -10
  38. prefect/task_server.py +2 -2
  39. prefect/utilities/dispatch.py +1 -0
  40. prefect/utilities/engine.py +629 -0
  41. prefect/utilities/pydantic.py +1 -1
  42. prefect/utilities/visualization.py +1 -1
  43. prefect/variables.py +88 -12
  44. prefect/workers/base.py +1 -1
  45. prefect/workers/block.py +1 -1
  46. {prefect_client-2.16.9.dist-info → prefect_client-2.17.0.dist-info}/METADATA +3 -3
  47. {prefect_client-2.16.9.dist-info → prefect_client-2.17.0.dist-info}/RECORD +50 -45
  48. {prefect_client-2.16.9.dist-info → prefect_client-2.17.0.dist-info}/LICENSE +0 -0
  49. {prefect_client-2.16.9.dist-info → prefect_client-2.17.0.dist-info}/WHEEL +0 -0
  50. {prefect_client-2.16.9.dist-info → prefect_client-2.17.0.dist-info}/top_level.txt +0 -0
prefect/__init__.py CHANGED
@@ -106,24 +106,6 @@ from prefect._internal.compatibility.deprecated import (
106
106
  register_renamed_module,
107
107
  )
108
108
 
109
- register_renamed_module(
110
- "prefect.client.orchestration",
111
- "prefect.client.orchestration",
112
- start_date="Feb 2023",
113
- )
114
- register_renamed_module(
115
- "prefect.docker",
116
- "prefect.utilities.dockerutils",
117
- start_date="Mar 2023",
118
- )
119
- register_renamed_module(
120
- "prefect.infrastructure.docker",
121
- "prefect.infrastructure.container",
122
- start_date="Mar 2023",
123
- )
124
- register_renamed_module(
125
- "prefect.projects", "prefect.deployments", start_date="Jun 2023"
126
- )
127
109
  register_renamed_module(
128
110
  "prefect.packaging", "prefect.deprecated.packaging", start_date="Mar 2024"
129
111
  )
@@ -12,16 +12,18 @@ e.g. Jan 2023.
12
12
  import functools
13
13
  import sys
14
14
  import warnings
15
- from typing import Any, Callable, List, Optional, Type, TypeVar
15
+ from typing import Any, Callable, Dict, List, Optional, Type, TypeVar, Union
16
16
 
17
17
  import pendulum
18
18
 
19
19
  from prefect._internal.pydantic import HAS_PYDANTIC_V2
20
20
 
21
21
  if HAS_PYDANTIC_V2:
22
- import pydantic.v1 as pydantic
22
+ from pydantic.v1 import BaseModel, Field, root_validator
23
+ from pydantic.v1.schema import default_ref_template
23
24
  else:
24
- import pydantic
25
+ from pydantic import BaseModel, Field, root_validator
26
+ from pydantic.schema import default_ref_template
25
27
 
26
28
  from prefect.utilities.callables import get_call_parameters
27
29
  from prefect.utilities.importtools import (
@@ -31,7 +33,7 @@ from prefect.utilities.importtools import (
31
33
  )
32
34
 
33
35
  T = TypeVar("T", bound=Callable)
34
- M = TypeVar("M", bound=pydantic.BaseModel)
36
+ M = TypeVar("M", bound=BaseModel)
35
37
 
36
38
 
37
39
  DEPRECATED_WARNING = (
@@ -207,7 +209,7 @@ def deprecated_field(
207
209
  ```python
208
210
 
209
211
  @deprecated_field("x", when=lambda x: x is not None)
210
- class Model(pydantic.BaseModel)
212
+ class Model(BaseModel)
211
213
  x: Optional[int] = None
212
214
  y: str
213
215
  ```
@@ -276,3 +278,104 @@ def register_renamed_module(old_name: str, new_name: str, start_date: str):
276
278
  DEPRECATED_MODULE_ALIASES.append(
277
279
  AliasedModuleDefinition(old_name, new_name, callback)
278
280
  )
281
+
282
+
283
+ class DeprecatedInfraOverridesField(BaseModel):
284
+ """
285
+ A model mixin that handles the deprecated `infra_overrides` field.
286
+
287
+ The `infra_overrides` field has been renamed to `job_variables`. This mixin maintains
288
+ backwards compatibility with users of the `infra_overrides` field while presenting
289
+ `job_variables` as the user-facing field.
290
+
291
+ When we remove support for `infra_overrides`, we can remove this class as a parent of
292
+ all schemas that use it, leaving them with only the `job_variables` field.
293
+ """
294
+
295
+ infra_overrides: Optional[Dict[str, Any]] = Field(
296
+ default_factory=dict,
297
+ description="Deprecated field. Use `job_variables` instead.",
298
+ )
299
+
300
+ @root_validator(pre=True)
301
+ def _job_variables_from_infra_overrides(
302
+ cls, values: Dict[str, Any]
303
+ ) -> Dict[str, Any]:
304
+ """
305
+ Validate that only one of `infra_overrides` or `job_variables` is used
306
+ and keep them in sync during init.
307
+ """
308
+ job_variables = values.get("job_variables")
309
+ infra_overrides = values.get("infra_overrides")
310
+
311
+ if job_variables is not None and infra_overrides is not None:
312
+ if job_variables != infra_overrides:
313
+ raise ValueError(
314
+ "The `infra_overrides` field has been renamed to `job_variables`."
315
+ "Use one of these fields, but not both."
316
+ )
317
+ return values
318
+ elif job_variables is not None and infra_overrides is None:
319
+ values["infra_overrides"] = job_variables
320
+ elif job_variables is None and infra_overrides is not None:
321
+ values["job_variables"] = infra_overrides
322
+ return values
323
+
324
+ def __setattr__(self, key: str, value: Any) -> None:
325
+ """
326
+ Override the default __setattr__ to ensure that setting `infra_overrides` or
327
+ `job_variables` will update both fields.
328
+ """
329
+ if key == "infra_overrides" or key == "job_variables":
330
+ updates = {"infra_overrides": value, "job_variables": value}
331
+ self.__dict__.update(updates)
332
+ return
333
+ super().__setattr__(key, value)
334
+
335
+ def dict(self, **kwargs) -> Dict[str, Any]:
336
+ """
337
+ Override the default dict method to ensure only `infra_overrides` is serialized.
338
+ This preserves backwards compatibility for newer clients talking to older servers.
339
+ """
340
+ exclude: Union[set, Dict[str, Any]] = kwargs.pop("exclude", set())
341
+ exclude_type = type(exclude)
342
+
343
+ if exclude_type is set:
344
+ exclude.add("job_variables")
345
+ elif exclude_type is dict:
346
+ exclude["job_variables"] = True
347
+ kwargs["exclude"] = exclude
348
+
349
+ return super().dict(**kwargs)
350
+
351
+ @classmethod
352
+ def schema(
353
+ cls, by_alias: bool = True, ref_template: str = default_ref_template
354
+ ) -> Dict[str, Any]:
355
+ """
356
+ Don't use the mixin docstring as the description if this class is missing a
357
+ docstring.
358
+ """
359
+ schema = super().schema(by_alias=by_alias, ref_template=ref_template)
360
+
361
+ if not cls.__doc__:
362
+ schema.pop("description", None)
363
+
364
+ return schema
365
+
366
+
367
+ def handle_deprecated_infra_overrides_parameter(
368
+ job_variables: Dict[str, Any], infra_overrides: Dict[str, Any]
369
+ ) -> Optional[Dict[str, Any]]:
370
+ if infra_overrides is not None and job_variables is not None:
371
+ raise RuntimeError(
372
+ "The `infra_overrides` argument has been renamed to `job_variables`."
373
+ "Use one or the other, but not both."
374
+ )
375
+ elif infra_overrides is not None and job_variables is None:
376
+ jv = infra_overrides
377
+ elif job_variables is not None and infra_overrides is None:
378
+ jv = job_variables
379
+ else:
380
+ jv = None
381
+ return jv
@@ -21,6 +21,8 @@ from ._compat import (
21
21
  BaseModel,
22
22
  Field,
23
23
  FieldInfo,
24
+ field_validator,
25
+ model_validator,
24
26
  )
25
27
 
26
28
  from ._types import IncEx
@@ -39,4 +41,6 @@ __all__ = [
39
41
  "HAS_PYDANTIC_V2",
40
42
  "Field",
41
43
  "FieldInfo",
44
+ "field_validator",
45
+ "model_validator",
42
46
  ]
@@ -1,3 +1,6 @@
1
+ """
2
+ This file introduces a conditional import of `BaseModel` from Pydantic, depending on the Pydantic version available. If Pydantic V2 is not used, it falls back to importing `BaseModel` from Pydantic V1. This is to ensure compatibility with different versions of Pydantic.
3
+ """
1
4
  import typing
2
5
 
3
6
  from prefect._internal.pydantic._flags import (
@@ -6,14 +9,43 @@ from prefect._internal.pydantic._flags import (
6
9
  )
7
10
 
8
11
  if typing.TYPE_CHECKING:
9
- from pydantic import BaseModel, Field
12
+ from pydantic import (
13
+ BaseModel,
14
+ ConfigDict,
15
+ Field,
16
+ PrivateAttr,
17
+ SecretStr,
18
+ ValidationError,
19
+ )
10
20
  from pydantic.fields import FieldInfo
11
21
 
12
22
  if HAS_PYDANTIC_V2 and not USE_PYDANTIC_V2:
13
- from pydantic.v1 import BaseModel, Field
23
+ from pydantic.v1 import (
24
+ BaseModel,
25
+ ConfigDict,
26
+ Field,
27
+ PrivateAttr,
28
+ SecretStr,
29
+ ValidationError,
30
+ )
14
31
  from pydantic.v1.fields import FieldInfo
15
32
  else:
16
- from pydantic import BaseModel, Field
33
+ from pydantic import (
34
+ BaseModel,
35
+ ConfigDict,
36
+ Field,
37
+ PrivateAttr,
38
+ SecretStr,
39
+ ValidationError,
40
+ )
17
41
  from pydantic.fields import FieldInfo
18
42
 
19
- __all__ = ["BaseModel", "Field", "FieldInfo"]
43
+ __all__ = [
44
+ "BaseModel",
45
+ "Field",
46
+ "FieldInfo",
47
+ "PrivateAttr",
48
+ "SecretStr",
49
+ "ConfigDict",
50
+ "ValidationError",
51
+ ]
@@ -1,17 +1,39 @@
1
+ """
2
+ Functions within this module check for Pydantic V2 compatibility and provide mechanisms for copying,
3
+ dumping, and validating models in a way that is agnostic to the underlying Pydantic version.
4
+ """
5
+ import typing
6
+
1
7
  from ._base_model import BaseModel as PydanticBaseModel
2
- from ._base_model import Field, FieldInfo
8
+ from ._base_model import (
9
+ ConfigDict,
10
+ Field,
11
+ FieldInfo,
12
+ PrivateAttr,
13
+ SecretStr,
14
+ ValidationError,
15
+ )
3
16
  from ._flags import HAS_PYDANTIC_V2, USE_PYDANTIC_V2
17
+ from .utilities.config_dict import ConfigMixin
18
+ from .utilities.field_validator import field_validator
4
19
  from .utilities.model_construct import ModelConstructMixin, model_construct
5
20
  from .utilities.model_copy import ModelCopyMixin, model_copy
6
21
  from .utilities.model_dump import ModelDumpMixin, model_dump
7
22
  from .utilities.model_dump_json import ModelDumpJsonMixin, model_dump_json
8
23
  from .utilities.model_fields import ModelFieldMixin
24
+ from .utilities.model_fields_set import ModelFieldsSetMixin, model_fields_set
9
25
  from .utilities.model_json_schema import ModelJsonSchemaMixin, model_json_schema
10
26
  from .utilities.model_validate import ModelValidateMixin, model_validate
11
27
  from .utilities.model_validate_json import ModelValidateJsonMixin, model_validate_json
28
+ from .utilities.model_validator import model_validator
12
29
  from .utilities.type_adapter import TypeAdapter, validate_python
13
30
 
14
- if HAS_PYDANTIC_V2 and USE_PYDANTIC_V2:
31
+ if typing.TYPE_CHECKING:
32
+
33
+ class BaseModel(PydanticBaseModel): # type: ignore
34
+ pass
35
+
36
+ elif HAS_PYDANTIC_V2 and USE_PYDANTIC_V2:
15
37
  # In this case, there's no functionality to add, so we just alias the Pydantic v2 BaseModel
16
38
  class BaseModel(PydanticBaseModel): # type: ignore
17
39
  pass
@@ -29,6 +51,8 @@ else:
29
51
  ModelValidateMixin,
30
52
  ModelValidateJsonMixin,
31
53
  ModelFieldMixin,
54
+ ConfigMixin,
55
+ ModelFieldsSetMixin,
32
56
  PydanticBaseModel,
33
57
  ):
34
58
  pass
@@ -42,9 +66,16 @@ __all__ = [
42
66
  "model_json_schema",
43
67
  "model_validate",
44
68
  "model_validate_json",
69
+ "model_fields_set",
45
70
  "TypeAdapter",
46
71
  "validate_python",
47
72
  "BaseModel",
48
73
  "Field",
49
74
  "FieldInfo",
75
+ "field_validator",
76
+ "model_validator",
77
+ "PrivateAttr",
78
+ "SecretStr",
79
+ "ConfigDict",
80
+ "ValidationError",
50
81
  ]
@@ -1,3 +1,6 @@
1
+ """
2
+ This file defines flags that determine whether Pydantic V2 is available and whether its features should be used.
3
+ """
1
4
  import os
2
5
 
3
6
  # Retrieve current version of Pydantic installed in environment
@@ -0,0 +1,72 @@
1
+ import typing
2
+
3
+ from prefect._internal.pydantic._base_model import BaseModel, ConfigDict
4
+ from prefect._internal.pydantic._flags import HAS_PYDANTIC_V2, USE_PYDANTIC_V2
5
+
6
+ if HAS_PYDANTIC_V2:
7
+ from pydantic.v1.main import ModelMetaclass as PydanticModelMetaclass
8
+ else:
9
+ from pydantic.main import ModelMetaclass as PydanticModelMetaclass
10
+
11
+
12
+ if USE_PYDANTIC_V2:
13
+
14
+ class ConfigMixin(BaseModel): # type: ignore
15
+ pass
16
+
17
+ else:
18
+ T = typing.TypeVar("T")
19
+
20
+ CONFIG_V1_V2_KEYS: typing.Dict[str, str] = {
21
+ "allow_population_by_field_name": "populate_by_name",
22
+ "anystr_lower": "str_to_lower",
23
+ "anystr_strip_whitespace": "str_strip_whitespace",
24
+ "anystr_upper": "str_to_upper",
25
+ "keep_untouched": "ignored_types",
26
+ "max_anystr_length": "str_max_length",
27
+ "min_anystr_length": "str_min_length",
28
+ "orm_mode": "from_attributes",
29
+ "schema_extra": "json_schema_extra",
30
+ "validate_all": "validate_default",
31
+ "copy_on_model_validation": "revalidate_instances",
32
+ }
33
+
34
+ CONFIG_V2_V1_KEYS: typing.Dict[str, str] = {
35
+ v: k for k, v in CONFIG_V1_V2_KEYS.items()
36
+ }
37
+
38
+ def _convert_v2_config_to_v1_config(
39
+ config_dict: typing.Union[ConfigDict, typing.Dict[str, typing.Any]],
40
+ ) -> type:
41
+ deprecated_renamed_keys = CONFIG_V2_V1_KEYS.keys() & config_dict.keys()
42
+ output: typing.Dict[str, typing.Any] = {}
43
+ for k in sorted(deprecated_renamed_keys):
44
+ if CONFIG_V2_V1_KEYS[k] == "copy_on_model_validation":
45
+ value = config_dict.get(k)
46
+ if value == "never":
47
+ output[CONFIG_V2_V1_KEYS[k]] = "none"
48
+ if value == "always":
49
+ output[CONFIG_V2_V1_KEYS[k]] = "deep"
50
+ if value == "subclass-instances":
51
+ output[CONFIG_V2_V1_KEYS[k]] = "deep"
52
+ else:
53
+ output[CONFIG_V2_V1_KEYS[k]] = config_dict.get(k)
54
+ return type("Config", (), output)
55
+
56
+ class ConfigMeta(PydanticModelMetaclass): # type: ignore
57
+ def __new__( # type: ignore
58
+ cls,
59
+ name: str,
60
+ bases: typing.Any,
61
+ namespace: typing.Dict[str, typing.Any],
62
+ **kwargs: typing.Any,
63
+ ): # type: ignore
64
+ if model_config := namespace.get("model_config"):
65
+ namespace["Config"] = _convert_v2_config_to_v1_config(model_config)
66
+ return super().__new__(cls, name, bases, namespace, **kwargs) # type: ignore
67
+
68
+ class ConfigMixin(BaseModel, metaclass=ConfigMeta):
69
+ model_config: typing.ClassVar[ConfigDict]
70
+
71
+
72
+ __all__ = ["ConfigMixin"]
@@ -0,0 +1,135 @@
1
+ """
2
+ Conditional decorator for fields depending on Pydantic version.
3
+ """
4
+
5
+ import functools
6
+ from inspect import signature
7
+ from typing import TYPE_CHECKING, Any, Callable, Dict, Literal, Optional, TypeVar, Union
8
+
9
+ from typing_extensions import TypeAlias
10
+
11
+ from prefect._internal.pydantic._flags import HAS_PYDANTIC_V2, USE_V2_MODELS
12
+
13
+ FieldValidatorModes: TypeAlias = Literal["before", "after", "wrap", "plain"]
14
+ T = TypeVar("T", bound=Callable[..., Any])
15
+
16
+ if TYPE_CHECKING:
17
+ from prefect._internal.pydantic._compat import BaseModel
18
+
19
+
20
+ def field_validator(
21
+ field: str,
22
+ /,
23
+ *fields: str,
24
+ mode: FieldValidatorModes = "after", # v2 only
25
+ check_fields: Union[bool, None] = None,
26
+ pre: bool = False, # v1 only
27
+ allow_reuse: Optional[bool] = None,
28
+ always: bool = False, # v1 only
29
+ ) -> Callable[[Any], Any]:
30
+ """Usage docs: https://docs.pydantic.dev/2.7/concepts/validators/#field-validators
31
+ Returns a decorator that conditionally applies Pydantic's `field_validator` or `validator`,
32
+ based on the Pydantic version available, for specified field(s) of a Pydantic model.
33
+
34
+ In Pydantic V2, it uses `field_validator` allowing more granular control over validation,
35
+ including pre-validation and post-validation modes. In Pydantic V1, it falls back to
36
+ using `validator`, which is less flexible but maintains backward compatibility.
37
+
38
+ Decorate methods on the class indicating that they should be used to validate fields.
39
+
40
+ Example usage:
41
+ ```py
42
+ from typing import Any
43
+
44
+ from pydantic import (
45
+ BaseModel,
46
+ ValidationError,
47
+ field_validator,
48
+ )
49
+
50
+ class Model(BaseModel):
51
+ a: str
52
+
53
+ @field_validator('a')
54
+ @classmethod
55
+ def ensure_foobar(cls, v: Any):
56
+ if 'foobar' not in v:
57
+ raise ValueError('"foobar" not found in a')
58
+ return v
59
+
60
+ print(repr(Model(a='this is foobar good')))
61
+ #> Model(a='this is foobar good')
62
+
63
+ try:
64
+ Model(a='snap')
65
+ except ValidationError as exc_info:
66
+ print(exc_info)
67
+ '''
68
+ 1 validation error for Model
69
+ a
70
+ Value error, "foobar" not found in a [type=value_error, input_value='snap', input_type=str]
71
+ '''
72
+ ```
73
+
74
+ For more in depth examples, see https://docs.pydantic.dev/latest/concepts/validators/#field-validators
75
+
76
+ Args:
77
+ field: The first field the `field_validator` should be called on; this is separate
78
+ from `fields` to ensure an error is raised if you don't pass at least one.
79
+ *fields: Additional field(s) the `field_validator` should be called on.
80
+ mode: Specifies whether to validate the fields before or after validation.
81
+ check_fields: Whether to check that the fields actually exist on the model.
82
+
83
+ Returns:
84
+ A decorator that can be used to decorate a function to be used as a field_validator.
85
+
86
+ Raises:
87
+ PydanticUserError:
88
+ - If `@field_validator` is used bare (with no fields).
89
+ - If the args passed to `@field_validator` as fields are not strings.
90
+ - If `@field_validator` applied to instance methods.
91
+ """
92
+
93
+ def decorator(validate_func: T) -> T:
94
+ if USE_V2_MODELS:
95
+ from pydantic import field_validator # type: ignore
96
+
97
+ return field_validator(
98
+ field, *fields, mode=mode, check_fields=check_fields
99
+ )(validate_func)
100
+ elif HAS_PYDANTIC_V2:
101
+ from pydantic.v1 import validator # type: ignore
102
+ else:
103
+ from pydantic import validator
104
+
105
+ # Extract the parameters of the validate_func function
106
+ # e.g. if validate_func has a signature of (cls, v, values, config), we want to
107
+ # filter the kwargs to include only those expected by validate_func, which may
108
+ # look like (cls, v) or (cls, v, values) etc.
109
+ validate_func_params = signature(validate_func).parameters
110
+
111
+ @functools.wraps(validate_func)
112
+ def wrapper(
113
+ cls: "BaseModel",
114
+ v: Any,
115
+ **kwargs: Any,
116
+ ) -> Any:
117
+ filtered_kwargs: Dict[str, Any] = {
118
+ k: v for k, v in kwargs.items() if k in validate_func_params
119
+ }
120
+
121
+ return validate_func(cls, v, **filtered_kwargs)
122
+
123
+ # In Pydantic V1, `allow_reuse` is by default False, while in Pydantic V2, it is by default True.
124
+ # We default to False in Pydantic V1 to maintain backward compatibility
125
+ # e.g. One uses @validator("a", pre=True, allow_reuse=True) in Pydantic V1
126
+ validator_kwargs: Dict[str, Any] = {
127
+ "pre": pre,
128
+ "always": always,
129
+ "check_fields": check_fields if check_fields is not None else True,
130
+ "allow_reuse": allow_reuse if allow_reuse is not None else False,
131
+ }
132
+
133
+ return validator(field, *fields, **validator_kwargs)(wrapper) # type: ignore
134
+
135
+ return decorator
@@ -0,0 +1,29 @@
1
+ import typing
2
+
3
+ from prefect._internal.pydantic._base_model import BaseModel
4
+ from prefect._internal.pydantic._flags import USE_V2_MODELS
5
+
6
+
7
+ def model_fields_set(model_instance: "BaseModel") -> typing.Set[str]:
8
+ """
9
+ Returns a set of the model's fields.
10
+ """
11
+ if USE_V2_MODELS:
12
+ return getattr(model_instance, "__pydantic_fields_set__")
13
+ else:
14
+ return getattr(model_instance, "__fields_set__")
15
+
16
+
17
+ class ModelFieldsSetMixin(BaseModel):
18
+ @property
19
+ def model_fields_set(self) -> typing.Set[str]:
20
+ """Returns the set of fields that have been explicitly set on this model instance.
21
+
22
+ Returns:
23
+ A set of strings representing the fields that have been set,
24
+ i.e. that were not filled from defaults.
25
+ """
26
+ return model_fields_set(self)
27
+
28
+
29
+ __all__ = ["ModelFieldsSetMixin", "model_fields_set"]
@@ -0,0 +1,79 @@
1
+ import functools
2
+ from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, TypeVar
3
+
4
+ from prefect._internal.pydantic._flags import HAS_PYDANTIC_V2, USE_V2_MODELS
5
+
6
+ T = TypeVar("T", bound=Callable[..., Any])
7
+
8
+ if TYPE_CHECKING:
9
+ from prefect._internal.pydantic._compat import BaseModel
10
+
11
+
12
+ def model_validator(
13
+ _func: Optional[Callable] = None,
14
+ *,
15
+ mode: Literal["wrap", "before", "after"] = "before", # v2 only
16
+ pre: bool = False, # v1 only
17
+ skip_on_failure: bool = False, # v1 only
18
+ ) -> Any:
19
+ """Usage docs: https://docs.pydantic.dev/2.6/concepts/validators/#model-validators
20
+
21
+ A decorator designed for Pydantic model methods to facilitate additional validations.
22
+ It can be applied to instance methods, class methods, or static methods of a class,
23
+ wrapping around the designated function to inject validation logic.
24
+
25
+ The `model_validator` does not differentiate between the types of methods it decorates
26
+ (instance, class, or static methods). It generically wraps the given function,
27
+ allowing the class's mechanism to determine how the method is treated
28
+ (e.g., passing `self` for instance methods or `cls` for class methods).
29
+
30
+ The actual handling of method types (instance, class, or static) is governed by the class
31
+ definition itself, using Python's standard `@classmethod` and `@staticmethod` decorators
32
+ where appropriate. This decorator simply intercepts the method call, allowing for custom
33
+ validation logic before, after, or wrapping the original method call, depending on the
34
+ `mode` parameter.
35
+
36
+ Args:
37
+ _func: The function to be decorated. If None, the decorator is applied with parameters.
38
+ mode: Specifies when the validation should occur. 'before' or 'after' are for v1 compatibility,
39
+ 'wrap' introduces v2 behavior where the validation wraps the original call.
40
+ pre: (v1 only) If True, the validator is called before Pydantic's own validators.
41
+ skip_on_failure: (v1 only) If True, skips validation if an earlier validation failed.
42
+
43
+ Returns:
44
+ The decorated function with added validation logic.
45
+
46
+ Note:
47
+ - The behavior of the decorator changes depending on the version of Pydantic being used.
48
+ - The specific logic for validation should be defined within the decorated function.
49
+ """
50
+
51
+ def decorator(validate_func: T) -> T:
52
+ if USE_V2_MODELS:
53
+ from pydantic import model_validator
54
+
55
+ return model_validator(
56
+ mode=mode,
57
+ )(validate_func) # type: ignore
58
+
59
+ elif HAS_PYDANTIC_V2:
60
+ from pydantic.v1 import root_validator
61
+
62
+ else:
63
+ from pydantic import root_validator
64
+
65
+ @functools.wraps(validate_func)
66
+ def wrapper(
67
+ cls: "BaseModel",
68
+ v: Any,
69
+ ) -> Any:
70
+ return validate_func(cls, v)
71
+
72
+ return root_validator(
73
+ pre=pre,
74
+ skip_on_failure=skip_on_failure,
75
+ )(wrapper) # type: ignore
76
+
77
+ if _func is None:
78
+ return decorator
79
+ return decorator(_func)
prefect/agent.py CHANGED
@@ -445,7 +445,7 @@ class PrefectAgent:
445
445
  # attributes of the infrastructure block
446
446
  doc_dict = infra_document.dict()
447
447
  infra_dict = doc_dict.get("data", {})
448
- for override, value in (deployment.infra_overrides or {}).items():
448
+ for override, value in (deployment.job_variables or {}).items():
449
449
  nested_fields = override.split(".")
450
450
  data = infra_dict
451
451
  for field in nested_fields[:-1]: