prefect-client 2.16.8__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 (89) hide show
  1. prefect/__init__.py +0 -18
  2. prefect/_internal/compatibility/deprecated.py +108 -5
  3. prefect/_internal/compatibility/experimental.py +9 -8
  4. prefect/_internal/concurrency/api.py +23 -42
  5. prefect/_internal/concurrency/waiters.py +25 -22
  6. prefect/_internal/pydantic/__init__.py +16 -3
  7. prefect/_internal/pydantic/_base_model.py +39 -4
  8. prefect/_internal/pydantic/_compat.py +69 -452
  9. prefect/_internal/pydantic/_flags.py +5 -0
  10. prefect/_internal/pydantic/_types.py +8 -0
  11. prefect/_internal/pydantic/utilities/__init__.py +0 -0
  12. prefect/_internal/pydantic/utilities/config_dict.py +72 -0
  13. prefect/_internal/pydantic/utilities/field_validator.py +135 -0
  14. prefect/_internal/pydantic/utilities/model_construct.py +56 -0
  15. prefect/_internal/pydantic/utilities/model_copy.py +55 -0
  16. prefect/_internal/pydantic/utilities/model_dump.py +136 -0
  17. prefect/_internal/pydantic/utilities/model_dump_json.py +112 -0
  18. prefect/_internal/pydantic/utilities/model_fields.py +50 -0
  19. prefect/_internal/pydantic/utilities/model_fields_set.py +29 -0
  20. prefect/_internal/pydantic/utilities/model_json_schema.py +82 -0
  21. prefect/_internal/pydantic/utilities/model_rebuild.py +80 -0
  22. prefect/_internal/pydantic/utilities/model_validate.py +75 -0
  23. prefect/_internal/pydantic/utilities/model_validate_json.py +68 -0
  24. prefect/_internal/pydantic/utilities/model_validator.py +79 -0
  25. prefect/_internal/pydantic/utilities/type_adapter.py +71 -0
  26. prefect/_internal/schemas/bases.py +1 -17
  27. prefect/_internal/schemas/validators.py +425 -4
  28. prefect/agent.py +1 -1
  29. prefect/blocks/kubernetes.py +7 -3
  30. prefect/blocks/notifications.py +18 -18
  31. prefect/blocks/webhook.py +1 -1
  32. prefect/client/base.py +7 -0
  33. prefect/client/cloud.py +1 -1
  34. prefect/client/orchestration.py +51 -11
  35. prefect/client/schemas/actions.py +367 -297
  36. prefect/client/schemas/filters.py +28 -28
  37. prefect/client/schemas/objects.py +78 -147
  38. prefect/client/schemas/responses.py +240 -60
  39. prefect/client/schemas/schedules.py +6 -8
  40. prefect/concurrency/events.py +2 -2
  41. prefect/context.py +4 -2
  42. prefect/deployments/base.py +6 -13
  43. prefect/deployments/deployments.py +34 -9
  44. prefect/deployments/runner.py +9 -27
  45. prefect/deprecated/packaging/base.py +5 -6
  46. prefect/deprecated/packaging/docker.py +19 -25
  47. prefect/deprecated/packaging/file.py +10 -5
  48. prefect/deprecated/packaging/orion.py +9 -4
  49. prefect/deprecated/packaging/serializers.py +8 -58
  50. prefect/engine.py +55 -618
  51. prefect/events/actions.py +16 -1
  52. prefect/events/clients.py +45 -13
  53. prefect/events/filters.py +19 -2
  54. prefect/events/related.py +4 -4
  55. prefect/events/schemas/automations.py +13 -2
  56. prefect/events/schemas/deployment_triggers.py +73 -5
  57. prefect/events/schemas/events.py +1 -1
  58. prefect/events/utilities.py +12 -4
  59. prefect/events/worker.py +26 -8
  60. prefect/exceptions.py +3 -8
  61. prefect/filesystems.py +7 -7
  62. prefect/flows.py +7 -3
  63. prefect/infrastructure/provisioners/ecs.py +1 -0
  64. prefect/logging/configuration.py +2 -2
  65. prefect/manifests.py +1 -8
  66. prefect/profiles.toml +1 -1
  67. prefect/pydantic/__init__.py +74 -2
  68. prefect/pydantic/main.py +26 -2
  69. prefect/serializers.py +6 -31
  70. prefect/settings.py +72 -26
  71. prefect/software/python.py +3 -5
  72. prefect/task_server.py +2 -2
  73. prefect/utilities/callables.py +1 -1
  74. prefect/utilities/collections.py +2 -1
  75. prefect/utilities/dispatch.py +1 -0
  76. prefect/utilities/engine.py +629 -0
  77. prefect/utilities/pydantic.py +1 -1
  78. prefect/utilities/schema_tools/validation.py +2 -2
  79. prefect/utilities/visualization.py +1 -1
  80. prefect/variables.py +88 -12
  81. prefect/workers/base.py +20 -11
  82. prefect/workers/block.py +4 -8
  83. prefect/workers/process.py +2 -5
  84. {prefect_client-2.16.8.dist-info → prefect_client-2.17.0.dist-info}/METADATA +4 -3
  85. {prefect_client-2.16.8.dist-info → prefect_client-2.17.0.dist-info}/RECORD +88 -72
  86. prefect/_internal/schemas/transformations.py +0 -106
  87. {prefect_client-2.16.8.dist-info → prefect_client-2.17.0.dist-info}/LICENSE +0 -0
  88. {prefect_client-2.16.8.dist-info → prefect_client-2.17.0.dist-info}/WHEEL +0 -0
  89. {prefect_client-2.16.8.dist-info → prefect_client-2.17.0.dist-info}/top_level.txt +0 -0
@@ -1,18 +1,33 @@
1
+ """
2
+ This module contains a collection of functions that are used to validate the
3
+ values of fields in Pydantic models. These functions are used as validators in
4
+ Pydantic models to ensure that the values of fields conform to the expected
5
+ format.
6
+ This will be subject to consolidation and refactoring over the next few months.
7
+ """
8
+
1
9
  import datetime
2
10
  import json
3
11
  import logging
4
12
  import re
13
+ import sys
5
14
  import urllib.parse
15
+ import warnings
16
+ from copy import copy
6
17
  from pathlib import Path
7
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
18
+ from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Tuple, Union
8
19
 
9
20
  import jsonschema
10
21
  import pendulum
22
+ import yaml
11
23
 
12
24
  from prefect._internal.pydantic import HAS_PYDANTIC_V2
25
+ from prefect._internal.pydantic._flags import USE_PYDANTIC_V2
13
26
  from prefect._internal.schemas.fields import DateTimeTZ
14
27
  from prefect.exceptions import InvalidNameError, InvalidRepositoryURLError
15
28
  from prefect.utilities.annotations import NotSet
29
+ from prefect.utilities.dockerutils import get_prefect_image_name
30
+ from prefect.utilities.filesystem import relative_path_to_current_platform
16
31
  from prefect.utilities.importtools import from_qualified_name
17
32
  from prefect.utilities.names import generate_slug
18
33
  from prefect.utilities.pydantic import JsonPatch
@@ -27,9 +42,11 @@ if TYPE_CHECKING:
27
42
  from prefect.utilities.callables import ParameterSchema
28
43
 
29
44
  if HAS_PYDANTIC_V2:
30
- from pydantic.v1.fields import ModelField
31
- else:
32
- from pydantic.fields import ModelField
45
+ if USE_PYDANTIC_V2:
46
+ # TODO: we need to account for rewriting the validator to not use ModelField
47
+ pass
48
+ if not USE_PYDANTIC_V2:
49
+ from pydantic.v1.fields import ModelField
33
50
 
34
51
 
35
52
  def raise_on_name_with_banned_characters(name: str) -> str:
@@ -191,6 +208,30 @@ def handle_openapi_schema(value: Optional["ParameterSchema"]) -> "ParameterSchem
191
208
  return value
192
209
 
193
210
 
211
+ def validate_parameters_conform_to_schema(value: dict, values: dict) -> dict:
212
+ """Validate that the parameters conform to the parameter schema."""
213
+ if values.get("enforce_parameter_schema"):
214
+ validate_values_conform_to_schema(
215
+ value, values.get("parameter_openapi_schema"), ignore_required=True
216
+ )
217
+ return value
218
+
219
+
220
+ def validate_parameter_openapi_schema(value: dict, values: dict) -> dict:
221
+ """Validate that the parameter_openapi_schema is a valid json schema."""
222
+ if values.get("enforce_parameter_schema"):
223
+ validate_schema(value)
224
+ return value
225
+
226
+
227
+ def return_none_schedule(v: Optional[Union[str, dict]]) -> Optional[Union[str, dict]]:
228
+ from prefect.client.schemas.schedules import NoSchedule
229
+
230
+ if isinstance(v, NoSchedule):
231
+ return None
232
+ return v
233
+
234
+
194
235
  ### SCHEDULE SCHEMA VALIDATORS ###
195
236
 
196
237
 
@@ -246,6 +287,91 @@ def reconcile_schedules(cls, values: dict) -> dict:
246
287
  return values
247
288
 
248
289
 
290
+ # TODO: consolidate with above if possible
291
+ def reconcile_schedules_runner(values: dict) -> dict:
292
+ """
293
+ Similar to above, we reconcile the `schedule` and `schedules` fields in a deployment.
294
+ """
295
+ from prefect.deployments.schedules import (
296
+ create_minimal_deployment_schedule,
297
+ normalize_to_minimal_deployment_schedules,
298
+ )
299
+
300
+ schedule = values.get("schedule")
301
+ schedules = values.get("schedules")
302
+
303
+ if schedules is None and schedule is not None:
304
+ values["schedules"] = [create_minimal_deployment_schedule(schedule)]
305
+ elif schedules is not None and len(schedules) > 0:
306
+ values["schedules"] = normalize_to_minimal_deployment_schedules(schedules)
307
+
308
+ return values
309
+
310
+
311
+ def set_deployment_schedules(values: dict) -> dict:
312
+ from prefect.server.schemas.actions import DeploymentScheduleCreate
313
+
314
+ if not values.get("schedules") and values.get("schedule"):
315
+ values["schedules"] = [
316
+ DeploymentScheduleCreate(
317
+ schedule=values["schedule"],
318
+ active=values["is_schedule_active"],
319
+ )
320
+ ]
321
+
322
+ return values
323
+
324
+
325
+ def remove_old_deployment_fields(values: dict) -> dict:
326
+ # 2.7.7 removed worker_pool_queue_id in lieu of worker_pool_name and
327
+ # worker_pool_queue_name. Those fields were later renamed to work_pool_name
328
+ # and work_queue_name. This validator removes old fields provided
329
+ # by older clients to avoid 422 errors.
330
+ values_copy = copy(values)
331
+ worker_pool_queue_id = values_copy.pop("worker_pool_queue_id", None)
332
+ worker_pool_name = values_copy.pop("worker_pool_name", None)
333
+ worker_pool_queue_name = values_copy.pop("worker_pool_queue_name", None)
334
+ work_pool_queue_name = values_copy.pop("work_pool_queue_name", None)
335
+ if worker_pool_queue_id:
336
+ warnings.warn(
337
+ (
338
+ "`worker_pool_queue_id` is no longer supported for creating or updating "
339
+ "deployments. Please use `work_pool_name` and "
340
+ "`work_queue_name` instead."
341
+ ),
342
+ UserWarning,
343
+ )
344
+ if worker_pool_name or worker_pool_queue_name or work_pool_queue_name:
345
+ warnings.warn(
346
+ (
347
+ "`worker_pool_name`, `worker_pool_queue_name`, and "
348
+ "`work_pool_name` are"
349
+ "no longer supported for creating or updating "
350
+ "deployments. Please use `work_pool_name` and "
351
+ "`work_queue_name` instead."
352
+ ),
353
+ UserWarning,
354
+ )
355
+ return values_copy
356
+
357
+
358
+ def reconcile_paused_deployment(values):
359
+ paused = values.get("paused")
360
+ is_schedule_active = values.get("is_schedule_active")
361
+
362
+ if paused is not None:
363
+ values["paused"] = paused
364
+ values["is_schedule_active"] = not paused
365
+ elif is_schedule_active is not None:
366
+ values["paused"] = not is_schedule_active
367
+ values["is_schedule_active"] = is_schedule_active
368
+ else:
369
+ values["paused"] = False
370
+ values["is_schedule_active"] = True
371
+
372
+ return values
373
+
374
+
249
375
  def interval_schedule_must_be_positive(v: datetime.timedelta) -> datetime.timedelta:
250
376
  if v.total_seconds() <= 0:
251
377
  raise ValueError("The interval must be positive")
@@ -473,6 +599,9 @@ def set_default_image(values: dict) -> dict:
473
599
  return values
474
600
 
475
601
 
602
+ ### STATE SCHEMA VALIDATORS ###
603
+
604
+
476
605
  def get_or_create_state_name(v: str, values: dict) -> str:
477
606
  """If a name is not provided, use the type"""
478
607
 
@@ -483,6 +612,23 @@ def get_or_create_state_name(v: str, values: dict) -> str:
483
612
  return v
484
613
 
485
614
 
615
+ def set_default_scheduled_time(cls, values: dict) -> dict:
616
+ """
617
+ TODO: This should throw an error instead of setting a default but is out of
618
+ scope for https://github.com/PrefectHQ/orion/pull/174/ and can be rolled
619
+ into work refactoring state initialization
620
+ """
621
+ from prefect.server.schemas.states import StateType
622
+
623
+ if values.get("type") == StateType.SCHEDULED:
624
+ state_details = values.setdefault(
625
+ "state_details", cls.__fields__["state_details"].get_default()
626
+ )
627
+ if not state_details.scheduled_time:
628
+ state_details.scheduled_time = pendulum.now("utc")
629
+ return values
630
+
631
+
486
632
  def get_or_create_run_name(name):
487
633
  return name or generate_slug(2)
488
634
 
@@ -558,6 +704,48 @@ def validate_picklelib(value: str) -> str:
558
704
  return value
559
705
 
560
706
 
707
+ def validate_picklelib_version(values: dict) -> dict:
708
+ """
709
+ Infers a default value for `picklelib_version` if null or ensures it matches
710
+ the version retrieved from the `pickelib`.
711
+ """
712
+ picklelib = values.get("picklelib")
713
+ picklelib_version = values.get("picklelib_version")
714
+
715
+ if not picklelib:
716
+ raise ValueError("Unable to check version of unrecognized picklelib module")
717
+
718
+ pickler = from_qualified_name(picklelib)
719
+ pickler_version = getattr(pickler, "__version__", None)
720
+
721
+ if not picklelib_version:
722
+ values["picklelib_version"] = pickler_version
723
+ elif picklelib_version != pickler_version:
724
+ warnings.warn(
725
+ (
726
+ f"Mismatched {picklelib!r} versions. Found {pickler_version} in the"
727
+ f" environment but {picklelib_version} was requested. This may"
728
+ " cause the serializer to fail."
729
+ ),
730
+ RuntimeWarning,
731
+ stacklevel=3,
732
+ )
733
+
734
+ return values
735
+
736
+
737
+ def validate_picklelib_and_modules(values: dict) -> dict:
738
+ """
739
+ Prevents modules from being specified if picklelib is not cloudpickle
740
+ """
741
+ if values.get("picklelib") != "cloudpickle" and values.get("pickle_modules"):
742
+ raise ValueError(
743
+ "`pickle_modules` cannot be used without 'cloudpickle'. Got"
744
+ f" {values.get('picklelib')!r}."
745
+ )
746
+ return values
747
+
748
+
561
749
  def validate_dump_kwargs(value: dict) -> dict:
562
750
  # `default` is set by `object_encoder`. A user provided callable would make this
563
751
  # class unserializable anyway.
@@ -607,3 +795,236 @@ def validate_compressionlib(value: str) -> str:
607
795
  )
608
796
 
609
797
  return value
798
+
799
+
800
+ # TODO: if we use this elsewhere we can change the error message to be more generic
801
+ def list_length_50_or_less(v: Optional[List[float]]) -> Optional[List[float]]:
802
+ if isinstance(v, list) and (len(v) > 50):
803
+ raise ValueError("Can not configure more than 50 retry delays per task.")
804
+ return v
805
+
806
+
807
+ # TODO: if we use this elsewhere we can change the error message to be more generic
808
+ def validate_not_negative(v: Optional[float]) -> Optional[float]:
809
+ if v is not None and v < 0:
810
+ raise ValueError("`retry_jitter_factor` must be >= 0.")
811
+ return v
812
+
813
+
814
+ def validate_message_template_variables(v: Optional[str]) -> Optional[str]:
815
+ from prefect.client.schemas.objects import FLOW_RUN_NOTIFICATION_TEMPLATE_KWARGS
816
+
817
+ if v is not None:
818
+ try:
819
+ v.format(**{k: "test" for k in FLOW_RUN_NOTIFICATION_TEMPLATE_KWARGS})
820
+ except KeyError as exc:
821
+ raise ValueError(f"Invalid template variable provided: '{exc.args[0]}'")
822
+ return v
823
+
824
+
825
+ def validate_default_queue_id_not_none(v: Optional[str]) -> Optional[str]:
826
+ if v is None:
827
+ raise ValueError(
828
+ "`default_queue_id` is a required field. If you are "
829
+ "creating a new WorkPool and don't have a queue "
830
+ "ID yet, use the `actions.WorkPoolCreate` model instead."
831
+ )
832
+ return v
833
+
834
+
835
+ def validate_max_metadata_length(
836
+ v: Optional[Dict[str, Any]],
837
+ ) -> Optional[Dict[str, Any]]:
838
+ max_metadata_length = 500
839
+ if not isinstance(v, dict):
840
+ return v
841
+ for key in v.keys():
842
+ if len(str(v[key])) > max_metadata_length:
843
+ v[key] = str(v[key])[:max_metadata_length] + "..."
844
+ return v
845
+
846
+
847
+ ### DOCKER SCHEMA VALIDATORS ###
848
+
849
+
850
+ def validate_registry_url(value: Optional[str]) -> Optional[str]:
851
+ if isinstance(value, str):
852
+ if "://" not in value:
853
+ return "https://" + value
854
+ return value
855
+
856
+
857
+ def convert_labels_to_docker_format(labels: Dict[str, str]) -> Dict[str, str]:
858
+ labels = labels or {}
859
+ new_labels = {}
860
+ for name, value in labels.items():
861
+ if "/" in name:
862
+ namespace, key = name.split("/", maxsplit=1)
863
+ new_namespace = ".".join(reversed(namespace.split(".")))
864
+ new_labels[f"{new_namespace}.{key}"] = value
865
+ else:
866
+ new_labels[name] = value
867
+ return new_labels
868
+
869
+
870
+ def check_volume_format(volumes: List[str]) -> List[str]:
871
+ for volume in volumes:
872
+ if ":" not in volume:
873
+ raise ValueError(
874
+ "Invalid volume specification. "
875
+ f"Expected format 'path:container_path', but got {volume!r}"
876
+ )
877
+
878
+ return volumes
879
+
880
+
881
+ def assign_default_base_image(values: Mapping[str, Any]) -> Mapping[str, Any]:
882
+ from prefect.software.conda import CondaEnvironment
883
+
884
+ if not values.get("base_image") and not values.get("dockerfile"):
885
+ values["base_image"] = get_prefect_image_name(
886
+ flavor=(
887
+ "conda"
888
+ if isinstance(values.get("python_environment"), CondaEnvironment)
889
+ else None
890
+ )
891
+ )
892
+ return values
893
+
894
+
895
+ def base_image_xor_dockerfile(values: Mapping[str, Any]):
896
+ if values.get("base_image") and values.get("dockerfile"):
897
+ raise ValueError(
898
+ "Either `base_image` or `dockerfile` should be provided, but not both"
899
+ )
900
+ return values
901
+
902
+
903
+ def set_default_python_environment(values: Mapping[str, Any]) -> Mapping[str, Any]:
904
+ from prefect.software.python import PythonEnvironment
905
+
906
+ if values.get("base_image") and not values.get("python_environment"):
907
+ values["python_environment"] = PythonEnvironment.from_environment()
908
+ return values
909
+
910
+
911
+ ### SETTINGS SCHEMA VALIDATORS ###
912
+
913
+
914
+ def validate_settings(value: dict) -> dict:
915
+ from prefect.settings import SETTING_VARIABLES, Setting
916
+
917
+ if value is None:
918
+ return value
919
+
920
+ # Cast string setting names to variables
921
+ validated = {}
922
+ for setting, val in value.items():
923
+ if isinstance(setting, str) and setting in SETTING_VARIABLES:
924
+ validated[SETTING_VARIABLES[setting]] = val
925
+ elif isinstance(setting, Setting):
926
+ validated[setting] = val
927
+ else:
928
+ raise ValueError(f"Unknown setting {setting!r}.")
929
+
930
+ return validated
931
+
932
+
933
+ def validate_yaml(value: Union[str, dict]) -> dict:
934
+ if isinstance(value, str):
935
+ return yaml.safe_load(value)
936
+ return value
937
+
938
+
939
+ ### TASK RUN SCHEMA VALIDATORS ###
940
+
941
+
942
+ def validate_cache_key_length(cache_key: Optional[str]) -> Optional[str]:
943
+ from prefect.settings import (
944
+ PREFECT_API_TASK_CACHE_KEY_MAX_LENGTH,
945
+ )
946
+
947
+ if cache_key and len(cache_key) > PREFECT_API_TASK_CACHE_KEY_MAX_LENGTH.value():
948
+ raise ValueError(
949
+ "Cache key exceeded maximum allowed length of"
950
+ f" {PREFECT_API_TASK_CACHE_KEY_MAX_LENGTH.value()} characters."
951
+ )
952
+ return cache_key
953
+
954
+
955
+ def set_run_policy_deprecated_fields(values: dict) -> dict:
956
+ """
957
+ If deprecated fields are provided, populate the corresponding new fields
958
+ to preserve orchestration behavior.
959
+ """
960
+ if not values.get("retries", None) and values.get("max_retries", 0) != 0:
961
+ values["retries"] = values["max_retries"]
962
+
963
+ if (
964
+ not values.get("retry_delay", None)
965
+ and values.get("retry_delay_seconds", 0) != 0
966
+ ):
967
+ values["retry_delay"] = values["retry_delay_seconds"]
968
+
969
+ return values
970
+
971
+
972
+ ### PYTHON ENVIRONMENT SCHEMA VALIDATORS ###
973
+
974
+
975
+ def infer_python_version(value: Optional[str]) -> Optional[str]:
976
+ if value is None:
977
+ return f"{sys.version_info.major}.{sys.version_info.minor}"
978
+ return value
979
+
980
+
981
+ def return_v_or_none(v: Optional[str]) -> Optional[str]:
982
+ """Make sure that empty strings are treated as None"""
983
+ if not v:
984
+ return None
985
+ return v
986
+
987
+
988
+ ### INFRASTRUCTURE BLOCK SCHEMA VALIDATORS ###
989
+
990
+
991
+ def validate_block_is_infrastructure(v: "Block") -> "Block":
992
+ from prefect.infrastructure.base import Infrastructure
993
+
994
+ print("v: ", v)
995
+ if not isinstance(v, Infrastructure):
996
+ raise TypeError("Provided block is not a valid infrastructure block.")
997
+
998
+ return v
999
+
1000
+
1001
+ ### BLOCK SCHEMA VALIDATORS ###
1002
+
1003
+
1004
+ def validate_parent_and_ref_diff(values: dict) -> dict:
1005
+ parent_id = values.get("parent_block_document_id")
1006
+ ref_id = values.get("reference_block_document_id")
1007
+ if parent_id and ref_id and parent_id == ref_id:
1008
+ raise ValueError(
1009
+ "`parent_block_document_id` and `reference_block_document_id` cannot be"
1010
+ " the same"
1011
+ )
1012
+ return values
1013
+
1014
+
1015
+ def validate_name_present_on_nonanonymous_blocks(values: dict) -> dict:
1016
+ # anonymous blocks may have no name prior to actually being
1017
+ # stored in the database
1018
+ if not values.get("is_anonymous") and not values.get("name"):
1019
+ raise ValueError("Names must be provided for block documents.")
1020
+ return values
1021
+
1022
+
1023
+ ### PROCESS JOB CONFIGURATION VALIDATORS ###
1024
+
1025
+
1026
+ def validate_command(v: str) -> Path:
1027
+ """Make sure that the working directory is formatted for the current platform."""
1028
+ if v:
1029
+ return relative_path_to_current_platform(v)
1030
+ return v
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]:
@@ -3,7 +3,9 @@ from typing import TYPE_CHECKING, Dict, Type
3
3
 
4
4
  import yaml
5
5
 
6
+ from prefect._internal.compatibility.deprecated import deprecated_class
6
7
  from prefect._internal.pydantic import HAS_PYDANTIC_V2
8
+ from prefect._internal.schemas.validators import validate_yaml
7
9
 
8
10
  if HAS_PYDANTIC_V2:
9
11
  from pydantic.v1 import Field, validator
@@ -23,6 +25,10 @@ else:
23
25
  kubernetes = lazy_import("kubernetes")
24
26
 
25
27
 
28
+ @deprecated_class(
29
+ start_date="Mar 2024",
30
+ help="Use the KubernetesClusterConfig block from prefect-kubernetes instead.",
31
+ )
26
32
  class KubernetesClusterConfig(Block):
27
33
  """
28
34
  Stores configuration for interaction with Kubernetes clusters.
@@ -55,9 +61,7 @@ class KubernetesClusterConfig(Block):
55
61
 
56
62
  @validator("config", pre=True)
57
63
  def parse_yaml_config(cls, value):
58
- if isinstance(value, str):
59
- return yaml.safe_load(value)
60
- return value
64
+ return validate_yaml(value)
61
65
 
62
66
  @classmethod
63
67
  def from_file(cls: Type[Self], path: Path = None, context_name: str = None) -> Self:
@@ -77,7 +77,7 @@ class AppriseNotificationBlock(AbstractAppriseNotificationBlock, ABC):
77
77
  default=...,
78
78
  title="Webhook URL",
79
79
  description="Incoming webhook URL used to send notifications.",
80
- example="https://hooks.example.com/XXX",
80
+ examples=["https://hooks.example.com/XXX"],
81
81
  )
82
82
 
83
83
 
@@ -105,7 +105,7 @@ class SlackWebhook(AppriseNotificationBlock):
105
105
  default=...,
106
106
  title="Webhook URL",
107
107
  description="Slack incoming webhook URL used to send notifications.",
108
- example="https://hooks.slack.com/XXX",
108
+ examples=["https://hooks.slack.com/XXX"],
109
109
  )
110
110
 
111
111
 
@@ -131,9 +131,9 @@ class MicrosoftTeamsWebhook(AppriseNotificationBlock):
131
131
  ...,
132
132
  title="Webhook URL",
133
133
  description="The Teams incoming webhook URL used to send notifications.",
134
- example=(
134
+ examples=[
135
135
  "https://your-org.webhook.office.com/webhookb2/XXX/IncomingWebhook/YYY/ZZZ"
136
- ),
136
+ ],
137
137
  )
138
138
 
139
139
 
@@ -222,7 +222,7 @@ class PagerDutyWebHook(AbstractAppriseNotificationBlock):
222
222
  custom_details: Optional[Dict[str, str]] = Field(
223
223
  default=None,
224
224
  description="Additional details to include as part of the payload.",
225
- example='{"disk_space_left": "145GB"}',
225
+ examples=['{"disk_space_left": "145GB"}'],
226
226
  )
227
227
 
228
228
  def block_initialization(self) -> None:
@@ -283,14 +283,14 @@ class TwilioSMS(AbstractAppriseNotificationBlock):
283
283
  from_phone_number: str = Field(
284
284
  default=...,
285
285
  description="The valid Twilio phone number to send the message from.",
286
- example="18001234567",
286
+ examples=["18001234567"],
287
287
  )
288
288
 
289
289
  to_phone_numbers: List[str] = Field(
290
290
  default=...,
291
291
  description="A list of valid Twilio phone number(s) to send the message to.",
292
292
  # not wrapped in brackets because of the way UI displays examples; in code should be ["18004242424"]
293
- example="18004242424",
293
+ examples=["18004242424"],
294
294
  )
295
295
 
296
296
  def block_initialization(self) -> None:
@@ -366,7 +366,7 @@ class OpsgenieWebhook(AbstractAppriseNotificationBlock):
366
366
  "A comma-separated list of tags you can associate with your Opsgenie"
367
367
  " message."
368
368
  ),
369
- example='["tag1", "tag2"]',
369
+ examples=['["tag1", "tag2"]'],
370
370
  )
371
371
 
372
372
  priority: Optional[str] = Field(
@@ -388,7 +388,7 @@ class OpsgenieWebhook(AbstractAppriseNotificationBlock):
388
388
  details: Optional[Dict[str, str]] = Field(
389
389
  default=None,
390
390
  description="Additional details composed of key/values pairs.",
391
- example='{"key1": "value1", "key2": "value2"}',
391
+ examples=['{"key1": "value1", "key2": "value2"}'],
392
392
  )
393
393
 
394
394
  def block_initialization(self) -> None:
@@ -445,7 +445,7 @@ class MattermostWebhook(AbstractAppriseNotificationBlock):
445
445
  hostname: str = Field(
446
446
  default=...,
447
447
  description="The hostname of your Mattermost server.",
448
- example="Mattermost.example.com",
448
+ examples=["Mattermost.example.com"],
449
449
  )
450
450
 
451
451
  token: SecretStr = Field(
@@ -617,7 +617,7 @@ class CustomWebhookNotificationBlock(NotificationBlock):
617
617
  url: str = Field(
618
618
  title="Webhook URL",
619
619
  description="The webhook URL.",
620
- example="https://hooks.slack.com/XXX",
620
+ examples=["https://hooks.slack.com/XXX"],
621
621
  )
622
622
 
623
623
  method: Literal["GET", "POST", "PUT", "PATCH", "DELETE"] = Field(
@@ -631,10 +631,10 @@ class CustomWebhookNotificationBlock(NotificationBlock):
631
631
  default=None,
632
632
  title="JSON Data",
633
633
  description="Send json data as payload.",
634
- example=(
634
+ examples=[
635
635
  '{"text": "{{subject}}\\n{{body}}", "title": "{{name}}", "token":'
636
636
  ' "{{tokenFromSecrets}}"}'
637
- ),
637
+ ],
638
638
  )
639
639
  form_data: Optional[Dict[str, str]] = Field(
640
640
  default=None,
@@ -642,10 +642,10 @@ class CustomWebhookNotificationBlock(NotificationBlock):
642
642
  description=(
643
643
  "Send form data as payload. Should not be used together with _JSON Data_."
644
644
  ),
645
- example=(
645
+ examples=[
646
646
  '{"text": "{{subject}}\\n{{body}}", "title": "{{name}}", "token":'
647
647
  ' "{{tokenFromSecrets}}"}'
648
- ),
648
+ ],
649
649
  )
650
650
 
651
651
  headers: Optional[Dict[str, str]] = Field(None, description="Custom headers.")
@@ -659,7 +659,7 @@ class CustomWebhookNotificationBlock(NotificationBlock):
659
659
  default_factory=lambda: SecretDict(dict()),
660
660
  title="Custom Secret Values",
661
661
  description="A dictionary of secret values to be substituted in other configs.",
662
- example='{"tokenFromSecrets":"SomeSecretToken"}',
662
+ examples=['{"tokenFromSecrets":"SomeSecretToken"}'],
663
663
  )
664
664
 
665
665
  def _build_request_args(self, body: str, subject: Optional[str]):
@@ -753,14 +753,14 @@ class SendgridEmail(AbstractAppriseNotificationBlock):
753
753
  sender_email: str = Field(
754
754
  title="Sender email id",
755
755
  description="The sender email id.",
756
- example="test-support@gmail.com",
756
+ examples=["test-support@gmail.com"],
757
757
  )
758
758
 
759
759
  to_emails: List[str] = Field(
760
760
  default=...,
761
761
  title="Recipient emails",
762
762
  description="Email ids of all recipients.",
763
- example='"recipient1@gmail.com"',
763
+ examples=['"recipient1@gmail.com"'],
764
764
  )
765
765
 
766
766
  def block_initialization(self) -> None:
prefect/blocks/webhook.py CHANGED
@@ -36,7 +36,7 @@ class Webhook(Block):
36
36
  default=...,
37
37
  title="Webhook URL",
38
38
  description="The webhook URL.",
39
- example="https://hooks.slack.com/XXX",
39
+ examples=["https://hooks.slack.com/XXX"],
40
40
  )
41
41
 
42
42
  headers: SecretDict = Field(