prefect-client 2.16.7__py3-none-any.whl → 2.16.9__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.
- prefect/_internal/compatibility/experimental.py +9 -8
- prefect/_internal/concurrency/api.py +23 -42
- prefect/_internal/concurrency/waiters.py +25 -22
- prefect/_internal/pydantic/__init__.py +12 -3
- prefect/_internal/pydantic/_base_model.py +7 -4
- prefect/_internal/pydantic/_compat.py +39 -453
- prefect/_internal/pydantic/_flags.py +2 -0
- prefect/_internal/pydantic/_types.py +8 -0
- prefect/_internal/pydantic/utilities/__init__.py +0 -0
- prefect/_internal/pydantic/utilities/model_construct.py +56 -0
- prefect/_internal/pydantic/utilities/model_copy.py +55 -0
- prefect/_internal/pydantic/utilities/model_dump.py +136 -0
- prefect/_internal/pydantic/utilities/model_dump_json.py +112 -0
- prefect/_internal/pydantic/utilities/model_fields.py +50 -0
- prefect/_internal/pydantic/utilities/model_json_schema.py +82 -0
- prefect/_internal/pydantic/utilities/model_rebuild.py +80 -0
- prefect/_internal/pydantic/utilities/model_validate.py +75 -0
- prefect/_internal/pydantic/utilities/model_validate_json.py +68 -0
- prefect/_internal/pydantic/utilities/type_adapter.py +71 -0
- prefect/_internal/schemas/bases.py +1 -17
- prefect/_internal/schemas/validators.py +425 -4
- prefect/blocks/kubernetes.py +7 -3
- prefect/client/cloud.py +1 -1
- prefect/client/orchestration.py +8 -8
- prefect/client/schemas/actions.py +348 -285
- prefect/client/schemas/objects.py +47 -126
- prefect/client/schemas/responses.py +231 -57
- prefect/concurrency/events.py +2 -2
- prefect/context.py +2 -1
- prefect/deployments/base.py +4 -3
- prefect/deployments/runner.py +7 -25
- prefect/deprecated/packaging/base.py +5 -6
- prefect/deprecated/packaging/docker.py +19 -25
- prefect/deprecated/packaging/file.py +10 -5
- prefect/deprecated/packaging/orion.py +9 -4
- prefect/deprecated/packaging/serializers.py +8 -58
- prefect/engine.py +23 -22
- prefect/events/actions.py +16 -1
- prefect/events/related.py +4 -4
- prefect/events/schemas/automations.py +13 -2
- prefect/events/schemas/deployment_triggers.py +73 -5
- prefect/events/schemas/events.py +1 -1
- prefect/flows.py +3 -0
- prefect/infrastructure/provisioners/ecs.py +1 -0
- prefect/logging/configuration.py +2 -2
- prefect/pydantic/__init__.py +48 -2
- prefect/pydantic/main.py +2 -2
- prefect/serializers.py +6 -31
- prefect/settings.py +40 -17
- prefect/software/python.py +3 -5
- prefect/utilities/callables.py +1 -1
- prefect/utilities/collections.py +2 -1
- prefect/utilities/schema_tools/validation.py +2 -2
- prefect/workers/base.py +19 -10
- prefect/workers/block.py +3 -7
- prefect/workers/process.py +2 -5
- {prefect_client-2.16.7.dist-info → prefect_client-2.16.9.dist-info}/METADATA +3 -2
- {prefect_client-2.16.7.dist-info → prefect_client-2.16.9.dist-info}/RECORD +61 -50
- prefect/_internal/schemas/transformations.py +0 -106
- {prefect_client-2.16.7.dist-info → prefect_client-2.16.9.dist-info}/LICENSE +0 -0
- {prefect_client-2.16.7.dist-info → prefect_client-2.16.9.dist-info}/WHEEL +0 -0
- {prefect_client-2.16.7.dist-info → prefect_client-2.16.9.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,7 @@
|
|
1
1
|
"""
|
2
2
|
Utilities for creating and working with Prefect REST API schemas.
|
3
3
|
"""
|
4
|
+
|
4
5
|
import datetime
|
5
6
|
import json
|
6
7
|
import os
|
@@ -23,7 +24,6 @@ else:
|
|
23
24
|
from pydantic import BaseModel, Field, SecretField
|
24
25
|
from pydantic.json import custom_pydantic_encoder
|
25
26
|
|
26
|
-
from prefect._internal.compatibility.experimental import experiment_enabled
|
27
27
|
from prefect._internal.schemas.fields import DateTimeTZ
|
28
28
|
from prefect._internal.schemas.serializers import orjson_dumps_extra_compatible
|
29
29
|
|
@@ -131,22 +131,6 @@ class PrefectBaseModel(BaseModel):
|
|
131
131
|
dict
|
132
132
|
"""
|
133
133
|
|
134
|
-
experimental_fields = [
|
135
|
-
field
|
136
|
-
for _, field in self.__fields__.items()
|
137
|
-
if field.field_info.extra.get("experimental")
|
138
|
-
]
|
139
|
-
experimental_fields_to_exclude = [
|
140
|
-
field.name
|
141
|
-
for field in experimental_fields
|
142
|
-
if not experiment_enabled(field.field_info.extra["experimental-group"])
|
143
|
-
]
|
144
|
-
|
145
|
-
if experimental_fields_to_exclude:
|
146
|
-
kwargs["exclude"] = (kwargs.get("exclude") or set()).union(
|
147
|
-
experimental_fields_to_exclude
|
148
|
-
)
|
149
|
-
|
150
134
|
if json_compatible and shallow:
|
151
135
|
raise ValueError(
|
152
136
|
"`json_compatible` can only be applied to the entire object."
|
@@ -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
|
-
|
31
|
-
|
32
|
-
|
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/blocks/kubernetes.py
CHANGED
@@ -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
|
-
|
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:
|
prefect/client/cloud.py
CHANGED
prefect/client/orchestration.py
CHANGED
@@ -216,7 +216,7 @@ class PrefectClient:
|
|
216
216
|
*,
|
217
217
|
api_key: str = None,
|
218
218
|
api_version: str = None,
|
219
|
-
httpx_settings:
|
219
|
+
httpx_settings: Optional[Dict[str, Any]] = None,
|
220
220
|
) -> None:
|
221
221
|
httpx_settings = httpx_settings.copy() if httpx_settings else {}
|
222
222
|
httpx_settings.setdefault("headers", {})
|
@@ -523,8 +523,8 @@ class PrefectClient:
|
|
523
523
|
self,
|
524
524
|
deployment_id: UUID,
|
525
525
|
*,
|
526
|
-
parameters: Dict[str, Any] = None,
|
527
|
-
context:
|
526
|
+
parameters: Optional[Dict[str, Any]] = None,
|
527
|
+
context: Optional[Dict[str, Any]] = None,
|
528
528
|
state: prefect.states.State = None,
|
529
529
|
name: str = None,
|
530
530
|
tags: Iterable[str] = None,
|
@@ -608,8 +608,8 @@ class PrefectClient:
|
|
608
608
|
self,
|
609
609
|
flow: "FlowObject",
|
610
610
|
name: str = None,
|
611
|
-
parameters: Dict[str, Any] = None,
|
612
|
-
context:
|
611
|
+
parameters: Optional[Dict[str, Any]] = None,
|
612
|
+
context: Optional[Dict[str, Any]] = None,
|
613
613
|
tags: Iterable[str] = None,
|
614
614
|
parent_task_run_id: UUID = None,
|
615
615
|
state: "prefect.states.State" = None,
|
@@ -1578,7 +1578,7 @@ class PrefectClient:
|
|
1578
1578
|
version: str = None,
|
1579
1579
|
schedule: SCHEDULE_TYPES = None,
|
1580
1580
|
schedules: List[DeploymentScheduleCreate] = None,
|
1581
|
-
parameters: Dict[str, Any] = None,
|
1581
|
+
parameters: Optional[Dict[str, Any]] = None,
|
1582
1582
|
description: str = None,
|
1583
1583
|
work_queue_name: str = None,
|
1584
1584
|
work_pool_name: str = None,
|
@@ -1588,8 +1588,8 @@ class PrefectClient:
|
|
1588
1588
|
path: str = None,
|
1589
1589
|
entrypoint: str = None,
|
1590
1590
|
infrastructure_document_id: UUID = None,
|
1591
|
-
infra_overrides: Dict[str, Any] = None,
|
1592
|
-
parameter_openapi_schema:
|
1591
|
+
infra_overrides: Optional[Dict[str, Any]] = None,
|
1592
|
+
parameter_openapi_schema: Optional[Dict[str, Any]] = None,
|
1593
1593
|
is_schedule_active: Optional[bool] = None,
|
1594
1594
|
paused: Optional[bool] = None,
|
1595
1595
|
pull_steps: Optional[List[dict]] = None,
|