prefect-client 2.19.3__py3-none-any.whl → 3.0.0rc1__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/__init__.py +8 -56
- prefect/_internal/compatibility/deprecated.py +6 -115
- prefect/_internal/compatibility/experimental.py +4 -79
- prefect/_internal/concurrency/api.py +0 -34
- prefect/_internal/concurrency/calls.py +0 -6
- prefect/_internal/concurrency/cancellation.py +0 -3
- prefect/_internal/concurrency/event_loop.py +0 -20
- prefect/_internal/concurrency/inspection.py +3 -3
- prefect/_internal/concurrency/threads.py +35 -0
- prefect/_internal/concurrency/waiters.py +0 -28
- prefect/_internal/pydantic/__init__.py +0 -45
- prefect/_internal/pydantic/v1_schema.py +21 -22
- prefect/_internal/pydantic/v2_schema.py +0 -2
- prefect/_internal/pydantic/v2_validated_func.py +18 -23
- prefect/_internal/schemas/bases.py +44 -177
- prefect/_internal/schemas/fields.py +1 -43
- prefect/_internal/schemas/validators.py +60 -158
- prefect/artifacts.py +161 -14
- prefect/automations.py +39 -4
- prefect/blocks/abstract.py +1 -1
- prefect/blocks/core.py +268 -148
- prefect/blocks/fields.py +2 -57
- prefect/blocks/kubernetes.py +8 -12
- prefect/blocks/notifications.py +40 -20
- prefect/blocks/system.py +22 -11
- prefect/blocks/webhook.py +2 -9
- prefect/client/base.py +4 -4
- prefect/client/cloud.py +8 -13
- prefect/client/orchestration.py +347 -341
- prefect/client/schemas/actions.py +92 -86
- prefect/client/schemas/filters.py +20 -40
- prefect/client/schemas/objects.py +147 -145
- prefect/client/schemas/responses.py +16 -24
- prefect/client/schemas/schedules.py +47 -35
- prefect/client/subscriptions.py +2 -2
- prefect/client/utilities.py +5 -2
- prefect/concurrency/asyncio.py +3 -1
- prefect/concurrency/events.py +1 -1
- prefect/concurrency/services.py +6 -3
- prefect/context.py +195 -27
- prefect/deployments/__init__.py +5 -6
- prefect/deployments/base.py +7 -5
- prefect/deployments/flow_runs.py +185 -0
- prefect/deployments/runner.py +50 -45
- prefect/deployments/schedules.py +28 -23
- prefect/deployments/steps/__init__.py +0 -1
- prefect/deployments/steps/core.py +1 -0
- prefect/deployments/steps/pull.py +7 -21
- prefect/engine.py +12 -2422
- prefect/events/actions.py +17 -23
- prefect/events/cli/automations.py +19 -6
- prefect/events/clients.py +14 -37
- prefect/events/filters.py +14 -18
- prefect/events/related.py +2 -2
- prefect/events/schemas/__init__.py +0 -5
- prefect/events/schemas/automations.py +55 -46
- prefect/events/schemas/deployment_triggers.py +7 -197
- prefect/events/schemas/events.py +34 -65
- prefect/events/schemas/labelling.py +10 -14
- prefect/events/utilities.py +2 -3
- prefect/events/worker.py +2 -3
- prefect/filesystems.py +6 -517
- prefect/{new_flow_engine.py → flow_engine.py} +313 -72
- prefect/flow_runs.py +377 -5
- prefect/flows.py +248 -165
- prefect/futures.py +186 -345
- prefect/infrastructure/__init__.py +0 -27
- prefect/infrastructure/provisioners/__init__.py +5 -3
- prefect/infrastructure/provisioners/cloud_run.py +11 -6
- prefect/infrastructure/provisioners/container_instance.py +11 -7
- prefect/infrastructure/provisioners/ecs.py +6 -4
- prefect/infrastructure/provisioners/modal.py +8 -5
- prefect/input/actions.py +2 -4
- prefect/input/run_input.py +5 -7
- prefect/logging/formatters.py +0 -2
- prefect/logging/handlers.py +3 -11
- prefect/logging/loggers.py +2 -2
- prefect/manifests.py +2 -1
- prefect/records/__init__.py +1 -0
- prefect/records/result_store.py +42 -0
- prefect/records/store.py +9 -0
- prefect/results.py +43 -39
- prefect/runner/runner.py +9 -9
- prefect/runner/server.py +6 -10
- prefect/runner/storage.py +3 -8
- prefect/runner/submit.py +2 -2
- prefect/runner/utils.py +2 -2
- prefect/serializers.py +24 -35
- prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
- prefect/settings.py +70 -133
- prefect/states.py +17 -47
- prefect/task_engine.py +697 -58
- prefect/task_runners.py +269 -301
- prefect/task_server.py +53 -34
- prefect/tasks.py +327 -337
- prefect/transactions.py +220 -0
- prefect/types/__init__.py +61 -82
- prefect/utilities/asyncutils.py +195 -136
- prefect/utilities/callables.py +121 -41
- prefect/utilities/collections.py +23 -38
- prefect/utilities/dispatch.py +11 -3
- prefect/utilities/dockerutils.py +4 -0
- prefect/utilities/engine.py +140 -20
- prefect/utilities/importtools.py +26 -27
- prefect/utilities/pydantic.py +128 -38
- prefect/utilities/schema_tools/hydration.py +5 -1
- prefect/utilities/templating.py +12 -2
- prefect/variables.py +78 -61
- prefect/workers/__init__.py +0 -1
- prefect/workers/base.py +15 -17
- prefect/workers/process.py +3 -8
- prefect/workers/server.py +2 -2
- {prefect_client-2.19.3.dist-info → prefect_client-3.0.0rc1.dist-info}/METADATA +22 -21
- prefect_client-3.0.0rc1.dist-info/RECORD +176 -0
- prefect/_internal/pydantic/_base_model.py +0 -51
- prefect/_internal/pydantic/_compat.py +0 -82
- prefect/_internal/pydantic/_flags.py +0 -20
- prefect/_internal/pydantic/_types.py +0 -8
- prefect/_internal/pydantic/utilities/__init__.py +0 -0
- prefect/_internal/pydantic/utilities/config_dict.py +0 -72
- prefect/_internal/pydantic/utilities/field_validator.py +0 -150
- prefect/_internal/pydantic/utilities/model_construct.py +0 -56
- prefect/_internal/pydantic/utilities/model_copy.py +0 -55
- prefect/_internal/pydantic/utilities/model_dump.py +0 -136
- prefect/_internal/pydantic/utilities/model_dump_json.py +0 -112
- prefect/_internal/pydantic/utilities/model_fields.py +0 -50
- prefect/_internal/pydantic/utilities/model_fields_set.py +0 -29
- prefect/_internal/pydantic/utilities/model_json_schema.py +0 -82
- prefect/_internal/pydantic/utilities/model_rebuild.py +0 -80
- prefect/_internal/pydantic/utilities/model_validate.py +0 -75
- prefect/_internal/pydantic/utilities/model_validate_json.py +0 -68
- prefect/_internal/pydantic/utilities/model_validator.py +0 -87
- prefect/_internal/pydantic/utilities/type_adapter.py +0 -71
- prefect/_vendor/__init__.py +0 -0
- prefect/_vendor/fastapi/__init__.py +0 -25
- prefect/_vendor/fastapi/applications.py +0 -946
- prefect/_vendor/fastapi/background.py +0 -3
- prefect/_vendor/fastapi/concurrency.py +0 -44
- prefect/_vendor/fastapi/datastructures.py +0 -58
- prefect/_vendor/fastapi/dependencies/__init__.py +0 -0
- prefect/_vendor/fastapi/dependencies/models.py +0 -64
- prefect/_vendor/fastapi/dependencies/utils.py +0 -877
- prefect/_vendor/fastapi/encoders.py +0 -177
- prefect/_vendor/fastapi/exception_handlers.py +0 -40
- prefect/_vendor/fastapi/exceptions.py +0 -46
- prefect/_vendor/fastapi/logger.py +0 -3
- prefect/_vendor/fastapi/middleware/__init__.py +0 -1
- prefect/_vendor/fastapi/middleware/asyncexitstack.py +0 -25
- prefect/_vendor/fastapi/middleware/cors.py +0 -3
- prefect/_vendor/fastapi/middleware/gzip.py +0 -3
- prefect/_vendor/fastapi/middleware/httpsredirect.py +0 -3
- prefect/_vendor/fastapi/middleware/trustedhost.py +0 -3
- prefect/_vendor/fastapi/middleware/wsgi.py +0 -3
- prefect/_vendor/fastapi/openapi/__init__.py +0 -0
- prefect/_vendor/fastapi/openapi/constants.py +0 -2
- prefect/_vendor/fastapi/openapi/docs.py +0 -203
- prefect/_vendor/fastapi/openapi/models.py +0 -480
- prefect/_vendor/fastapi/openapi/utils.py +0 -485
- prefect/_vendor/fastapi/param_functions.py +0 -340
- prefect/_vendor/fastapi/params.py +0 -453
- prefect/_vendor/fastapi/requests.py +0 -4
- prefect/_vendor/fastapi/responses.py +0 -40
- prefect/_vendor/fastapi/routing.py +0 -1331
- prefect/_vendor/fastapi/security/__init__.py +0 -15
- prefect/_vendor/fastapi/security/api_key.py +0 -98
- prefect/_vendor/fastapi/security/base.py +0 -6
- prefect/_vendor/fastapi/security/http.py +0 -172
- prefect/_vendor/fastapi/security/oauth2.py +0 -227
- prefect/_vendor/fastapi/security/open_id_connect_url.py +0 -34
- prefect/_vendor/fastapi/security/utils.py +0 -10
- prefect/_vendor/fastapi/staticfiles.py +0 -1
- prefect/_vendor/fastapi/templating.py +0 -3
- prefect/_vendor/fastapi/testclient.py +0 -1
- prefect/_vendor/fastapi/types.py +0 -3
- prefect/_vendor/fastapi/utils.py +0 -235
- prefect/_vendor/fastapi/websockets.py +0 -7
- prefect/_vendor/starlette/__init__.py +0 -1
- prefect/_vendor/starlette/_compat.py +0 -28
- prefect/_vendor/starlette/_exception_handler.py +0 -80
- prefect/_vendor/starlette/_utils.py +0 -88
- prefect/_vendor/starlette/applications.py +0 -261
- prefect/_vendor/starlette/authentication.py +0 -159
- prefect/_vendor/starlette/background.py +0 -43
- prefect/_vendor/starlette/concurrency.py +0 -59
- prefect/_vendor/starlette/config.py +0 -151
- prefect/_vendor/starlette/convertors.py +0 -87
- prefect/_vendor/starlette/datastructures.py +0 -707
- prefect/_vendor/starlette/endpoints.py +0 -130
- prefect/_vendor/starlette/exceptions.py +0 -60
- prefect/_vendor/starlette/formparsers.py +0 -276
- prefect/_vendor/starlette/middleware/__init__.py +0 -17
- prefect/_vendor/starlette/middleware/authentication.py +0 -52
- prefect/_vendor/starlette/middleware/base.py +0 -220
- prefect/_vendor/starlette/middleware/cors.py +0 -176
- prefect/_vendor/starlette/middleware/errors.py +0 -265
- prefect/_vendor/starlette/middleware/exceptions.py +0 -74
- prefect/_vendor/starlette/middleware/gzip.py +0 -113
- prefect/_vendor/starlette/middleware/httpsredirect.py +0 -19
- prefect/_vendor/starlette/middleware/sessions.py +0 -82
- prefect/_vendor/starlette/middleware/trustedhost.py +0 -64
- prefect/_vendor/starlette/middleware/wsgi.py +0 -147
- prefect/_vendor/starlette/requests.py +0 -328
- prefect/_vendor/starlette/responses.py +0 -347
- prefect/_vendor/starlette/routing.py +0 -933
- prefect/_vendor/starlette/schemas.py +0 -154
- prefect/_vendor/starlette/staticfiles.py +0 -248
- prefect/_vendor/starlette/status.py +0 -199
- prefect/_vendor/starlette/templating.py +0 -231
- prefect/_vendor/starlette/testclient.py +0 -804
- prefect/_vendor/starlette/types.py +0 -30
- prefect/_vendor/starlette/websockets.py +0 -193
- prefect/agent.py +0 -698
- prefect/deployments/deployments.py +0 -1042
- prefect/deprecated/__init__.py +0 -0
- prefect/deprecated/data_documents.py +0 -350
- prefect/deprecated/packaging/__init__.py +0 -12
- prefect/deprecated/packaging/base.py +0 -96
- prefect/deprecated/packaging/docker.py +0 -146
- prefect/deprecated/packaging/file.py +0 -92
- prefect/deprecated/packaging/orion.py +0 -80
- prefect/deprecated/packaging/serializers.py +0 -171
- prefect/events/instrument.py +0 -135
- prefect/infrastructure/base.py +0 -323
- prefect/infrastructure/container.py +0 -818
- prefect/infrastructure/kubernetes.py +0 -920
- prefect/infrastructure/process.py +0 -289
- prefect/new_task_engine.py +0 -423
- prefect/pydantic/__init__.py +0 -76
- prefect/pydantic/main.py +0 -39
- prefect/software/__init__.py +0 -2
- prefect/software/base.py +0 -50
- prefect/software/conda.py +0 -199
- prefect/software/pip.py +0 -122
- prefect/software/python.py +0 -52
- prefect/workers/block.py +0 -218
- prefect_client-2.19.3.dist-info/RECORD +0 -292
- {prefect_client-2.19.3.dist-info → prefect_client-3.0.0rc1.dist-info}/LICENSE +0 -0
- {prefect_client-2.19.3.dist-info → prefect_client-3.0.0rc1.dist-info}/WHEEL +0 -0
- {prefect_client-2.19.3.dist-info → prefect_client-3.0.0rc1.dist-info}/top_level.txt +0 -0
@@ -6,11 +6,9 @@ format.
|
|
6
6
|
This will be subject to consolidation and refactoring over the next few months.
|
7
7
|
"""
|
8
8
|
|
9
|
-
import datetime
|
10
9
|
import json
|
11
10
|
import logging
|
12
11
|
import re
|
13
|
-
import sys
|
14
12
|
import urllib.parse
|
15
13
|
import warnings
|
16
14
|
from copy import copy
|
@@ -20,48 +18,24 @@ from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Tuple, Uni
|
|
20
18
|
import jsonschema
|
21
19
|
import pendulum
|
22
20
|
import yaml
|
21
|
+
from pydantic_extra_types.pendulum_dt import DateTime
|
23
22
|
|
24
|
-
from prefect.
|
25
|
-
from prefect._internal.pydantic._flags import USE_PYDANTIC_V2
|
26
|
-
from prefect._internal.schemas.fields import DateTimeTZ
|
27
|
-
from prefect.exceptions import InvalidNameError, InvalidRepositoryURLError
|
23
|
+
from prefect.exceptions import InvalidRepositoryURLError
|
28
24
|
from prefect.utilities.annotations import NotSet
|
25
|
+
from prefect.utilities.collections import isiterable
|
29
26
|
from prefect.utilities.dockerutils import get_prefect_image_name
|
30
27
|
from prefect.utilities.filesystem import relative_path_to_current_platform
|
31
28
|
from prefect.utilities.importtools import from_qualified_name
|
32
29
|
from prefect.utilities.names import generate_slug
|
33
30
|
from prefect.utilities.pydantic import JsonPatch
|
34
31
|
|
35
|
-
BANNED_CHARACTERS = ["/", "%", "&", ">", "<"]
|
36
32
|
LOWERCASE_LETTERS_NUMBERS_AND_DASHES_ONLY_REGEX = "^[a-z0-9-]*$"
|
37
33
|
LOWERCASE_LETTERS_NUMBERS_AND_UNDERSCORES_REGEX = "^[a-z0-9_]*$"
|
38
34
|
|
39
35
|
if TYPE_CHECKING:
|
40
36
|
from prefect.blocks.core import Block
|
41
|
-
from prefect.events.schemas import DeploymentTrigger
|
42
37
|
from prefect.utilities.callables import ParameterSchema
|
43
38
|
|
44
|
-
if HAS_PYDANTIC_V2:
|
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
|
50
|
-
|
51
|
-
|
52
|
-
def raise_on_name_with_banned_characters(name: str) -> str:
|
53
|
-
"""
|
54
|
-
Raise an InvalidNameError if the given name contains any invalid
|
55
|
-
characters.
|
56
|
-
"""
|
57
|
-
if name is not None:
|
58
|
-
if any(c in name for c in BANNED_CHARACTERS):
|
59
|
-
raise InvalidNameError(
|
60
|
-
f"Name {name!r} contains an invalid character. "
|
61
|
-
f"Must not contain any of: {BANNED_CHARACTERS}."
|
62
|
-
)
|
63
|
-
return name
|
64
|
-
|
65
39
|
|
66
40
|
def raise_on_name_alphanumeric_dashes_only(
|
67
41
|
value: Optional[str], field_name: str = "value"
|
@@ -236,6 +210,12 @@ def return_none_schedule(v: Optional[Union[str, dict]]) -> Optional[Union[str, d
|
|
236
210
|
return v
|
237
211
|
|
238
212
|
|
213
|
+
def convert_to_strings(value: Union[Any, List[Any]]) -> Union[str, List[str]]:
|
214
|
+
if isiterable(value):
|
215
|
+
return [str(item) for item in value]
|
216
|
+
return str(value)
|
217
|
+
|
218
|
+
|
239
219
|
### SCHEDULE SCHEMA VALIDATORS ###
|
240
220
|
|
241
221
|
|
@@ -264,15 +244,15 @@ def reconcile_schedules(cls, values: dict) -> dict:
|
|
264
244
|
"""
|
265
245
|
|
266
246
|
from prefect.deployments.schedules import (
|
267
|
-
|
268
|
-
|
247
|
+
create_deployment_schedule_create,
|
248
|
+
normalize_to_deployment_schedule_create,
|
269
249
|
)
|
270
250
|
|
271
251
|
schedule = values.get("schedule", NotSet)
|
272
252
|
schedules = values.get("schedules", NotSet)
|
273
253
|
|
274
254
|
if schedules is not NotSet:
|
275
|
-
values["schedules"] =
|
255
|
+
values["schedules"] = normalize_to_deployment_schedule_create(schedules)
|
276
256
|
elif schedule is not NotSet:
|
277
257
|
values["schedule"] = None
|
278
258
|
|
@@ -280,7 +260,7 @@ def reconcile_schedules(cls, values: dict) -> dict:
|
|
280
260
|
values["schedules"] = []
|
281
261
|
else:
|
282
262
|
values["schedules"] = [
|
283
|
-
|
263
|
+
create_deployment_schedule_create(
|
284
264
|
schedule=schedule, active=values.get("is_schedule_active")
|
285
265
|
)
|
286
266
|
]
|
@@ -297,17 +277,17 @@ def reconcile_schedules_runner(values: dict) -> dict:
|
|
297
277
|
Similar to above, we reconcile the `schedule` and `schedules` fields in a deployment.
|
298
278
|
"""
|
299
279
|
from prefect.deployments.schedules import (
|
300
|
-
|
301
|
-
|
280
|
+
create_deployment_schedule_create,
|
281
|
+
normalize_to_deployment_schedule_create,
|
302
282
|
)
|
303
283
|
|
304
284
|
schedule = values.get("schedule")
|
305
285
|
schedules = values.get("schedules")
|
306
286
|
|
307
287
|
if schedules is None and schedule is not None:
|
308
|
-
values["schedules"] = [
|
288
|
+
values["schedules"] = [create_deployment_schedule_create(schedule)]
|
309
289
|
elif schedules is not None and len(schedules) > 0:
|
310
|
-
values["schedules"] =
|
290
|
+
values["schedules"] = normalize_to_deployment_schedule_create(schedules)
|
311
291
|
|
312
292
|
return values
|
313
293
|
|
@@ -316,12 +296,15 @@ def set_deployment_schedules(values: dict) -> dict:
|
|
316
296
|
from prefect.server.schemas.actions import DeploymentScheduleCreate
|
317
297
|
|
318
298
|
if not values.get("schedules") and values.get("schedule"):
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
299
|
+
kwargs = {
|
300
|
+
key: values[key]
|
301
|
+
for key in ["schedule", "is_schedule_active"]
|
302
|
+
if key in values
|
303
|
+
}
|
304
|
+
if "is_schedule_active" in kwargs:
|
305
|
+
kwargs["active"] = kwargs.pop("is_schedule_active")
|
306
|
+
|
307
|
+
values["schedules"] = [DeploymentScheduleCreate(**kwargs)]
|
325
308
|
|
326
309
|
return values
|
327
310
|
|
@@ -382,13 +365,11 @@ def reconcile_paused_deployment(values):
|
|
382
365
|
return values
|
383
366
|
|
384
367
|
|
385
|
-
def default_anchor_date(v:
|
386
|
-
if v is None:
|
387
|
-
return pendulum.now("UTC")
|
368
|
+
def default_anchor_date(v: DateTime) -> DateTime:
|
388
369
|
return pendulum.instance(v)
|
389
370
|
|
390
371
|
|
391
|
-
def get_valid_timezones(v: str) -> Tuple[str, ...]:
|
372
|
+
def get_valid_timezones(v: Optional[str]) -> Tuple[str, ...]:
|
392
373
|
# pendulum.tz.timezones is a callable in 3.0 and above
|
393
374
|
# https://github.com/PrefectHQ/prefect/issues/11619
|
394
375
|
if callable(pendulum.tz.timezones):
|
@@ -397,27 +378,6 @@ def get_valid_timezones(v: str) -> Tuple[str, ...]:
|
|
397
378
|
return pendulum.tz.timezones
|
398
379
|
|
399
380
|
|
400
|
-
def validate_rrule_timezone(v: str) -> str:
|
401
|
-
"""
|
402
|
-
Validate that the provided timezone is a valid IANA timezone.
|
403
|
-
|
404
|
-
Unfortunately this list is slightly different from the list of valid
|
405
|
-
timezones in pendulum that we use for cron and interval timezone validation.
|
406
|
-
"""
|
407
|
-
from prefect._internal.pytz import HAS_PYTZ
|
408
|
-
|
409
|
-
if HAS_PYTZ:
|
410
|
-
import pytz
|
411
|
-
else:
|
412
|
-
from prefect._internal import pytz
|
413
|
-
|
414
|
-
if v and v not in pytz.all_timezones_set:
|
415
|
-
raise ValueError(f'Invalid timezone: "{v}"')
|
416
|
-
elif v is None:
|
417
|
-
return "UTC"
|
418
|
-
return v
|
419
|
-
|
420
|
-
|
421
381
|
def validate_timezone(v: str, timezones: Tuple[str, ...]) -> str:
|
422
382
|
if v and v not in timezones:
|
423
383
|
raise ValueError(
|
@@ -427,7 +387,8 @@ def validate_timezone(v: str, timezones: Tuple[str, ...]) -> str:
|
|
427
387
|
return v
|
428
388
|
|
429
389
|
|
430
|
-
def default_timezone(v: str, values: Optional[dict] =
|
390
|
+
def default_timezone(v: Optional[str], values: Optional[dict] = None) -> str:
|
391
|
+
values = values or {}
|
431
392
|
timezones = get_valid_timezones(v)
|
432
393
|
|
433
394
|
if v is not None:
|
@@ -435,7 +396,7 @@ def default_timezone(v: str, values: Optional[dict] = {}) -> str:
|
|
435
396
|
|
436
397
|
# anchor schedules
|
437
398
|
elif v is None and values and values.get("anchor_date"):
|
438
|
-
tz = values["anchor_date"].tz
|
399
|
+
tz = getattr(values["anchor_date"].tz, "name", None) or "UTC"
|
439
400
|
if tz in timezones:
|
440
401
|
return tz
|
441
402
|
# sometimes anchor dates have "timezones" that are UTC offsets
|
@@ -485,34 +446,6 @@ def validate_rrule_string(v: str) -> str:
|
|
485
446
|
return v
|
486
447
|
|
487
448
|
|
488
|
-
### AUTOMATION SCHEMA VALIDATORS ###
|
489
|
-
|
490
|
-
|
491
|
-
def validate_trigger_within(
|
492
|
-
value: datetime.timedelta, field: "ModelField"
|
493
|
-
) -> datetime.timedelta:
|
494
|
-
"""
|
495
|
-
Validate that the `within` field is greater than the minimum value.
|
496
|
-
"""
|
497
|
-
minimum = field.field_info.extra["minimum"]
|
498
|
-
if value.total_seconds() < minimum:
|
499
|
-
raise ValueError("The minimum `within` is 0 seconds")
|
500
|
-
return value
|
501
|
-
|
502
|
-
|
503
|
-
def validate_automation_names(
|
504
|
-
field_value: List["DeploymentTrigger"], values: dict
|
505
|
-
) -> List["DeploymentTrigger"]:
|
506
|
-
"""
|
507
|
-
Ensure that each trigger has a name for its automation if none is provided.
|
508
|
-
"""
|
509
|
-
for i, trigger in enumerate(field_value, start=1):
|
510
|
-
if trigger.name is None:
|
511
|
-
trigger.name = f"{values['name']}__automation_{i}"
|
512
|
-
|
513
|
-
return field_value
|
514
|
-
|
515
|
-
|
516
449
|
### INFRASTRUCTURE SCHEMA VALIDATORS ###
|
517
450
|
|
518
451
|
|
@@ -589,7 +522,6 @@ def set_default_image(values: dict) -> dict:
|
|
589
522
|
"""
|
590
523
|
Set the default image for a Kubernetes job if not provided.
|
591
524
|
"""
|
592
|
-
from prefect.utilities.dockerutils import get_prefect_image_name
|
593
525
|
|
594
526
|
job = values.get("job")
|
595
527
|
image = values.get("image")
|
@@ -616,23 +548,6 @@ def get_or_create_state_name(v: str, values: dict) -> str:
|
|
616
548
|
return v
|
617
549
|
|
618
550
|
|
619
|
-
def set_default_scheduled_time(cls, values: dict) -> dict:
|
620
|
-
"""
|
621
|
-
TODO: This should throw an error instead of setting a default but is out of
|
622
|
-
scope for https://github.com/PrefectHQ/orion/pull/174/ and can be rolled
|
623
|
-
into work refactoring state initialization
|
624
|
-
"""
|
625
|
-
from prefect.server.schemas.states import StateType
|
626
|
-
|
627
|
-
if values.get("type") == StateType.SCHEDULED:
|
628
|
-
state_details = values.setdefault(
|
629
|
-
"state_details", cls.__fields__["state_details"].get_default()
|
630
|
-
)
|
631
|
-
if not state_details.scheduled_time:
|
632
|
-
state_details.scheduled_time = pendulum.now("utc")
|
633
|
-
return values
|
634
|
-
|
635
|
-
|
636
551
|
def get_or_create_run_name(name):
|
637
552
|
return name or generate_slug(2)
|
638
553
|
|
@@ -882,20 +797,6 @@ def check_volume_format(volumes: List[str]) -> List[str]:
|
|
882
797
|
return volumes
|
883
798
|
|
884
799
|
|
885
|
-
def assign_default_base_image(values: Mapping[str, Any]) -> Mapping[str, Any]:
|
886
|
-
from prefect.software.conda import CondaEnvironment
|
887
|
-
|
888
|
-
if not values.get("base_image") and not values.get("dockerfile"):
|
889
|
-
values["base_image"] = get_prefect_image_name(
|
890
|
-
flavor=(
|
891
|
-
"conda"
|
892
|
-
if isinstance(values.get("python_environment"), CondaEnvironment)
|
893
|
-
else None
|
894
|
-
)
|
895
|
-
)
|
896
|
-
return values
|
897
|
-
|
898
|
-
|
899
800
|
def base_image_xor_dockerfile(values: Mapping[str, Any]):
|
900
801
|
if values.get("base_image") and values.get("dockerfile"):
|
901
802
|
raise ValueError(
|
@@ -904,14 +805,6 @@ def base_image_xor_dockerfile(values: Mapping[str, Any]):
|
|
904
805
|
return values
|
905
806
|
|
906
807
|
|
907
|
-
def set_default_python_environment(values: Mapping[str, Any]) -> Mapping[str, Any]:
|
908
|
-
from prefect.software.python import PythonEnvironment
|
909
|
-
|
910
|
-
if values.get("base_image") and not values.get("python_environment"):
|
911
|
-
values["python_environment"] = PythonEnvironment.from_environment()
|
912
|
-
return values
|
913
|
-
|
914
|
-
|
915
808
|
### SETTINGS SCHEMA VALIDATORS ###
|
916
809
|
|
917
810
|
|
@@ -929,7 +822,7 @@ def validate_settings(value: dict) -> dict:
|
|
929
822
|
elif isinstance(setting, Setting):
|
930
823
|
validated[setting] = val
|
931
824
|
else:
|
932
|
-
|
825
|
+
warnings.warn(f"Setting {setting!r} is not recognized and will be ignored.")
|
933
826
|
|
934
827
|
return validated
|
935
828
|
|
@@ -976,12 +869,6 @@ def set_run_policy_deprecated_fields(values: dict) -> dict:
|
|
976
869
|
### PYTHON ENVIRONMENT SCHEMA VALIDATORS ###
|
977
870
|
|
978
871
|
|
979
|
-
def infer_python_version(value: Optional[str]) -> Optional[str]:
|
980
|
-
if value is None:
|
981
|
-
return f"{sys.version_info.major}.{sys.version_info.minor}"
|
982
|
-
return value
|
983
|
-
|
984
|
-
|
985
872
|
def return_v_or_none(v: Optional[str]) -> Optional[str]:
|
986
873
|
"""Make sure that empty strings are treated as None"""
|
987
874
|
if not v:
|
@@ -989,19 +876,6 @@ def return_v_or_none(v: Optional[str]) -> Optional[str]:
|
|
989
876
|
return v
|
990
877
|
|
991
878
|
|
992
|
-
### INFRASTRUCTURE BLOCK SCHEMA VALIDATORS ###
|
993
|
-
|
994
|
-
|
995
|
-
def validate_block_is_infrastructure(v: "Block") -> "Block":
|
996
|
-
from prefect.infrastructure.base import Infrastructure
|
997
|
-
|
998
|
-
print("v: ", v)
|
999
|
-
if not isinstance(v, Infrastructure):
|
1000
|
-
raise TypeError("Provided block is not a valid infrastructure block.")
|
1001
|
-
|
1002
|
-
return v
|
1003
|
-
|
1004
|
-
|
1005
879
|
### BLOCK SCHEMA VALIDATORS ###
|
1006
880
|
|
1007
881
|
|
@@ -1032,3 +906,31 @@ def validate_command(v: str) -> Path:
|
|
1032
906
|
if v:
|
1033
907
|
return relative_path_to_current_platform(v)
|
1034
908
|
return v
|
909
|
+
|
910
|
+
|
911
|
+
### UNCATEGORIZED VALIDATORS ###
|
912
|
+
|
913
|
+
# the above categories seem to be getting a bit unwieldy, so this is a temporary
|
914
|
+
# catch-all for validators until we organize these into files
|
915
|
+
|
916
|
+
|
917
|
+
def validate_block_document_name(value):
|
918
|
+
if value is not None:
|
919
|
+
raise_on_name_alphanumeric_dashes_only(value, field_name="Block document name")
|
920
|
+
return value
|
921
|
+
|
922
|
+
|
923
|
+
def validate_artifact_key(value):
|
924
|
+
raise_on_name_alphanumeric_dashes_only(value, field_name="Artifact key")
|
925
|
+
return value
|
926
|
+
|
927
|
+
|
928
|
+
def validate_variable_name(value):
|
929
|
+
if value is not None:
|
930
|
+
raise_on_name_alphanumeric_underscores_only(value, field_name="Variable name")
|
931
|
+
return value
|
932
|
+
|
933
|
+
|
934
|
+
def validate_block_type_slug(value):
|
935
|
+
raise_on_name_alphanumeric_dashes_only(value, field_name="Block type slug")
|
936
|
+
return value
|
prefect/artifacts.py
CHANGED
@@ -6,20 +6,26 @@ from __future__ import annotations
|
|
6
6
|
|
7
7
|
import json # noqa: I001
|
8
8
|
import math
|
9
|
-
from typing import Any, Dict, List, Optional, Tuple, Union
|
9
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
|
10
10
|
from uuid import UUID
|
11
11
|
|
12
|
-
from typing_extensions import Self
|
13
|
-
|
14
|
-
from prefect.client.orchestration import PrefectClient
|
15
12
|
from prefect.client.schemas.actions import ArtifactCreate as ArtifactRequest
|
13
|
+
from prefect.client.schemas.actions import ArtifactUpdate
|
16
14
|
from prefect.client.schemas.filters import ArtifactFilter, ArtifactFilterKey
|
17
|
-
from prefect.client.schemas.objects import Artifact as ArtifactResponse
|
18
15
|
from prefect.client.schemas.sorting import ArtifactSort
|
19
16
|
from prefect.client.utilities import get_or_create_client, inject_client
|
17
|
+
from prefect.logging.loggers import get_logger
|
20
18
|
from prefect.utilities.asyncutils import sync_compatible
|
21
19
|
from prefect.utilities.context import get_task_and_flow_run_ids
|
22
20
|
|
21
|
+
logger = get_logger("artifacts")
|
22
|
+
|
23
|
+
if TYPE_CHECKING:
|
24
|
+
from typing_extensions import Self
|
25
|
+
|
26
|
+
from prefect.client.orchestration import PrefectClient
|
27
|
+
from prefect.client.schemas.objects import Artifact as ArtifactResponse
|
28
|
+
|
23
29
|
|
24
30
|
class Artifact(ArtifactRequest):
|
25
31
|
"""
|
@@ -36,9 +42,9 @@ class Artifact(ArtifactRequest):
|
|
36
42
|
|
37
43
|
@sync_compatible
|
38
44
|
async def create(
|
39
|
-
self: Self,
|
40
|
-
client: Optional[PrefectClient] = None,
|
41
|
-
) -> ArtifactResponse:
|
45
|
+
self: "Self",
|
46
|
+
client: Optional["PrefectClient"] = None,
|
47
|
+
) -> "ArtifactResponse":
|
42
48
|
"""
|
43
49
|
A method to create an artifact.
|
44
50
|
|
@@ -64,8 +70,8 @@ class Artifact(ArtifactRequest):
|
|
64
70
|
@classmethod
|
65
71
|
@sync_compatible
|
66
72
|
async def get(
|
67
|
-
cls, key: Optional[str] = None, client: Optional[PrefectClient] = None
|
68
|
-
) -> Optional[ArtifactResponse]:
|
73
|
+
cls, key: Optional[str] = None, client: Optional["PrefectClient"] = None
|
74
|
+
) -> Optional["ArtifactResponse"]:
|
69
75
|
"""
|
70
76
|
A method to get an artifact.
|
71
77
|
|
@@ -95,9 +101,9 @@ class Artifact(ArtifactRequest):
|
|
95
101
|
key: Optional[str] = None,
|
96
102
|
description: Optional[str] = None,
|
97
103
|
data: Optional[Union[Dict[str, Any], Any]] = None,
|
98
|
-
client: Optional[PrefectClient] = None,
|
104
|
+
client: Optional["PrefectClient"] = None,
|
99
105
|
**kwargs: Any,
|
100
|
-
) -> Tuple[ArtifactResponse, bool]:
|
106
|
+
) -> Tuple["ArtifactResponse", bool]:
|
101
107
|
"""
|
102
108
|
A method to get or create an artifact.
|
103
109
|
|
@@ -171,13 +177,54 @@ class TableArtifact(Artifact):
|
|
171
177
|
return json.dumps(self._sanitize(self.table))
|
172
178
|
|
173
179
|
|
180
|
+
class ProgressArtifact(Artifact):
|
181
|
+
progress: float
|
182
|
+
type: Optional[str] = "progress"
|
183
|
+
|
184
|
+
async def format(self) -> float:
|
185
|
+
# Ensure progress is between 0 and 100
|
186
|
+
min_progress = 0.0
|
187
|
+
max_progress = 100.0
|
188
|
+
if self.progress < min_progress or self.progress > max_progress:
|
189
|
+
logger.warning(
|
190
|
+
f"ProgressArtifact received an invalid value, Progress: {self.progress}%"
|
191
|
+
)
|
192
|
+
self.progress = max(min_progress, min(self.progress, max_progress))
|
193
|
+
logger.warning(f"Interpreting as {self.progress}% progress")
|
194
|
+
|
195
|
+
return self.progress
|
196
|
+
|
197
|
+
|
198
|
+
class ImageArtifact(Artifact):
|
199
|
+
"""
|
200
|
+
An artifact that will display an image from a publicly accessible URL in the UI.
|
201
|
+
|
202
|
+
Arguments:
|
203
|
+
image_url: The URL of the image to display.
|
204
|
+
"""
|
205
|
+
|
206
|
+
image_url: str
|
207
|
+
type: Optional[str] = "image"
|
208
|
+
|
209
|
+
async def format(self) -> str:
|
210
|
+
"""
|
211
|
+
This method is used to format the artifact data so it can be properly sent
|
212
|
+
to the API when the .create() method is called. It is async because the
|
213
|
+
method is awaited in the parent class.
|
214
|
+
|
215
|
+
Returns:
|
216
|
+
str: The image URL.
|
217
|
+
"""
|
218
|
+
return self.image_url
|
219
|
+
|
220
|
+
|
174
221
|
@inject_client
|
175
222
|
async def _create_artifact(
|
176
223
|
type: str,
|
177
224
|
key: Optional[str] = None,
|
178
225
|
description: Optional[str] = None,
|
179
226
|
data: Optional[Union[Dict[str, Any], Any]] = None,
|
180
|
-
client: Optional[PrefectClient] = None,
|
227
|
+
client: Optional["PrefectClient"] = None,
|
181
228
|
) -> UUID:
|
182
229
|
"""
|
183
230
|
Helper function to create an artifact.
|
@@ -210,7 +257,7 @@ async def create_link_artifact(
|
|
210
257
|
link_text: Optional[str] = None,
|
211
258
|
key: Optional[str] = None,
|
212
259
|
description: Optional[str] = None,
|
213
|
-
client: Optional[PrefectClient] = None,
|
260
|
+
client: Optional["PrefectClient"] = None,
|
214
261
|
) -> UUID:
|
215
262
|
"""
|
216
263
|
Create a link artifact.
|
@@ -292,3 +339,103 @@ async def create_table_artifact(
|
|
292
339
|
).create()
|
293
340
|
|
294
341
|
return artifact.id
|
342
|
+
|
343
|
+
|
344
|
+
@sync_compatible
|
345
|
+
async def create_progress_artifact(
|
346
|
+
progress: float,
|
347
|
+
key: Optional[str] = None,
|
348
|
+
description: Optional[str] = None,
|
349
|
+
) -> UUID:
|
350
|
+
"""
|
351
|
+
Create a progress artifact.
|
352
|
+
|
353
|
+
Arguments:
|
354
|
+
progress: The percentage of progress represented by a float between 0 and 100.
|
355
|
+
key: A user-provided string identifier.
|
356
|
+
Required for the artifact to show in the Artifacts page in the UI.
|
357
|
+
The key must only contain lowercase letters, numbers, and dashes.
|
358
|
+
description: A user-specified description of the artifact.
|
359
|
+
|
360
|
+
Returns:
|
361
|
+
The progress artifact ID.
|
362
|
+
"""
|
363
|
+
|
364
|
+
artifact = await ProgressArtifact(
|
365
|
+
key=key,
|
366
|
+
description=description,
|
367
|
+
progress=progress,
|
368
|
+
).create()
|
369
|
+
|
370
|
+
return artifact.id
|
371
|
+
|
372
|
+
|
373
|
+
@sync_compatible
|
374
|
+
async def update_progress_artifact(
|
375
|
+
artifact_id: UUID,
|
376
|
+
progress: float,
|
377
|
+
description: Optional[str] = None,
|
378
|
+
client: Optional[PrefectClient] = None,
|
379
|
+
) -> UUID:
|
380
|
+
"""
|
381
|
+
Update a progress artifact.
|
382
|
+
|
383
|
+
Arguments:
|
384
|
+
artifact_id: The ID of the artifact to update.
|
385
|
+
progress: The percentage of progress represented by a float between 0 and 100.
|
386
|
+
description: A user-specified description of the artifact.
|
387
|
+
|
388
|
+
Returns:
|
389
|
+
The progress artifact ID.
|
390
|
+
"""
|
391
|
+
|
392
|
+
client, _ = get_or_create_client(client)
|
393
|
+
|
394
|
+
artifact = ProgressArtifact(
|
395
|
+
description=description,
|
396
|
+
progress=progress,
|
397
|
+
)
|
398
|
+
update = (
|
399
|
+
ArtifactUpdate(
|
400
|
+
description=artifact.description,
|
401
|
+
data=await artifact.format(),
|
402
|
+
)
|
403
|
+
if description
|
404
|
+
else ArtifactUpdate(data=await artifact.format())
|
405
|
+
)
|
406
|
+
|
407
|
+
await client.update_artifact(
|
408
|
+
artifact_id=artifact_id,
|
409
|
+
artifact=update,
|
410
|
+
)
|
411
|
+
|
412
|
+
return artifact_id
|
413
|
+
|
414
|
+
|
415
|
+
@sync_compatible
|
416
|
+
async def create_image_artifact(
|
417
|
+
image_url: str,
|
418
|
+
key: Optional[str] = None,
|
419
|
+
description: Optional[str] = None,
|
420
|
+
) -> UUID:
|
421
|
+
"""
|
422
|
+
Create an image artifact.
|
423
|
+
|
424
|
+
Arguments:
|
425
|
+
image_url: The URL of the image to display.
|
426
|
+
key: A user-provided string identifier.
|
427
|
+
Required for the artifact to show in the Artifacts page in the UI.
|
428
|
+
The key must only contain lowercase letters, numbers, and dashes.
|
429
|
+
description: A user-specified description of the artifact.
|
430
|
+
|
431
|
+
Returns:
|
432
|
+
The image artifact ID.
|
433
|
+
"""
|
434
|
+
|
435
|
+
artifact = await ImageArtifact(
|
436
|
+
key=key,
|
437
|
+
description=description,
|
438
|
+
image_url=image_url,
|
439
|
+
).create()
|
440
|
+
|
441
|
+
return artifact.id
|
prefect/automations.py
CHANGED
@@ -5,6 +5,24 @@ from pydantic import Field
|
|
5
5
|
from typing_extensions import Self
|
6
6
|
|
7
7
|
from prefect.client.utilities import get_or_create_client
|
8
|
+
from prefect.events.actions import (
|
9
|
+
CallWebhook,
|
10
|
+
CancelFlowRun,
|
11
|
+
ChangeFlowRunState,
|
12
|
+
DeclareIncident,
|
13
|
+
DoNothing,
|
14
|
+
PauseAutomation,
|
15
|
+
PauseDeployment,
|
16
|
+
PauseWorkPool,
|
17
|
+
PauseWorkQueue,
|
18
|
+
ResumeAutomation,
|
19
|
+
ResumeDeployment,
|
20
|
+
ResumeWorkPool,
|
21
|
+
ResumeWorkQueue,
|
22
|
+
RunDeployment,
|
23
|
+
SendNotification,
|
24
|
+
SuspendFlowRun,
|
25
|
+
)
|
8
26
|
from prefect.events.schemas.automations import (
|
9
27
|
AutomationCore,
|
10
28
|
CompositeTrigger,
|
@@ -37,6 +55,23 @@ __all__ = [
|
|
37
55
|
"SequenceTrigger",
|
38
56
|
"CompoundTrigger",
|
39
57
|
"MetricTriggerQuery",
|
58
|
+
# action types
|
59
|
+
"DoNothing",
|
60
|
+
"RunDeployment",
|
61
|
+
"PauseDeployment",
|
62
|
+
"ResumeDeployment",
|
63
|
+
"CancelFlowRun",
|
64
|
+
"ChangeFlowRunState",
|
65
|
+
"PauseWorkQueue",
|
66
|
+
"ResumeWorkQueue",
|
67
|
+
"SendNotification",
|
68
|
+
"CallWebhook",
|
69
|
+
"PauseAutomation",
|
70
|
+
"ResumeAutomation",
|
71
|
+
"SuspendFlowRun",
|
72
|
+
"PauseWorkPool",
|
73
|
+
"ResumeWorkPool",
|
74
|
+
"DeclareIncident",
|
40
75
|
]
|
41
76
|
|
42
77
|
|
@@ -65,7 +100,7 @@ class Automation(AutomationCore):
|
|
65
100
|
created_automation = auto_to_create.create()
|
66
101
|
"""
|
67
102
|
client, _ = get_or_create_client()
|
68
|
-
automation = AutomationCore(**self.
|
103
|
+
automation = AutomationCore(**self.model_dump(exclude={"id"}))
|
69
104
|
self.id = await client.create_automation(automation=automation)
|
70
105
|
return self
|
71
106
|
|
@@ -79,7 +114,7 @@ class Automation(AutomationCore):
|
|
79
114
|
"""
|
80
115
|
|
81
116
|
client, _ = get_or_create_client()
|
82
|
-
automation = AutomationCore(**self.
|
117
|
+
automation = AutomationCore(**self.model_dump(exclude={"id", "owner_resource"}))
|
83
118
|
await client.update_automation(automation_id=self.id, automation=automation)
|
84
119
|
|
85
120
|
@classmethod
|
@@ -106,11 +141,11 @@ class Automation(AutomationCore):
|
|
106
141
|
except PrefectHTTPStatusError as exc:
|
107
142
|
if exc.response.status_code == 404:
|
108
143
|
raise ValueError(f"Automation with ID {id!r} not found")
|
109
|
-
return Automation(**automation.
|
144
|
+
return Automation(**automation.model_dump())
|
110
145
|
else:
|
111
146
|
automation = await client.read_automations_by_name(name=name)
|
112
147
|
if len(automation) > 0:
|
113
|
-
return Automation(**automation[0].
|
148
|
+
return Automation(**automation[0].model_dump()) if automation else None
|
114
149
|
else:
|
115
150
|
raise ValueError(f"Automation with name {name!r} not found")
|
116
151
|
|
prefect/blocks/abstract.py
CHANGED
@@ -73,7 +73,7 @@ class NotificationBlock(Block, ABC):
|
|
73
73
|
"""
|
74
74
|
|
75
75
|
_block_schema_capabilities = ["notify"]
|
76
|
-
_events_excluded_methods = Block._events_excluded_methods + ["notify"]
|
76
|
+
_events_excluded_methods = Block._events_excluded_methods.default + ["notify"]
|
77
77
|
|
78
78
|
@property
|
79
79
|
def logger(self) -> Logger:
|