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/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
|
@@ -89,7 +89,6 @@ from prefect.task_runners import TaskRunner, ThreadPoolTaskRunner
|
|
89
89
|
from prefect.types import BANNED_CHARACTERS, WITHOUT_BANNED_CHARACTERS
|
90
90
|
from prefect.utilities.annotations import NotSet
|
91
91
|
from prefect.utilities.asyncutils import (
|
92
|
-
is_async_fn,
|
93
92
|
run_sync_in_worker_thread,
|
94
93
|
sync_compatible,
|
95
94
|
)
|
@@ -102,7 +101,7 @@ from prefect.utilities.callables import (
|
|
102
101
|
from prefect.utilities.collections import listrepr
|
103
102
|
from prefect.utilities.filesystem import relative_path_to_current_platform
|
104
103
|
from prefect.utilities.hashing import file_hash
|
105
|
-
from prefect.utilities.importtools import import_object
|
104
|
+
from prefect.utilities.importtools import import_object, safe_load_namespace
|
106
105
|
|
107
106
|
from ._internal.pydantic.v2_schema import is_v2_type
|
108
107
|
from ._internal.pydantic.v2_validated_func import V2ValidatedFunction
|
@@ -289,7 +288,18 @@ class Flow(Generic[P, R]):
|
|
289
288
|
self.description = description or inspect.getdoc(fn)
|
290
289
|
update_wrapper(self, fn)
|
291
290
|
self.fn = fn
|
292
|
-
|
291
|
+
|
292
|
+
# the flow is considered async if its function is async or an async
|
293
|
+
# generator
|
294
|
+
self.isasync = inspect.iscoroutinefunction(
|
295
|
+
self.fn
|
296
|
+
) or inspect.isasyncgenfunction(self.fn)
|
297
|
+
|
298
|
+
# the flow is considered a generator if its function is a generator or
|
299
|
+
# an async generator
|
300
|
+
self.isgenerator = inspect.isgeneratorfunction(
|
301
|
+
self.fn
|
302
|
+
) or inspect.isasyncgenfunction(self.fn)
|
293
303
|
|
294
304
|
raise_for_reserved_arguments(self.fn, ["return_state", "wait_for"])
|
295
305
|
|
@@ -354,6 +364,28 @@ class Flow(Generic[P, R]):
|
|
354
364
|
|
355
365
|
self._entrypoint = f"{module}:{fn.__name__}"
|
356
366
|
|
367
|
+
@property
|
368
|
+
def ismethod(self) -> bool:
|
369
|
+
return hasattr(self.fn, "__prefect_self__")
|
370
|
+
|
371
|
+
def __get__(self, instance, owner):
|
372
|
+
"""
|
373
|
+
Implement the descriptor protocol so that the flow can be used as an instance method.
|
374
|
+
When an instance method is loaded, this method is called with the "self" instance as
|
375
|
+
an argument. We return a copy of the flow with that instance bound to the flow's function.
|
376
|
+
"""
|
377
|
+
|
378
|
+
# if no instance is provided, it's being accessed on the class
|
379
|
+
if instance is None:
|
380
|
+
return self
|
381
|
+
|
382
|
+
# if the flow is being accessed on an instance, bind the instance to the __prefect_self__ attribute
|
383
|
+
# of the flow's function. This will allow it to be automatically added to the flow's parameters
|
384
|
+
else:
|
385
|
+
bound_flow = copy(self)
|
386
|
+
bound_flow.fn.__prefect_self__ = instance
|
387
|
+
return bound_flow
|
388
|
+
|
357
389
|
def with_options(
|
358
390
|
self,
|
359
391
|
*,
|
@@ -555,6 +587,9 @@ class Flow(Generic[P, R]):
|
|
555
587
|
"""
|
556
588
|
serialized_parameters = {}
|
557
589
|
for key, value in parameters.items():
|
590
|
+
# do not serialize the bound self object
|
591
|
+
if self.ismethod and value is self.fn.__prefect_self__:
|
592
|
+
continue
|
558
593
|
try:
|
559
594
|
serialized_parameters[key] = jsonable_encoder(value)
|
560
595
|
except (TypeError, ValueError):
|
@@ -1241,19 +1276,14 @@ class Flow(Generic[P, R]):
|
|
1241
1276
|
# we can add support for exploring subflows for tasks in the future.
|
1242
1277
|
return track_viz_task(self.isasync, self.name, parameters)
|
1243
1278
|
|
1244
|
-
from prefect.flow_engine import run_flow
|
1279
|
+
from prefect.flow_engine import run_flow
|
1245
1280
|
|
1246
|
-
|
1281
|
+
return run_flow(
|
1247
1282
|
flow=self,
|
1248
1283
|
parameters=parameters,
|
1249
1284
|
wait_for=wait_for,
|
1250
1285
|
return_type=return_type,
|
1251
1286
|
)
|
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
1287
|
|
1258
1288
|
@sync_compatible
|
1259
1289
|
async def visualize(self, *args, **kwargs):
|
@@ -1329,8 +1359,8 @@ def flow(
|
|
1329
1359
|
retries: Optional[int] = None,
|
1330
1360
|
retry_delay_seconds: Optional[Union[int, float]] = None,
|
1331
1361
|
task_runner: Optional[TaskRunner] = None,
|
1332
|
-
description: str = None,
|
1333
|
-
timeout_seconds: Union[int, float] = None,
|
1362
|
+
description: Optional[str] = None,
|
1363
|
+
timeout_seconds: Union[int, float, None] = None,
|
1334
1364
|
validate_parameters: bool = True,
|
1335
1365
|
persist_result: Optional[bool] = None,
|
1336
1366
|
result_storage: Optional[ResultStorage] = None,
|
@@ -1358,11 +1388,11 @@ def flow(
|
|
1358
1388
|
name: Optional[str] = None,
|
1359
1389
|
version: Optional[str] = None,
|
1360
1390
|
flow_run_name: Optional[Union[Callable[[], str], str]] = None,
|
1361
|
-
retries: int = None,
|
1362
|
-
retry_delay_seconds: Union[int, float] = None,
|
1391
|
+
retries: Optional[int] = None,
|
1392
|
+
retry_delay_seconds: Union[int, float, None] = None,
|
1363
1393
|
task_runner: Optional[TaskRunner] = None,
|
1364
|
-
description: str = None,
|
1365
|
-
timeout_seconds: Union[int, float] = None,
|
1394
|
+
description: Optional[str] = None,
|
1395
|
+
timeout_seconds: Union[int, float, None] = None,
|
1366
1396
|
validate_parameters: bool = True,
|
1367
1397
|
persist_result: Optional[bool] = None,
|
1368
1398
|
result_storage: Optional[ResultStorage] = None,
|
@@ -1485,6 +1515,9 @@ def flow(
|
|
1485
1515
|
>>> pass
|
1486
1516
|
"""
|
1487
1517
|
if __fn:
|
1518
|
+
if isinstance(__fn, (classmethod, staticmethod)):
|
1519
|
+
method_decorator = type(__fn).__name__
|
1520
|
+
raise TypeError(f"@{method_decorator} should be applied on top of @flow")
|
1488
1521
|
return cast(
|
1489
1522
|
Flow[P, R],
|
1490
1523
|
Flow(
|
@@ -1560,7 +1593,9 @@ flow.from_source = Flow.from_source
|
|
1560
1593
|
|
1561
1594
|
|
1562
1595
|
def select_flow(
|
1563
|
-
flows: Iterable[Flow],
|
1596
|
+
flows: Iterable[Flow],
|
1597
|
+
flow_name: Optional[str] = None,
|
1598
|
+
from_message: Optional[str] = None,
|
1564
1599
|
) -> Flow:
|
1565
1600
|
"""
|
1566
1601
|
Select the only flow in an iterable or a flow specified by name.
|
@@ -1574,33 +1609,33 @@ def select_flow(
|
|
1574
1609
|
UnspecifiedFlowError: If multiple flows exist but no flow name was provided
|
1575
1610
|
"""
|
1576
1611
|
# Convert to flows by name
|
1577
|
-
|
1612
|
+
flows_dict = {f.name: f for f in flows}
|
1578
1613
|
|
1579
1614
|
# Add a leading space if given, otherwise use an empty string
|
1580
1615
|
from_message = (" " + from_message) if from_message else ""
|
1581
|
-
if not
|
1616
|
+
if not Optional:
|
1582
1617
|
raise MissingFlowError(f"No flows found{from_message}.")
|
1583
1618
|
|
1584
|
-
elif flow_name and flow_name not in
|
1619
|
+
elif flow_name and flow_name not in flows_dict:
|
1585
1620
|
raise MissingFlowError(
|
1586
1621
|
f"Flow {flow_name!r} not found{from_message}. "
|
1587
|
-
f"Found the following flows: {listrepr(
|
1622
|
+
f"Found the following flows: {listrepr(flows_dict.keys())}. "
|
1588
1623
|
"Check to make sure that your flow function is decorated with `@flow`."
|
1589
1624
|
)
|
1590
1625
|
|
1591
|
-
elif not flow_name and len(
|
1626
|
+
elif not flow_name and len(flows_dict) > 1:
|
1592
1627
|
raise UnspecifiedFlowError(
|
1593
1628
|
(
|
1594
|
-
f"Found {len(
|
1595
|
-
f" {listrepr(sorted(
|
1629
|
+
f"Found {len(flows_dict)} flows{from_message}:"
|
1630
|
+
f" {listrepr(sorted(flows_dict.keys()))}. Specify a flow name to select a"
|
1596
1631
|
" flow."
|
1597
1632
|
),
|
1598
1633
|
)
|
1599
1634
|
|
1600
1635
|
if flow_name:
|
1601
|
-
return
|
1636
|
+
return flows_dict[flow_name]
|
1602
1637
|
else:
|
1603
|
-
return list(
|
1638
|
+
return list(flows_dict.values())[0]
|
1604
1639
|
|
1605
1640
|
|
1606
1641
|
def load_flows_from_script(path: str) -> List[Flow]:
|
@@ -1617,7 +1652,7 @@ def load_flows_from_script(path: str) -> List[Flow]:
|
|
1617
1652
|
return registry_from_script(path).get_instances(Flow)
|
1618
1653
|
|
1619
1654
|
|
1620
|
-
def load_flow_from_script(path: str, flow_name: str = None) -> Flow:
|
1655
|
+
def load_flow_from_script(path: str, flow_name: Optional[str] = None) -> Flow:
|
1621
1656
|
"""
|
1622
1657
|
Extract a flow object from a script by running all of the code in the file.
|
1623
1658
|
|
@@ -1661,7 +1696,7 @@ def load_flow_from_entrypoint(
|
|
1661
1696
|
FlowScriptError: If an exception is encountered while running the script
|
1662
1697
|
MissingFlowError: If the flow function specified in the entrypoint does not exist
|
1663
1698
|
"""
|
1664
|
-
with PrefectObjectRegistry(
|
1699
|
+
with PrefectObjectRegistry( # type: ignore
|
1665
1700
|
block_code_execution=True,
|
1666
1701
|
capture_failures=True,
|
1667
1702
|
):
|
@@ -1686,7 +1721,7 @@ def load_flow_from_entrypoint(
|
|
1686
1721
|
return flow
|
1687
1722
|
|
1688
1723
|
|
1689
|
-
def load_flow_from_text(script_contents: AnyStr, flow_name: str):
|
1724
|
+
def load_flow_from_text(script_contents: AnyStr, flow_name: str) -> Flow:
|
1690
1725
|
"""
|
1691
1726
|
Load a flow from a text script.
|
1692
1727
|
|
@@ -1717,7 +1752,7 @@ async def serve(
|
|
1717
1752
|
print_starting_message: bool = True,
|
1718
1753
|
limit: Optional[int] = None,
|
1719
1754
|
**kwargs,
|
1720
|
-
):
|
1755
|
+
) -> NoReturn:
|
1721
1756
|
"""
|
1722
1757
|
Serve the provided list of deployments.
|
1723
1758
|
|
@@ -1807,7 +1842,7 @@ async def load_flow_from_flow_run(
|
|
1807
1842
|
flow_run: "FlowRun",
|
1808
1843
|
ignore_storage: bool = False,
|
1809
1844
|
storage_base_path: Optional[str] = None,
|
1810
|
-
) ->
|
1845
|
+
) -> Flow:
|
1811
1846
|
"""
|
1812
1847
|
Load a flow from the location/script provided in a deployment's storage document.
|
1813
1848
|
|
@@ -1861,7 +1896,9 @@ async def load_flow_from_flow_run(
|
|
1861
1896
|
await storage_block.get_directory(from_path=from_path, local_path=".")
|
1862
1897
|
|
1863
1898
|
if deployment.pull_steps:
|
1864
|
-
run_logger.debug(
|
1899
|
+
run_logger.debug(
|
1900
|
+
f"Running {len(deployment.pull_steps)} deployment pull step(s)"
|
1901
|
+
)
|
1865
1902
|
output = await run_steps(deployment.pull_steps)
|
1866
1903
|
if output.get("directory"):
|
1867
1904
|
run_logger.debug(f"Changing working directory to {output['directory']!r}")
|
@@ -1933,11 +1970,33 @@ def load_flow_argument_from_entrypoint(
|
|
1933
1970
|
):
|
1934
1971
|
for keyword in decorator.keywords:
|
1935
1972
|
if keyword.arg == arg:
|
1936
|
-
|
1937
|
-
|
1938
|
-
|
1973
|
+
if isinstance(keyword.value, ast.Constant):
|
1974
|
+
return (
|
1975
|
+
keyword.value.value
|
1976
|
+
) # Return the string value of the argument
|
1977
|
+
|
1978
|
+
# if the arg value is not a raw str (i.e. a variable or expression),
|
1979
|
+
# then attempt to evaluate it
|
1980
|
+
namespace = safe_load_namespace(source_code)
|
1981
|
+
literal_arg_value = ast.get_source_segment(
|
1982
|
+
source_code, keyword.value
|
1983
|
+
)
|
1984
|
+
try:
|
1985
|
+
evaluated_value = eval(literal_arg_value, namespace) # type: ignore
|
1986
|
+
except Exception as e:
|
1987
|
+
logger.info(
|
1988
|
+
"Failed to parse @flow argument: `%s=%s` due to the following error. Ignoring and falling back to default behavior.",
|
1989
|
+
arg,
|
1990
|
+
literal_arg_value,
|
1991
|
+
exc_info=e,
|
1992
|
+
)
|
1993
|
+
# ignore the decorator arg and fallback to default behavior
|
1994
|
+
break
|
1995
|
+
return str(evaluated_value)
|
1939
1996
|
|
1940
1997
|
if arg == "name":
|
1941
1998
|
return func_name.replace(
|
1942
1999
|
"_", "-"
|
1943
2000
|
) # If no matching decorator or keyword argument is found
|
2001
|
+
|
2002
|
+
return None
|
prefect/futures.py
CHANGED
@@ -56,7 +56,7 @@ class PrefectFuture(abc.ABC):
|
|
56
56
|
def wait(self, timeout: Optional[float] = None) -> None:
|
57
57
|
...
|
58
58
|
"""
|
59
|
-
Wait for the task run to complete.
|
59
|
+
Wait for the task run to complete.
|
60
60
|
|
61
61
|
If the task run has already completed, this method will return immediately.
|
62
62
|
|
@@ -163,6 +163,10 @@ class PrefectDistributedFuture(PrefectFuture):
|
|
163
163
|
)
|
164
164
|
return
|
165
165
|
|
166
|
+
# Ask for the instance of TaskRunWaiter _now_ so that it's already running and
|
167
|
+
# can catch the completion event if it happens before we start listening for it.
|
168
|
+
TaskRunWaiter.instance()
|
169
|
+
|
166
170
|
# Read task run to see if it is still running
|
167
171
|
async with get_client() as client:
|
168
172
|
task_run = await client.read_task_run(task_run_id=self._task_run_id)
|
@@ -245,6 +249,10 @@ def resolve_futures_to_states(
|
|
245
249
|
context={},
|
246
250
|
)
|
247
251
|
|
252
|
+
# if no futures were found, return the original expression
|
253
|
+
if not futures:
|
254
|
+
return expr
|
255
|
+
|
248
256
|
# Get final states for each future
|
249
257
|
states = []
|
250
258
|
for future in futures:
|
@@ -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:
|