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.
Files changed (69) hide show
  1. prefect/__init__.py +0 -1
  2. prefect/_internal/compatibility/migration.py +124 -0
  3. prefect/_internal/concurrency/__init__.py +2 -2
  4. prefect/_internal/concurrency/primitives.py +1 -0
  5. prefect/_internal/pydantic/annotations/pendulum.py +2 -2
  6. prefect/_internal/pytz.py +1 -1
  7. prefect/blocks/core.py +1 -1
  8. prefect/client/orchestration.py +96 -22
  9. prefect/client/schemas/actions.py +1 -1
  10. prefect/client/schemas/filters.py +6 -0
  11. prefect/client/schemas/objects.py +10 -3
  12. prefect/client/subscriptions.py +6 -5
  13. prefect/context.py +1 -27
  14. prefect/deployments/__init__.py +3 -0
  15. prefect/deployments/base.py +4 -2
  16. prefect/deployments/deployments.py +3 -0
  17. prefect/deployments/steps/pull.py +1 -0
  18. prefect/deployments/steps/utility.py +2 -1
  19. prefect/engine.py +3 -0
  20. prefect/events/cli/automations.py +1 -1
  21. prefect/events/clients.py +7 -1
  22. prefect/exceptions.py +9 -0
  23. prefect/filesystems.py +22 -11
  24. prefect/flow_engine.py +195 -153
  25. prefect/flows.py +95 -36
  26. prefect/futures.py +9 -1
  27. prefect/infrastructure/provisioners/container_instance.py +1 -0
  28. prefect/infrastructure/provisioners/ecs.py +2 -2
  29. prefect/input/__init__.py +4 -0
  30. prefect/logging/formatters.py +2 -2
  31. prefect/logging/handlers.py +2 -2
  32. prefect/logging/loggers.py +1 -1
  33. prefect/plugins.py +1 -0
  34. prefect/records/cache_policies.py +3 -3
  35. prefect/records/result_store.py +10 -3
  36. prefect/results.py +47 -73
  37. prefect/runner/runner.py +1 -1
  38. prefect/runner/server.py +1 -1
  39. prefect/runtime/__init__.py +1 -0
  40. prefect/runtime/deployment.py +1 -0
  41. prefect/runtime/flow_run.py +1 -0
  42. prefect/runtime/task_run.py +1 -0
  43. prefect/settings.py +16 -3
  44. prefect/states.py +15 -4
  45. prefect/task_engine.py +195 -39
  46. prefect/task_runners.py +9 -3
  47. prefect/task_runs.py +26 -12
  48. prefect/task_worker.py +149 -20
  49. prefect/tasks.py +153 -71
  50. prefect/transactions.py +85 -15
  51. prefect/types/__init__.py +10 -3
  52. prefect/utilities/asyncutils.py +3 -3
  53. prefect/utilities/callables.py +16 -4
  54. prefect/utilities/collections.py +120 -57
  55. prefect/utilities/dockerutils.py +5 -3
  56. prefect/utilities/engine.py +11 -0
  57. prefect/utilities/filesystem.py +4 -5
  58. prefect/utilities/importtools.py +29 -0
  59. prefect/utilities/services.py +2 -2
  60. prefect/utilities/urls.py +195 -0
  61. prefect/utilities/visualization.py +1 -0
  62. prefect/variables.py +4 -0
  63. prefect/workers/base.py +35 -0
  64. {prefect_client-3.0.0rc2.dist-info → prefect_client-3.0.0rc4.dist-info}/METADATA +2 -2
  65. {prefect_client-3.0.0rc2.dist-info → prefect_client-3.0.0rc4.dist-info}/RECORD +68 -66
  66. prefect/blocks/kubernetes.py +0 -115
  67. {prefect_client-3.0.0rc2.dist-info → prefect_client-3.0.0rc4.dist-info}/LICENSE +0 -0
  68. {prefect_client-3.0.0rc2.dist-info → prefect_client-3.0.0rc4.dist-info}/WHEEL +0 -0
  69. {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 get_or_create_default_task_scheduling_storage() -> ResultStorage:
67
+ async def _get_or_create_default_storage(block_document_slug: str) -> ResultStorage:
80
68
  """
81
- Generate a default file system for background task parameter/result storage.
69
+ Generate a default file system for storage.
82
70
  """
83
71
  default_storage_name, storage_path = cache_key = (
84
- PREFECT_TASK_SCHEDULING_DEFAULT_STORAGE_BLOCK.value(),
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 Exception(
100
- "The default task storage block does not exist, but it is of type "
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 _default_task_scheduling_storages[cache_key]
105
+ return _default_storages[cache_key]
118
106
  except KeyError:
119
107
  storage = await get_storage()
120
- _default_task_scheduling_storages[cache_key] = storage
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("result_storage", await get_default_result_storage())
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
- 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
- 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(self, obj: R, key: str = None) -> Union[R, "BaseResult[R]"]:
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(serializer=serializer, data=data)
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
@@ -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
 
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(seconds=30),
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, result_factory: ResultFactory, key: str = None
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(state.data, key=key)
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(retval, key=key),
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(data=await result_factory.create_result(data, key=key))
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