prefect-client 3.0.0rc2__py3-none-any.whl → 3.0.0rc4__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 +0 -1
- prefect/_internal/compatibility/migration.py +124 -0
- prefect/_internal/concurrency/__init__.py +2 -2
- prefect/_internal/concurrency/primitives.py +1 -0
- prefect/_internal/pydantic/annotations/pendulum.py +2 -2
- prefect/_internal/pytz.py +1 -1
- prefect/blocks/core.py +1 -1
- prefect/client/orchestration.py +96 -22
- prefect/client/schemas/actions.py +1 -1
- prefect/client/schemas/filters.py +6 -0
- prefect/client/schemas/objects.py +10 -3
- prefect/client/subscriptions.py +6 -5
- prefect/context.py +1 -27
- prefect/deployments/__init__.py +3 -0
- prefect/deployments/base.py +4 -2
- prefect/deployments/deployments.py +3 -0
- prefect/deployments/steps/pull.py +1 -0
- prefect/deployments/steps/utility.py +2 -1
- prefect/engine.py +3 -0
- prefect/events/cli/automations.py +1 -1
- prefect/events/clients.py +7 -1
- prefect/exceptions.py +9 -0
- prefect/filesystems.py +22 -11
- prefect/flow_engine.py +195 -153
- prefect/flows.py +95 -36
- prefect/futures.py +9 -1
- prefect/infrastructure/provisioners/container_instance.py +1 -0
- prefect/infrastructure/provisioners/ecs.py +2 -2
- prefect/input/__init__.py +4 -0
- prefect/logging/formatters.py +2 -2
- prefect/logging/handlers.py +2 -2
- prefect/logging/loggers.py +1 -1
- prefect/plugins.py +1 -0
- prefect/records/cache_policies.py +3 -3
- prefect/records/result_store.py +10 -3
- prefect/results.py +47 -73
- prefect/runner/runner.py +1 -1
- prefect/runner/server.py +1 -1
- prefect/runtime/__init__.py +1 -0
- prefect/runtime/deployment.py +1 -0
- prefect/runtime/flow_run.py +1 -0
- prefect/runtime/task_run.py +1 -0
- prefect/settings.py +16 -3
- prefect/states.py +15 -4
- prefect/task_engine.py +195 -39
- prefect/task_runners.py +9 -3
- prefect/task_runs.py +26 -12
- prefect/task_worker.py +149 -20
- prefect/tasks.py +153 -71
- prefect/transactions.py +85 -15
- prefect/types/__init__.py +10 -3
- prefect/utilities/asyncutils.py +3 -3
- prefect/utilities/callables.py +16 -4
- prefect/utilities/collections.py +120 -57
- prefect/utilities/dockerutils.py +5 -3
- prefect/utilities/engine.py +11 -0
- prefect/utilities/filesystem.py +4 -5
- prefect/utilities/importtools.py +29 -0
- prefect/utilities/services.py +2 -2
- prefect/utilities/urls.py +195 -0
- prefect/utilities/visualization.py +1 -0
- prefect/variables.py +4 -0
- prefect/workers/base.py +35 -0
- {prefect_client-3.0.0rc2.dist-info → prefect_client-3.0.0rc4.dist-info}/METADATA +2 -2
- {prefect_client-3.0.0rc2.dist-info → prefect_client-3.0.0rc4.dist-info}/RECORD +68 -66
- prefect/blocks/kubernetes.py +0 -115
- {prefect_client-3.0.0rc2.dist-info → prefect_client-3.0.0rc4.dist-info}/LICENSE +0 -0
- {prefect_client-3.0.0rc2.dist-info → prefect_client-3.0.0rc4.dist-info}/WHEEL +0 -0
- {prefect_client-3.0.0rc2.dist-info → prefect_client-3.0.0rc4.dist-info}/top_level.txt +0 -0
prefect/results.py
CHANGED
@@ -18,6 +18,7 @@ from uuid import UUID
|
|
18
18
|
|
19
19
|
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, ValidationError
|
20
20
|
from pydantic_core import PydanticUndefinedType
|
21
|
+
from pydantic_extra_types.pendulum_dt import DateTime
|
21
22
|
from typing_extensions import ParamSpec, Self
|
22
23
|
|
23
24
|
import prefect
|
@@ -60,28 +61,15 @@ logger = get_logger("results")
|
|
60
61
|
P = ParamSpec("P")
|
61
62
|
R = TypeVar("R")
|
62
63
|
|
63
|
-
|
64
|
-
@sync_compatible
|
65
|
-
async def get_default_result_storage() -> ResultStorage:
|
66
|
-
"""
|
67
|
-
Generate a default file system for result storage.
|
68
|
-
"""
|
69
|
-
return (
|
70
|
-
await Block.load(PREFECT_DEFAULT_RESULT_STORAGE_BLOCK.value())
|
71
|
-
if PREFECT_DEFAULT_RESULT_STORAGE_BLOCK.value() is not None
|
72
|
-
else LocalFileSystem(basepath=PREFECT_LOCAL_STORAGE_PATH.value())
|
73
|
-
)
|
74
|
-
|
75
|
-
|
76
|
-
_default_task_scheduling_storages: Dict[Tuple[str, str], WritableFileSystem] = {}
|
64
|
+
_default_storages: Dict[Tuple[str, str], WritableFileSystem] = {}
|
77
65
|
|
78
66
|
|
79
|
-
async def
|
67
|
+
async def _get_or_create_default_storage(block_document_slug: str) -> ResultStorage:
|
80
68
|
"""
|
81
|
-
Generate a default file system for
|
69
|
+
Generate a default file system for storage.
|
82
70
|
"""
|
83
71
|
default_storage_name, storage_path = cache_key = (
|
84
|
-
|
72
|
+
block_document_slug,
|
85
73
|
PREFECT_LOCAL_STORAGE_PATH.value(),
|
86
74
|
)
|
87
75
|
|
@@ -96,8 +84,8 @@ async def get_or_create_default_task_scheduling_storage() -> ResultStorage:
|
|
96
84
|
if block_type_slug == "local-file-system":
|
97
85
|
block = LocalFileSystem(basepath=storage_path)
|
98
86
|
else:
|
99
|
-
raise
|
100
|
-
"The default
|
87
|
+
raise ValueError(
|
88
|
+
"The default storage block does not exist, but it is of type "
|
101
89
|
f"'{block_type_slug}' which cannot be created implicitly. Please create "
|
102
90
|
"the block manually."
|
103
91
|
)
|
@@ -114,13 +102,32 @@ async def get_or_create_default_task_scheduling_storage() -> ResultStorage:
|
|
114
102
|
return block
|
115
103
|
|
116
104
|
try:
|
117
|
-
return
|
105
|
+
return _default_storages[cache_key]
|
118
106
|
except KeyError:
|
119
107
|
storage = await get_storage()
|
120
|
-
|
108
|
+
_default_storages[cache_key] = storage
|
121
109
|
return storage
|
122
110
|
|
123
111
|
|
112
|
+
@sync_compatible
|
113
|
+
async def get_or_create_default_result_storage() -> ResultStorage:
|
114
|
+
"""
|
115
|
+
Generate a default file system for result storage.
|
116
|
+
"""
|
117
|
+
return await _get_or_create_default_storage(
|
118
|
+
PREFECT_DEFAULT_RESULT_STORAGE_BLOCK.value()
|
119
|
+
)
|
120
|
+
|
121
|
+
|
122
|
+
async def get_or_create_default_task_scheduling_storage() -> ResultStorage:
|
123
|
+
"""
|
124
|
+
Generate a default file system for background task parameter/result storage.
|
125
|
+
"""
|
126
|
+
return await _get_or_create_default_storage(
|
127
|
+
PREFECT_TASK_SCHEDULING_DEFAULT_STORAGE_BLOCK.value()
|
128
|
+
)
|
129
|
+
|
130
|
+
|
124
131
|
def get_default_result_serializer() -> ResultSerializer:
|
125
132
|
"""
|
126
133
|
Generate a default file system for result storage.
|
@@ -201,7 +208,9 @@ class ResultFactory(BaseModel):
|
|
201
208
|
kwargs.pop(key)
|
202
209
|
|
203
210
|
# Apply defaults
|
204
|
-
kwargs.setdefault(
|
211
|
+
kwargs.setdefault(
|
212
|
+
"result_storage", await get_or_create_default_result_storage()
|
213
|
+
)
|
205
214
|
kwargs.setdefault("result_serializer", get_default_result_serializer())
|
206
215
|
kwargs.setdefault("persist_result", get_default_persist_setting())
|
207
216
|
kwargs.setdefault("cache_result_in_memory", True)
|
@@ -271,14 +280,9 @@ class ResultFactory(BaseModel):
|
|
271
280
|
"""
|
272
281
|
Create a new result factory for a task.
|
273
282
|
"""
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
if ctx and ctx.autonomous_task_run:
|
279
|
-
return await cls.from_autonomous_task(task, client=client)
|
280
|
-
|
281
|
-
return await cls._from_task(task, get_default_result_storage, client=client)
|
283
|
+
return await cls._from_task(
|
284
|
+
task, get_or_create_default_result_storage, client=client
|
285
|
+
)
|
282
286
|
|
283
287
|
@classmethod
|
284
288
|
@inject_client
|
@@ -426,16 +430,16 @@ class ResultFactory(BaseModel):
|
|
426
430
|
)
|
427
431
|
|
428
432
|
@sync_compatible
|
429
|
-
async def create_result(
|
433
|
+
async def create_result(
|
434
|
+
self, obj: R, key: Optional[str] = None, expiration: Optional[DateTime] = None
|
435
|
+
) -> Union[R, "BaseResult[R]"]:
|
430
436
|
"""
|
431
437
|
Create a result type for the given object.
|
432
438
|
|
433
439
|
If persistence is disabled, the object is wrapped in an `UnpersistedResult` and
|
434
440
|
returned.
|
435
441
|
|
436
|
-
If persistence is enabled
|
437
|
-
- Bool and null types are converted into `LiteralResult`.
|
438
|
-
- Other types are serialized, persisted to storage, and a reference is returned.
|
442
|
+
If persistence is enabled the object is serialized, persisted to storage, and a reference is returned.
|
439
443
|
"""
|
440
444
|
# Null objects are "cached" in memory at no cost
|
441
445
|
should_cache_object = self.cache_result_in_memory or obj is None
|
@@ -443,9 +447,6 @@ class ResultFactory(BaseModel):
|
|
443
447
|
if not self.persist_result:
|
444
448
|
return await UnpersistedResult.create(obj, cache_object=should_cache_object)
|
445
449
|
|
446
|
-
if type(obj) in LITERAL_TYPES:
|
447
|
-
return await LiteralResult.create(obj)
|
448
|
-
|
449
450
|
if key:
|
450
451
|
|
451
452
|
def key_fn():
|
@@ -462,6 +463,7 @@ class ResultFactory(BaseModel):
|
|
462
463
|
storage_key_fn=storage_key_fn,
|
463
464
|
serializer=self.serializer,
|
464
465
|
cache_object=should_cache_object,
|
466
|
+
expiration=expiration,
|
465
467
|
)
|
466
468
|
|
467
469
|
@sync_compatible
|
@@ -569,41 +571,6 @@ class UnpersistedResult(BaseResult):
|
|
569
571
|
return result
|
570
572
|
|
571
573
|
|
572
|
-
class LiteralResult(BaseResult):
|
573
|
-
"""
|
574
|
-
Result type for literal values like `None`, `True`, `False`.
|
575
|
-
|
576
|
-
These values are stored inline and JSON serialized when sent to the Prefect API.
|
577
|
-
They are not persisted to external result storage.
|
578
|
-
"""
|
579
|
-
|
580
|
-
type: str = "literal"
|
581
|
-
value: Any = None
|
582
|
-
|
583
|
-
def has_cached_object(self) -> bool:
|
584
|
-
# This result type always has the object cached in memory
|
585
|
-
return True
|
586
|
-
|
587
|
-
@sync_compatible
|
588
|
-
async def get(self) -> R:
|
589
|
-
return self.value
|
590
|
-
|
591
|
-
@classmethod
|
592
|
-
@sync_compatible
|
593
|
-
async def create(
|
594
|
-
cls: "Type[LiteralResult]",
|
595
|
-
obj: R,
|
596
|
-
) -> "LiteralResult[R]":
|
597
|
-
if type(obj) not in LITERAL_TYPES:
|
598
|
-
raise TypeError(
|
599
|
-
f"Unsupported type {type(obj).__name__!r} for result literal. Expected"
|
600
|
-
f" one of: {', '.join(type_.__name__ for type_ in LITERAL_TYPES)}"
|
601
|
-
)
|
602
|
-
|
603
|
-
description = f"Result with value `{obj}` persisted to Prefect."
|
604
|
-
return cls(value=obj, artifact_type="result", artifact_description=description)
|
605
|
-
|
606
|
-
|
607
574
|
class PersistedResult(BaseResult):
|
608
575
|
"""
|
609
576
|
Result type which stores a reference to a persisted result.
|
@@ -619,6 +586,7 @@ class PersistedResult(BaseResult):
|
|
619
586
|
serializer_type: str
|
620
587
|
storage_block_id: uuid.UUID
|
621
588
|
storage_key: str
|
589
|
+
expiration: Optional[DateTime] = None
|
622
590
|
|
623
591
|
_should_cache_object: bool = PrivateAttr(default=True)
|
624
592
|
|
@@ -634,6 +602,7 @@ class PersistedResult(BaseResult):
|
|
634
602
|
|
635
603
|
blob = await self._read_blob(client=client)
|
636
604
|
obj = blob.serializer.loads(blob.data)
|
605
|
+
self.expiration = blob.expiration
|
637
606
|
|
638
607
|
if self._should_cache_object:
|
639
608
|
self._cache_object(obj)
|
@@ -673,6 +642,7 @@ class PersistedResult(BaseResult):
|
|
673
642
|
storage_key_fn: Callable[[], str],
|
674
643
|
serializer: Serializer,
|
675
644
|
cache_object: bool = True,
|
645
|
+
expiration: Optional[DateTime] = None,
|
676
646
|
) -> "PersistedResult[R]":
|
677
647
|
"""
|
678
648
|
Create a new result reference from a user's object.
|
@@ -684,7 +654,9 @@ class PersistedResult(BaseResult):
|
|
684
654
|
storage_block_id is not None
|
685
655
|
), "Unexpected storage block ID. Was it persisted?"
|
686
656
|
data = serializer.dumps(obj)
|
687
|
-
blob = PersistedResultBlob(
|
657
|
+
blob = PersistedResultBlob(
|
658
|
+
serializer=serializer, data=data, expiration=expiration
|
659
|
+
)
|
688
660
|
|
689
661
|
key = storage_key_fn()
|
690
662
|
if not isinstance(key, str):
|
@@ -709,6 +681,7 @@ class PersistedResult(BaseResult):
|
|
709
681
|
storage_key=key,
|
710
682
|
artifact_type="result",
|
711
683
|
artifact_description=description,
|
684
|
+
expiration=expiration,
|
712
685
|
)
|
713
686
|
|
714
687
|
if cache_object:
|
@@ -730,6 +703,7 @@ class PersistedResultBlob(BaseModel):
|
|
730
703
|
serializer: Serializer
|
731
704
|
data: bytes
|
732
705
|
prefect_version: str = Field(default=prefect.__version__)
|
706
|
+
expiration: Optional[DateTime] = None
|
733
707
|
|
734
708
|
def to_bytes(self) -> bytes:
|
735
709
|
return self.model_dump_json(serialize_as_any=True).encode()
|
prefect/runner/runner.py
CHANGED
prefect/runner/server.py
CHANGED
@@ -42,7 +42,7 @@ class RunnerGenericFlowRunRequest(BaseModel):
|
|
42
42
|
parent_task_run_id: Optional[uuid.UUID] = None
|
43
43
|
|
44
44
|
|
45
|
-
def perform_health_check(runner, delay_threshold: int = None) -> JSONResponse:
|
45
|
+
def perform_health_check(runner, delay_threshold: Optional[int] = None) -> JSONResponse:
|
46
46
|
if delay_threshold is None:
|
47
47
|
delay_threshold = (
|
48
48
|
PREFECT_RUNNER_SERVER_MISSED_POLLS_TOLERANCE.value()
|
prefect/runtime/__init__.py
CHANGED
prefect/runtime/deployment.py
CHANGED
prefect/runtime/flow_run.py
CHANGED
prefect/runtime/task_run.py
CHANGED
prefect/settings.py
CHANGED
@@ -42,6 +42,7 @@ dependent on the value of other settings or perform other dynamic effects.
|
|
42
42
|
|
43
43
|
import logging
|
44
44
|
import os
|
45
|
+
import socket
|
45
46
|
import string
|
46
47
|
import warnings
|
47
48
|
from contextlib import contextmanager
|
@@ -84,6 +85,7 @@ from prefect._internal.schemas.validators import validate_settings
|
|
84
85
|
from prefect.exceptions import MissingProfileError
|
85
86
|
from prefect.utilities.names import OBFUSCATED_PREFIX, obfuscate
|
86
87
|
from prefect.utilities.pydantic import add_cloudpickle_reduction
|
88
|
+
from prefect.utilities.slugify import slugify
|
87
89
|
|
88
90
|
T = TypeVar("T")
|
89
91
|
|
@@ -417,6 +419,18 @@ def warn_on_misconfigured_api_url(values):
|
|
417
419
|
return values
|
418
420
|
|
419
421
|
|
422
|
+
def default_result_storage_block_name(
|
423
|
+
settings: Optional["Settings"] = None, value: Optional[str] = None
|
424
|
+
):
|
425
|
+
"""
|
426
|
+
`value_callback` for `PREFECT_DEFAULT_RESULT_STORAGE_BLOCK_NAME` that sets the default
|
427
|
+
value to the hostname of the machine.
|
428
|
+
"""
|
429
|
+
if value is None:
|
430
|
+
return f"local-file-system/{slugify(socket.gethostname())}-storage"
|
431
|
+
return value
|
432
|
+
|
433
|
+
|
420
434
|
def default_database_connection_url(settings, value):
|
421
435
|
templater = template_with_settings(PREFECT_HOME, PREFECT_API_DATABASE_PASSWORD)
|
422
436
|
|
@@ -1527,7 +1541,7 @@ The maximum number of retries to queue for submission.
|
|
1527
1541
|
|
1528
1542
|
PREFECT_TASK_SCHEDULING_PENDING_TASK_TIMEOUT = Setting(
|
1529
1543
|
timedelta,
|
1530
|
-
default=timedelta(
|
1544
|
+
default=timedelta(0),
|
1531
1545
|
)
|
1532
1546
|
"""
|
1533
1547
|
How long before a PENDING task are made available to another task worker. In practice,
|
@@ -1575,8 +1589,7 @@ PREFECT_EXPERIMENTAL_ENABLE_SCHEDULE_CONCURRENCY = Setting(bool, default=False)
|
|
1575
1589
|
# Defaults -----------------------------------------------------------------------------
|
1576
1590
|
|
1577
1591
|
PREFECT_DEFAULT_RESULT_STORAGE_BLOCK = Setting(
|
1578
|
-
Optional[str],
|
1579
|
-
default=None,
|
1592
|
+
Optional[str], default=None, value_callback=default_result_storage_block_name
|
1580
1593
|
)
|
1581
1594
|
"""The `block-type/block-document` slug of a block to use as the default result storage."""
|
1582
1595
|
|
prefect/states.py
CHANGED
@@ -205,7 +205,10 @@ async def exception_to_failed_state(
|
|
205
205
|
|
206
206
|
|
207
207
|
async def return_value_to_state(
|
208
|
-
retval: R,
|
208
|
+
retval: R,
|
209
|
+
result_factory: ResultFactory,
|
210
|
+
key: Optional[str] = None,
|
211
|
+
expiration: Optional[datetime.datetime] = None,
|
209
212
|
) -> State[R]:
|
210
213
|
"""
|
211
214
|
Given a return value from a user's function, create a `State` the run should
|
@@ -238,7 +241,9 @@ async def return_value_to_state(
|
|
238
241
|
# Unless the user has already constructed a result explicitly, use the factory
|
239
242
|
# to update the data to the correct type
|
240
243
|
if not isinstance(state.data, BaseResult):
|
241
|
-
state.data = await result_factory.create_result(
|
244
|
+
state.data = await result_factory.create_result(
|
245
|
+
state.data, key=key, expiration=expiration
|
246
|
+
)
|
242
247
|
|
243
248
|
return state
|
244
249
|
|
@@ -278,7 +283,9 @@ async def return_value_to_state(
|
|
278
283
|
return State(
|
279
284
|
type=new_state_type,
|
280
285
|
message=message,
|
281
|
-
data=await result_factory.create_result(
|
286
|
+
data=await result_factory.create_result(
|
287
|
+
retval, key=key, expiration=expiration
|
288
|
+
),
|
282
289
|
)
|
283
290
|
|
284
291
|
# Generators aren't portable, implicitly convert them to a list.
|
@@ -291,7 +298,11 @@ async def return_value_to_state(
|
|
291
298
|
if isinstance(data, BaseResult):
|
292
299
|
return Completed(data=data)
|
293
300
|
else:
|
294
|
-
return Completed(
|
301
|
+
return Completed(
|
302
|
+
data=await result_factory.create_result(
|
303
|
+
data, key=key, expiration=expiration
|
304
|
+
)
|
305
|
+
)
|
295
306
|
|
296
307
|
|
297
308
|
@sync_compatible
|