prefect-client 3.0.0rc2__py3-none-any.whl → 3.0.0rc3__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/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 +3 -2
- 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 +116 -154
- prefect/flows.py +83 -34
- 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 +27 -55
- 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 +15 -2
- prefect/states.py +15 -4
- prefect/task_engine.py +190 -33
- prefect/task_runners.py +9 -3
- prefect/task_runs.py +3 -3
- prefect/task_worker.py +29 -9
- prefect/tasks.py +133 -57
- prefect/transactions.py +87 -15
- prefect/types/__init__.py +1 -1
- prefect/utilities/asyncutils.py +3 -3
- prefect/utilities/callables.py +16 -4
- 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.0rc3.dist-info}/METADATA +2 -2
- {prefect_client-3.0.0rc2.dist-info → prefect_client-3.0.0rc3.dist-info}/RECORD +65 -62
- {prefect_client-3.0.0rc2.dist-info → prefect_client-3.0.0rc3.dist-info}/LICENSE +0 -0
- {prefect_client-3.0.0rc2.dist-info → prefect_client-3.0.0rc3.dist-info}/WHEEL +0 -0
- {prefect_client-3.0.0rc2.dist-info → prefect_client-3.0.0rc3.dist-info}/top_level.txt +0 -0
prefect/flows.py
CHANGED
@@ -4,7 +4,6 @@ Module containing the base workflow class and decorator - for most use cases, us
|
|
4
4
|
|
5
5
|
# This file requires type-checking with pyright because mypy does not yet support PEP612
|
6
6
|
# See https://github.com/python/mypy/issues/8645
|
7
|
-
|
8
7
|
import ast
|
9
8
|
import datetime
|
10
9
|
import importlib.util
|
@@ -15,6 +14,7 @@ import re
|
|
15
14
|
import sys
|
16
15
|
import tempfile
|
17
16
|
import warnings
|
17
|
+
from copy import copy
|
18
18
|
from functools import partial, update_wrapper
|
19
19
|
from pathlib import Path
|
20
20
|
from tempfile import NamedTemporaryFile
|
@@ -102,7 +102,7 @@ from prefect.utilities.callables import (
|
|
102
102
|
from prefect.utilities.collections import listrepr
|
103
103
|
from prefect.utilities.filesystem import relative_path_to_current_platform
|
104
104
|
from prefect.utilities.hashing import file_hash
|
105
|
-
from prefect.utilities.importtools import import_object
|
105
|
+
from prefect.utilities.importtools import import_object, safe_load_namespace
|
106
106
|
|
107
107
|
from ._internal.pydantic.v2_schema import is_v2_type
|
108
108
|
from ._internal.pydantic.v2_validated_func import V2ValidatedFunction
|
@@ -354,6 +354,28 @@ class Flow(Generic[P, R]):
|
|
354
354
|
|
355
355
|
self._entrypoint = f"{module}:{fn.__name__}"
|
356
356
|
|
357
|
+
@property
|
358
|
+
def ismethod(self) -> bool:
|
359
|
+
return hasattr(self.fn, "__prefect_self__")
|
360
|
+
|
361
|
+
def __get__(self, instance, owner):
|
362
|
+
"""
|
363
|
+
Implement the descriptor protocol so that the flow can be used as an instance method.
|
364
|
+
When an instance method is loaded, this method is called with the "self" instance as
|
365
|
+
an argument. We return a copy of the flow with that instance bound to the flow's function.
|
366
|
+
"""
|
367
|
+
|
368
|
+
# if no instance is provided, it's being accessed on the class
|
369
|
+
if instance is None:
|
370
|
+
return self
|
371
|
+
|
372
|
+
# if the flow is being accessed on an instance, bind the instance to the __prefect_self__ attribute
|
373
|
+
# of the flow's function. This will allow it to be automatically added to the flow's parameters
|
374
|
+
else:
|
375
|
+
bound_flow = copy(self)
|
376
|
+
bound_flow.fn.__prefect_self__ = instance
|
377
|
+
return bound_flow
|
378
|
+
|
357
379
|
def with_options(
|
358
380
|
self,
|
359
381
|
*,
|
@@ -555,6 +577,9 @@ class Flow(Generic[P, R]):
|
|
555
577
|
"""
|
556
578
|
serialized_parameters = {}
|
557
579
|
for key, value in parameters.items():
|
580
|
+
# do not serialize the bound self object
|
581
|
+
if self.ismethod and value is self.fn.__prefect_self__:
|
582
|
+
continue
|
558
583
|
try:
|
559
584
|
serialized_parameters[key] = jsonable_encoder(value)
|
560
585
|
except (TypeError, ValueError):
|
@@ -1241,19 +1266,14 @@ class Flow(Generic[P, R]):
|
|
1241
1266
|
# we can add support for exploring subflows for tasks in the future.
|
1242
1267
|
return track_viz_task(self.isasync, self.name, parameters)
|
1243
1268
|
|
1244
|
-
from prefect.flow_engine import run_flow
|
1269
|
+
from prefect.flow_engine import run_flow
|
1245
1270
|
|
1246
|
-
|
1271
|
+
return run_flow(
|
1247
1272
|
flow=self,
|
1248
1273
|
parameters=parameters,
|
1249
1274
|
wait_for=wait_for,
|
1250
1275
|
return_type=return_type,
|
1251
1276
|
)
|
1252
|
-
if self.isasync:
|
1253
|
-
# this returns an awaitable coroutine
|
1254
|
-
return run_flow(**run_kwargs)
|
1255
|
-
else:
|
1256
|
-
return run_flow_sync(**run_kwargs)
|
1257
1277
|
|
1258
1278
|
@sync_compatible
|
1259
1279
|
async def visualize(self, *args, **kwargs):
|
@@ -1329,8 +1349,8 @@ def flow(
|
|
1329
1349
|
retries: Optional[int] = None,
|
1330
1350
|
retry_delay_seconds: Optional[Union[int, float]] = None,
|
1331
1351
|
task_runner: Optional[TaskRunner] = None,
|
1332
|
-
description: str = None,
|
1333
|
-
timeout_seconds: Union[int, float] = None,
|
1352
|
+
description: Optional[str] = None,
|
1353
|
+
timeout_seconds: Union[int, float, None] = None,
|
1334
1354
|
validate_parameters: bool = True,
|
1335
1355
|
persist_result: Optional[bool] = None,
|
1336
1356
|
result_storage: Optional[ResultStorage] = None,
|
@@ -1358,11 +1378,11 @@ def flow(
|
|
1358
1378
|
name: Optional[str] = None,
|
1359
1379
|
version: Optional[str] = None,
|
1360
1380
|
flow_run_name: Optional[Union[Callable[[], str], str]] = None,
|
1361
|
-
retries: int = None,
|
1362
|
-
retry_delay_seconds: Union[int, float] = None,
|
1381
|
+
retries: Optional[int] = None,
|
1382
|
+
retry_delay_seconds: Union[int, float, None] = None,
|
1363
1383
|
task_runner: Optional[TaskRunner] = None,
|
1364
|
-
description: str = None,
|
1365
|
-
timeout_seconds: Union[int, float] = None,
|
1384
|
+
description: Optional[str] = None,
|
1385
|
+
timeout_seconds: Union[int, float, None] = None,
|
1366
1386
|
validate_parameters: bool = True,
|
1367
1387
|
persist_result: Optional[bool] = None,
|
1368
1388
|
result_storage: Optional[ResultStorage] = None,
|
@@ -1485,6 +1505,9 @@ def flow(
|
|
1485
1505
|
>>> pass
|
1486
1506
|
"""
|
1487
1507
|
if __fn:
|
1508
|
+
if isinstance(__fn, (classmethod, staticmethod)):
|
1509
|
+
method_decorator = type(__fn).__name__
|
1510
|
+
raise TypeError(f"@{method_decorator} should be applied on top of @flow")
|
1488
1511
|
return cast(
|
1489
1512
|
Flow[P, R],
|
1490
1513
|
Flow(
|
@@ -1560,7 +1583,9 @@ flow.from_source = Flow.from_source
|
|
1560
1583
|
|
1561
1584
|
|
1562
1585
|
def select_flow(
|
1563
|
-
flows: Iterable[Flow],
|
1586
|
+
flows: Iterable[Flow],
|
1587
|
+
flow_name: Optional[str] = None,
|
1588
|
+
from_message: Optional[str] = None,
|
1564
1589
|
) -> Flow:
|
1565
1590
|
"""
|
1566
1591
|
Select the only flow in an iterable or a flow specified by name.
|
@@ -1574,33 +1599,33 @@ def select_flow(
|
|
1574
1599
|
UnspecifiedFlowError: If multiple flows exist but no flow name was provided
|
1575
1600
|
"""
|
1576
1601
|
# Convert to flows by name
|
1577
|
-
|
1602
|
+
flows_dict = {f.name: f for f in flows}
|
1578
1603
|
|
1579
1604
|
# Add a leading space if given, otherwise use an empty string
|
1580
1605
|
from_message = (" " + from_message) if from_message else ""
|
1581
|
-
if not
|
1606
|
+
if not Optional:
|
1582
1607
|
raise MissingFlowError(f"No flows found{from_message}.")
|
1583
1608
|
|
1584
|
-
elif flow_name and flow_name not in
|
1609
|
+
elif flow_name and flow_name not in flows_dict:
|
1585
1610
|
raise MissingFlowError(
|
1586
1611
|
f"Flow {flow_name!r} not found{from_message}. "
|
1587
|
-
f"Found the following flows: {listrepr(
|
1612
|
+
f"Found the following flows: {listrepr(flows_dict.keys())}. "
|
1588
1613
|
"Check to make sure that your flow function is decorated with `@flow`."
|
1589
1614
|
)
|
1590
1615
|
|
1591
|
-
elif not flow_name and len(
|
1616
|
+
elif not flow_name and len(flows_dict) > 1:
|
1592
1617
|
raise UnspecifiedFlowError(
|
1593
1618
|
(
|
1594
|
-
f"Found {len(
|
1595
|
-
f" {listrepr(sorted(
|
1619
|
+
f"Found {len(flows_dict)} flows{from_message}:"
|
1620
|
+
f" {listrepr(sorted(flows_dict.keys()))}. Specify a flow name to select a"
|
1596
1621
|
" flow."
|
1597
1622
|
),
|
1598
1623
|
)
|
1599
1624
|
|
1600
1625
|
if flow_name:
|
1601
|
-
return
|
1626
|
+
return flows_dict[flow_name]
|
1602
1627
|
else:
|
1603
|
-
return list(
|
1628
|
+
return list(flows_dict.values())[0]
|
1604
1629
|
|
1605
1630
|
|
1606
1631
|
def load_flows_from_script(path: str) -> List[Flow]:
|
@@ -1617,7 +1642,7 @@ def load_flows_from_script(path: str) -> List[Flow]:
|
|
1617
1642
|
return registry_from_script(path).get_instances(Flow)
|
1618
1643
|
|
1619
1644
|
|
1620
|
-
def load_flow_from_script(path: str, flow_name: str = None) -> Flow:
|
1645
|
+
def load_flow_from_script(path: str, flow_name: Optional[str] = None) -> Flow:
|
1621
1646
|
"""
|
1622
1647
|
Extract a flow object from a script by running all of the code in the file.
|
1623
1648
|
|
@@ -1661,7 +1686,7 @@ def load_flow_from_entrypoint(
|
|
1661
1686
|
FlowScriptError: If an exception is encountered while running the script
|
1662
1687
|
MissingFlowError: If the flow function specified in the entrypoint does not exist
|
1663
1688
|
"""
|
1664
|
-
with PrefectObjectRegistry(
|
1689
|
+
with PrefectObjectRegistry( # type: ignore
|
1665
1690
|
block_code_execution=True,
|
1666
1691
|
capture_failures=True,
|
1667
1692
|
):
|
@@ -1686,7 +1711,7 @@ def load_flow_from_entrypoint(
|
|
1686
1711
|
return flow
|
1687
1712
|
|
1688
1713
|
|
1689
|
-
def load_flow_from_text(script_contents: AnyStr, flow_name: str):
|
1714
|
+
def load_flow_from_text(script_contents: AnyStr, flow_name: str) -> Flow:
|
1690
1715
|
"""
|
1691
1716
|
Load a flow from a text script.
|
1692
1717
|
|
@@ -1717,7 +1742,7 @@ async def serve(
|
|
1717
1742
|
print_starting_message: bool = True,
|
1718
1743
|
limit: Optional[int] = None,
|
1719
1744
|
**kwargs,
|
1720
|
-
):
|
1745
|
+
) -> NoReturn:
|
1721
1746
|
"""
|
1722
1747
|
Serve the provided list of deployments.
|
1723
1748
|
|
@@ -1807,7 +1832,7 @@ async def load_flow_from_flow_run(
|
|
1807
1832
|
flow_run: "FlowRun",
|
1808
1833
|
ignore_storage: bool = False,
|
1809
1834
|
storage_base_path: Optional[str] = None,
|
1810
|
-
) ->
|
1835
|
+
) -> Flow:
|
1811
1836
|
"""
|
1812
1837
|
Load a flow from the location/script provided in a deployment's storage document.
|
1813
1838
|
|
@@ -1861,7 +1886,9 @@ async def load_flow_from_flow_run(
|
|
1861
1886
|
await storage_block.get_directory(from_path=from_path, local_path=".")
|
1862
1887
|
|
1863
1888
|
if deployment.pull_steps:
|
1864
|
-
run_logger.debug(
|
1889
|
+
run_logger.debug(
|
1890
|
+
f"Running {len(deployment.pull_steps)} deployment pull step(s)"
|
1891
|
+
)
|
1865
1892
|
output = await run_steps(deployment.pull_steps)
|
1866
1893
|
if output.get("directory"):
|
1867
1894
|
run_logger.debug(f"Changing working directory to {output['directory']!r}")
|
@@ -1933,11 +1960,33 @@ def load_flow_argument_from_entrypoint(
|
|
1933
1960
|
):
|
1934
1961
|
for keyword in decorator.keywords:
|
1935
1962
|
if keyword.arg == arg:
|
1936
|
-
|
1937
|
-
|
1938
|
-
|
1963
|
+
if isinstance(keyword.value, ast.Constant):
|
1964
|
+
return (
|
1965
|
+
keyword.value.value
|
1966
|
+
) # Return the string value of the argument
|
1967
|
+
|
1968
|
+
# if the arg value is not a raw str (i.e. a variable or expression),
|
1969
|
+
# then attempt to evaluate it
|
1970
|
+
namespace = safe_load_namespace(source_code)
|
1971
|
+
literal_arg_value = ast.get_source_segment(
|
1972
|
+
source_code, keyword.value
|
1973
|
+
)
|
1974
|
+
try:
|
1975
|
+
evaluated_value = eval(literal_arg_value, namespace) # type: ignore
|
1976
|
+
except Exception as e:
|
1977
|
+
logger.info(
|
1978
|
+
"Failed to parse @flow argument: `%s=%s` due to the following error. Ignoring and falling back to default behavior.",
|
1979
|
+
arg,
|
1980
|
+
literal_arg_value,
|
1981
|
+
exc_info=e,
|
1982
|
+
)
|
1983
|
+
# ignore the decorator arg and fallback to default behavior
|
1984
|
+
break
|
1985
|
+
return str(evaluated_value)
|
1939
1986
|
|
1940
1987
|
if arg == "name":
|
1941
1988
|
return func_name.replace(
|
1942
1989
|
"_", "-"
|
1943
1990
|
) # If no matching decorator or keyword argument is found
|
1991
|
+
|
1992
|
+
return None
|
@@ -367,7 +367,7 @@ class AuthenticationResource:
|
|
367
367
|
work_pool_name: str,
|
368
368
|
user_name: str = "prefect-ecs-user",
|
369
369
|
policy_name: str = "prefect-ecs-policy",
|
370
|
-
credentials_block_name: str = None,
|
370
|
+
credentials_block_name: Optional[str] = None,
|
371
371
|
):
|
372
372
|
self._user_name = user_name
|
373
373
|
self._credentials_block_name = (
|
@@ -1130,7 +1130,7 @@ class ElasticContainerServicePushProvisioner:
|
|
1130
1130
|
work_pool_name: str,
|
1131
1131
|
user_name: str = "prefect-ecs-user",
|
1132
1132
|
policy_name: str = "prefect-ecs-policy",
|
1133
|
-
credentials_block_name: str = None,
|
1133
|
+
credentials_block_name: Optional[str] = None,
|
1134
1134
|
cluster_name: str = "prefect-ecs-cluster",
|
1135
1135
|
vpc_name: str = "prefect-ecs-vpc",
|
1136
1136
|
ecs_security_group_name: str = "prefect-ecs-security-group",
|
prefect/input/__init__.py
CHANGED
@@ -12,6 +12,8 @@ from .run_input import (
|
|
12
12
|
RunInputMetadata,
|
13
13
|
keyset_from_base_key,
|
14
14
|
keyset_from_paused_state,
|
15
|
+
receive_input,
|
16
|
+
send_input,
|
15
17
|
)
|
16
18
|
|
17
19
|
__all__ = [
|
@@ -26,4 +28,6 @@ __all__ = [
|
|
26
28
|
"keyset_from_base_key",
|
27
29
|
"keyset_from_paused_state",
|
28
30
|
"read_flow_run_input",
|
31
|
+
"receive_input",
|
32
|
+
"send_input",
|
29
33
|
]
|
prefect/logging/formatters.py
CHANGED
@@ -78,8 +78,8 @@ class PrefectFormatter(logging.Formatter):
|
|
78
78
|
validate=True,
|
79
79
|
*,
|
80
80
|
defaults=None,
|
81
|
-
task_run_fmt: str = None,
|
82
|
-
flow_run_fmt: str = None,
|
81
|
+
task_run_fmt: Optional[str] = None,
|
82
|
+
flow_run_fmt: Optional[str] = None,
|
83
83
|
) -> None:
|
84
84
|
"""
|
85
85
|
Implementation of the standard Python formatter with support for multiple
|
prefect/logging/handlers.py
CHANGED
@@ -108,8 +108,8 @@ class APILogHandler(logging.Handler):
|
|
108
108
|
)
|
109
109
|
|
110
110
|
# Not ideal, but this method is called by the stdlib and cannot return a
|
111
|
-
# coroutine so we just schedule the drain in
|
112
|
-
from_sync.
|
111
|
+
# coroutine so we just schedule the drain in the global loop thread and continue
|
112
|
+
from_sync.call_soon_in_loop_thread(create_call(APILogWorker.drain_all))
|
113
113
|
return None
|
114
114
|
else:
|
115
115
|
# We set a timeout of 5s because we don't want to block forever if the worker
|
prefect/logging/loggers.py
CHANGED
@@ -69,7 +69,7 @@ class PrefectLogAdapter(logging.LoggerAdapter):
|
|
69
69
|
|
70
70
|
|
71
71
|
@lru_cache()
|
72
|
-
def get_logger(name: str = None) -> logging.Logger:
|
72
|
+
def get_logger(name: Optional[str] = None) -> logging.Logger:
|
73
73
|
"""
|
74
74
|
Get a `prefect` logger. These loggers are intended for internal use within the
|
75
75
|
`prefect` package.
|
prefect/plugins.py
CHANGED
@@ -77,7 +77,7 @@ class CacheKeyFnPolicy(CachePolicy):
|
|
77
77
|
|
78
78
|
@dataclass
|
79
79
|
class CompoundCachePolicy(CachePolicy):
|
80
|
-
policies: list = None
|
80
|
+
policies: Optional[list] = None
|
81
81
|
|
82
82
|
def compute_key(
|
83
83
|
self,
|
@@ -87,7 +87,7 @@ class CompoundCachePolicy(CachePolicy):
|
|
87
87
|
**kwargs,
|
88
88
|
) -> Optional[str]:
|
89
89
|
keys = []
|
90
|
-
for policy in self.policies:
|
90
|
+
for policy in self.policies or []:
|
91
91
|
keys.append(
|
92
92
|
policy.compute_key(
|
93
93
|
task_ctx=task_ctx,
|
@@ -153,7 +153,7 @@ class Inputs(CachePolicy):
|
|
153
153
|
And exclude/include config.
|
154
154
|
"""
|
155
155
|
|
156
|
-
exclude: list = None
|
156
|
+
exclude: Optional[list] = None
|
157
157
|
|
158
158
|
def compute_key(
|
159
159
|
self,
|
prefect/records/result_store.py
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
from dataclasses import dataclass
|
2
2
|
from typing import Any
|
3
3
|
|
4
|
-
|
4
|
+
import pendulum
|
5
|
+
|
5
6
|
from prefect.results import BaseResult, PersistedResult, ResultFactory
|
6
7
|
from prefect.utilities.asyncutils import run_coro_as_sync
|
7
8
|
|
@@ -17,9 +18,15 @@ class ResultFactoryStore(RecordStore):
|
|
17
18
|
try:
|
18
19
|
result = self.read(key)
|
19
20
|
result.get(_sync=True)
|
21
|
+
if result.expiration:
|
22
|
+
# if the result has an expiration,
|
23
|
+
# check if it is still in the future
|
24
|
+
exists = result.expiration > pendulum.now("utc")
|
25
|
+
else:
|
26
|
+
exists = True
|
20
27
|
self.cache = result
|
21
|
-
return
|
22
|
-
except
|
28
|
+
return exists
|
29
|
+
except Exception:
|
23
30
|
return False
|
24
31
|
|
25
32
|
def read(self, key: str) -> BaseResult:
|
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
|
@@ -37,6 +38,7 @@ from prefect.settings import (
|
|
37
38
|
PREFECT_RESULTS_DEFAULT_SERIALIZER,
|
38
39
|
PREFECT_RESULTS_PERSIST_BY_DEFAULT,
|
39
40
|
PREFECT_TASK_SCHEDULING_DEFAULT_STORAGE_BLOCK,
|
41
|
+
default_result_storage_block_name,
|
40
42
|
)
|
41
43
|
from prefect.utilities.annotations import NotSet
|
42
44
|
from prefect.utilities.asyncutils import sync_compatible
|
@@ -66,11 +68,18 @@ async def get_default_result_storage() -> ResultStorage:
|
|
66
68
|
"""
|
67
69
|
Generate a default file system for result storage.
|
68
70
|
"""
|
69
|
-
|
70
|
-
await Block.load(PREFECT_DEFAULT_RESULT_STORAGE_BLOCK.value())
|
71
|
-
|
72
|
-
|
73
|
-
|
71
|
+
try:
|
72
|
+
return await Block.load(PREFECT_DEFAULT_RESULT_STORAGE_BLOCK.value())
|
73
|
+
except ValueError as e:
|
74
|
+
if "Unable to find" not in str(e):
|
75
|
+
raise e
|
76
|
+
elif (
|
77
|
+
PREFECT_DEFAULT_RESULT_STORAGE_BLOCK.value()
|
78
|
+
== default_result_storage_block_name()
|
79
|
+
):
|
80
|
+
return LocalFileSystem(basepath=PREFECT_LOCAL_STORAGE_PATH.value())
|
81
|
+
else:
|
82
|
+
raise
|
74
83
|
|
75
84
|
|
76
85
|
_default_task_scheduling_storages: Dict[Tuple[str, str], WritableFileSystem] = {}
|
@@ -271,13 +280,6 @@ class ResultFactory(BaseModel):
|
|
271
280
|
"""
|
272
281
|
Create a new result factory for a task.
|
273
282
|
"""
|
274
|
-
from prefect.context import FlowRunContext
|
275
|
-
|
276
|
-
ctx = FlowRunContext.get()
|
277
|
-
|
278
|
-
if ctx and ctx.autonomous_task_run:
|
279
|
-
return await cls.from_autonomous_task(task, client=client)
|
280
|
-
|
281
283
|
return await cls._from_task(task, get_default_result_storage, client=client)
|
282
284
|
|
283
285
|
@classmethod
|
@@ -426,16 +428,16 @@ class ResultFactory(BaseModel):
|
|
426
428
|
)
|
427
429
|
|
428
430
|
@sync_compatible
|
429
|
-
async def create_result(
|
431
|
+
async def create_result(
|
432
|
+
self, obj: R, key: Optional[str] = None, expiration: Optional[DateTime] = None
|
433
|
+
) -> Union[R, "BaseResult[R]"]:
|
430
434
|
"""
|
431
435
|
Create a result type for the given object.
|
432
436
|
|
433
437
|
If persistence is disabled, the object is wrapped in an `UnpersistedResult` and
|
434
438
|
returned.
|
435
439
|
|
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.
|
440
|
+
If persistence is enabled the object is serialized, persisted to storage, and a reference is returned.
|
439
441
|
"""
|
440
442
|
# Null objects are "cached" in memory at no cost
|
441
443
|
should_cache_object = self.cache_result_in_memory or obj is None
|
@@ -443,9 +445,6 @@ class ResultFactory(BaseModel):
|
|
443
445
|
if not self.persist_result:
|
444
446
|
return await UnpersistedResult.create(obj, cache_object=should_cache_object)
|
445
447
|
|
446
|
-
if type(obj) in LITERAL_TYPES:
|
447
|
-
return await LiteralResult.create(obj)
|
448
|
-
|
449
448
|
if key:
|
450
449
|
|
451
450
|
def key_fn():
|
@@ -462,6 +461,7 @@ class ResultFactory(BaseModel):
|
|
462
461
|
storage_key_fn=storage_key_fn,
|
463
462
|
serializer=self.serializer,
|
464
463
|
cache_object=should_cache_object,
|
464
|
+
expiration=expiration,
|
465
465
|
)
|
466
466
|
|
467
467
|
@sync_compatible
|
@@ -569,41 +569,6 @@ class UnpersistedResult(BaseResult):
|
|
569
569
|
return result
|
570
570
|
|
571
571
|
|
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
572
|
class PersistedResult(BaseResult):
|
608
573
|
"""
|
609
574
|
Result type which stores a reference to a persisted result.
|
@@ -619,6 +584,7 @@ class PersistedResult(BaseResult):
|
|
619
584
|
serializer_type: str
|
620
585
|
storage_block_id: uuid.UUID
|
621
586
|
storage_key: str
|
587
|
+
expiration: Optional[DateTime] = None
|
622
588
|
|
623
589
|
_should_cache_object: bool = PrivateAttr(default=True)
|
624
590
|
|
@@ -634,6 +600,7 @@ class PersistedResult(BaseResult):
|
|
634
600
|
|
635
601
|
blob = await self._read_blob(client=client)
|
636
602
|
obj = blob.serializer.loads(blob.data)
|
603
|
+
self.expiration = blob.expiration
|
637
604
|
|
638
605
|
if self._should_cache_object:
|
639
606
|
self._cache_object(obj)
|
@@ -673,6 +640,7 @@ class PersistedResult(BaseResult):
|
|
673
640
|
storage_key_fn: Callable[[], str],
|
674
641
|
serializer: Serializer,
|
675
642
|
cache_object: bool = True,
|
643
|
+
expiration: Optional[DateTime] = None,
|
676
644
|
) -> "PersistedResult[R]":
|
677
645
|
"""
|
678
646
|
Create a new result reference from a user's object.
|
@@ -684,7 +652,9 @@ class PersistedResult(BaseResult):
|
|
684
652
|
storage_block_id is not None
|
685
653
|
), "Unexpected storage block ID. Was it persisted?"
|
686
654
|
data = serializer.dumps(obj)
|
687
|
-
blob = PersistedResultBlob(
|
655
|
+
blob = PersistedResultBlob(
|
656
|
+
serializer=serializer, data=data, expiration=expiration
|
657
|
+
)
|
688
658
|
|
689
659
|
key = storage_key_fn()
|
690
660
|
if not isinstance(key, str):
|
@@ -709,6 +679,7 @@ class PersistedResult(BaseResult):
|
|
709
679
|
storage_key=key,
|
710
680
|
artifact_type="result",
|
711
681
|
artifact_description=description,
|
682
|
+
expiration=expiration,
|
712
683
|
)
|
713
684
|
|
714
685
|
if cache_object:
|
@@ -730,6 +701,7 @@ class PersistedResultBlob(BaseModel):
|
|
730
701
|
serializer: Serializer
|
731
702
|
data: bytes
|
732
703
|
prefect_version: str = Field(default=prefect.__version__)
|
704
|
+
expiration: Optional[DateTime] = None
|
733
705
|
|
734
706
|
def to_bytes(self) -> bytes:
|
735
707
|
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