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.
Files changed (65) hide show
  1. prefect/_internal/compatibility/migration.py +124 -0
  2. prefect/_internal/concurrency/__init__.py +2 -2
  3. prefect/_internal/concurrency/primitives.py +1 -0
  4. prefect/_internal/pydantic/annotations/pendulum.py +2 -2
  5. prefect/_internal/pytz.py +1 -1
  6. prefect/blocks/core.py +1 -1
  7. prefect/client/orchestration.py +96 -22
  8. prefect/client/schemas/actions.py +1 -1
  9. prefect/client/schemas/filters.py +6 -0
  10. prefect/client/schemas/objects.py +10 -3
  11. prefect/client/subscriptions.py +3 -2
  12. prefect/context.py +1 -27
  13. prefect/deployments/__init__.py +3 -0
  14. prefect/deployments/base.py +4 -2
  15. prefect/deployments/deployments.py +3 -0
  16. prefect/deployments/steps/pull.py +1 -0
  17. prefect/deployments/steps/utility.py +2 -1
  18. prefect/engine.py +3 -0
  19. prefect/events/cli/automations.py +1 -1
  20. prefect/events/clients.py +7 -1
  21. prefect/exceptions.py +9 -0
  22. prefect/filesystems.py +22 -11
  23. prefect/flow_engine.py +116 -154
  24. prefect/flows.py +83 -34
  25. prefect/infrastructure/provisioners/container_instance.py +1 -0
  26. prefect/infrastructure/provisioners/ecs.py +2 -2
  27. prefect/input/__init__.py +4 -0
  28. prefect/logging/formatters.py +2 -2
  29. prefect/logging/handlers.py +2 -2
  30. prefect/logging/loggers.py +1 -1
  31. prefect/plugins.py +1 -0
  32. prefect/records/cache_policies.py +3 -3
  33. prefect/records/result_store.py +10 -3
  34. prefect/results.py +27 -55
  35. prefect/runner/runner.py +1 -1
  36. prefect/runner/server.py +1 -1
  37. prefect/runtime/__init__.py +1 -0
  38. prefect/runtime/deployment.py +1 -0
  39. prefect/runtime/flow_run.py +1 -0
  40. prefect/runtime/task_run.py +1 -0
  41. prefect/settings.py +15 -2
  42. prefect/states.py +15 -4
  43. prefect/task_engine.py +190 -33
  44. prefect/task_runners.py +9 -3
  45. prefect/task_runs.py +3 -3
  46. prefect/task_worker.py +29 -9
  47. prefect/tasks.py +133 -57
  48. prefect/transactions.py +87 -15
  49. prefect/types/__init__.py +1 -1
  50. prefect/utilities/asyncutils.py +3 -3
  51. prefect/utilities/callables.py +16 -4
  52. prefect/utilities/dockerutils.py +5 -3
  53. prefect/utilities/engine.py +11 -0
  54. prefect/utilities/filesystem.py +4 -5
  55. prefect/utilities/importtools.py +29 -0
  56. prefect/utilities/services.py +2 -2
  57. prefect/utilities/urls.py +195 -0
  58. prefect/utilities/visualization.py +1 -0
  59. prefect/variables.py +4 -0
  60. prefect/workers/base.py +35 -0
  61. {prefect_client-3.0.0rc2.dist-info → prefect_client-3.0.0rc3.dist-info}/METADATA +2 -2
  62. {prefect_client-3.0.0rc2.dist-info → prefect_client-3.0.0rc3.dist-info}/RECORD +65 -62
  63. {prefect_client-3.0.0rc2.dist-info → prefect_client-3.0.0rc3.dist-info}/LICENSE +0 -0
  64. {prefect_client-3.0.0rc2.dist-info → prefect_client-3.0.0rc3.dist-info}/WHEEL +0 -0
  65. {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, run_flow_sync
1269
+ from prefect.flow_engine import run_flow
1245
1270
 
1246
- run_kwargs = dict(
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], flow_name: str = None, from_message: str = None
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
- flows = {f.name: f for f in flows}
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 flows:
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 flows:
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(flows.keys())}. "
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(flows) > 1:
1616
+ elif not flow_name and len(flows_dict) > 1:
1592
1617
  raise UnspecifiedFlowError(
1593
1618
  (
1594
- f"Found {len(flows)} flows{from_message}:"
1595
- f" {listrepr(sorted(flows.keys()))}. Specify a flow name to select a"
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 flows[flow_name]
1626
+ return flows_dict[flow_name]
1602
1627
  else:
1603
- return list(flows.values())[0]
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
- ) -> "Flow":
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(f"Running {len(deployment.pull_steps)} deployment pull steps")
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
- return (
1937
- keyword.value.value
1938
- ) # Return the string value of the argument
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
@@ -10,6 +10,7 @@ Classes:
10
10
  ContainerInstancePushProvisioner: A class for provisioning infrastructure using Azure Container Instances.
11
11
 
12
12
  """
13
+
13
14
  import json
14
15
  import random
15
16
  import shlex
@@ -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
  ]
@@ -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
@@ -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 a new thread and continue
112
- from_sync.call_soon_in_new_thread(create_call(APILogWorker.drain_all))
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
@@ -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
@@ -7,6 +7,7 @@ Currently supported entrypoints:
7
7
  - prefect.collections: Identifies this package as a Prefect collection that
8
8
  should be imported when Prefect is imported.
9
9
  """
10
+
10
11
  import sys
11
12
  from types import ModuleType
12
13
  from typing import Any, Dict, Union
@@ -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,
@@ -1,7 +1,8 @@
1
1
  from dataclasses import dataclass
2
2
  from typing import Any
3
3
 
4
- from prefect.exceptions import ObjectNotFound
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 True
22
- except (ObjectNotFound, ValueError):
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
- 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
- )
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(self, obj: R, key: str = None) -> Union[R, "BaseResult[R]"]:
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(serializer=serializer, data=data)
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
@@ -209,7 +209,7 @@ class Runner:
209
209
  async def add_flow(
210
210
  self,
211
211
  flow: Flow,
212
- name: str = None,
212
+ name: Optional[str] = None,
213
213
  interval: Optional[
214
214
  Union[
215
215
  Iterable[Union[int, float, datetime.timedelta]],
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()
@@ -8,6 +8,7 @@ Example usage:
8
8
  print(f"This script is running from deployment {deployment.id} with parameters {deployment.parameters}")
9
9
  ```
10
10
  """
11
+
11
12
  import prefect.runtime.deployment
12
13
  import prefect.runtime.flow_run
13
14
  import prefect.runtime.task_run
@@ -24,6 +24,7 @@ Available attributes:
24
24
  include default values set on the flow function, only the parameter values set on the deployment
25
25
  object or those directly provided via API for this run
26
26
  """
27
+
27
28
  import os
28
29
  from typing import Any, Dict, List, Optional
29
30
 
@@ -18,6 +18,7 @@ Available attributes:
18
18
  - `parent_deployment_id`: the ID of the deployment that triggered this run, if any
19
19
  - `run_count`: the number of times this flow run has been run
20
20
  """
21
+
21
22
  import os
22
23
  from typing import Any, Dict, List, Optional
23
24
 
@@ -14,6 +14,7 @@ Available attributes:
14
14
  - `run_count`: the number of times this task run has been run
15
15
  - `task_name`: the name of the task
16
16
  """
17
+
17
18
  import os
18
19
  from typing import Any, Dict, List, Optional
19
20