prefect-client 3.0.0rc18__py3-none-any.whl → 3.0.0rc20__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 (55) hide show
  1. prefect/__init__.py +0 -3
  2. prefect/_internal/concurrency/services.py +14 -0
  3. prefect/_internal/schemas/bases.py +1 -0
  4. prefect/blocks/core.py +41 -30
  5. prefect/blocks/system.py +48 -12
  6. prefect/client/cloud.py +56 -7
  7. prefect/client/collections.py +1 -1
  8. prefect/client/orchestration.py +111 -8
  9. prefect/client/schemas/objects.py +40 -2
  10. prefect/concurrency/asyncio.py +8 -2
  11. prefect/concurrency/services.py +16 -6
  12. prefect/concurrency/sync.py +4 -1
  13. prefect/concurrency/v1/__init__.py +0 -0
  14. prefect/concurrency/v1/asyncio.py +143 -0
  15. prefect/concurrency/v1/context.py +27 -0
  16. prefect/concurrency/v1/events.py +61 -0
  17. prefect/concurrency/v1/services.py +116 -0
  18. prefect/concurrency/v1/sync.py +92 -0
  19. prefect/context.py +2 -2
  20. prefect/deployments/flow_runs.py +0 -7
  21. prefect/deployments/runner.py +11 -0
  22. prefect/events/clients.py +41 -0
  23. prefect/events/related.py +72 -73
  24. prefect/events/utilities.py +2 -0
  25. prefect/events/worker.py +12 -3
  26. prefect/exceptions.py +6 -0
  27. prefect/flow_engine.py +5 -0
  28. prefect/flows.py +9 -2
  29. prefect/logging/handlers.py +4 -1
  30. prefect/main.py +8 -6
  31. prefect/records/base.py +74 -18
  32. prefect/records/filesystem.py +207 -0
  33. prefect/records/memory.py +16 -3
  34. prefect/records/result_store.py +19 -14
  35. prefect/results.py +232 -169
  36. prefect/runner/runner.py +7 -4
  37. prefect/settings.py +14 -15
  38. prefect/states.py +73 -18
  39. prefect/task_engine.py +127 -221
  40. prefect/task_worker.py +7 -39
  41. prefect/tasks.py +0 -7
  42. prefect/transactions.py +89 -27
  43. prefect/utilities/annotations.py +4 -3
  44. prefect/utilities/asyncutils.py +4 -4
  45. prefect/utilities/callables.py +1 -3
  46. prefect/utilities/dispatch.py +16 -11
  47. prefect/utilities/engine.py +1 -4
  48. prefect/utilities/schema_tools/hydration.py +13 -0
  49. prefect/workers/base.py +78 -18
  50. {prefect_client-3.0.0rc18.dist-info → prefect_client-3.0.0rc20.dist-info}/METADATA +3 -4
  51. {prefect_client-3.0.0rc18.dist-info → prefect_client-3.0.0rc20.dist-info}/RECORD +54 -48
  52. prefect/manifests.py +0 -21
  53. {prefect_client-3.0.0rc18.dist-info → prefect_client-3.0.0rc20.dist-info}/LICENSE +0 -0
  54. {prefect_client-3.0.0rc18.dist-info → prefect_client-3.0.0rc20.dist-info}/WHEEL +0 -0
  55. {prefect_client-3.0.0rc18.dist-info → prefect_client-3.0.0rc20.dist-info}/top_level.txt +0 -0
prefect/events/related.py CHANGED
@@ -21,6 +21,7 @@ from .schemas.events import RelatedResource
21
21
 
22
22
  if TYPE_CHECKING:
23
23
  from prefect._internal.schemas.bases import ObjectBaseModel
24
+ from prefect.client.orchestration import PrefectClient
24
25
 
25
26
  ResourceCacheEntry = Dict[str, Union[str, "ObjectBaseModel", None]]
26
27
  RelatedResourceCache = Dict[str, Tuple[ResourceCacheEntry, DateTime]]
@@ -54,9 +55,9 @@ def object_as_related_resource(kind: str, role: str, object: Any) -> RelatedReso
54
55
 
55
56
 
56
57
  async def related_resources_from_run_context(
58
+ client: "PrefectClient",
57
59
  exclude: Optional[Set[str]] = None,
58
60
  ) -> List[RelatedResource]:
59
- from prefect.client.orchestration import get_client
60
61
  from prefect.client.schemas.objects import FlowRun
61
62
  from prefect.context import FlowRunContext, TaskRunContext
62
63
 
@@ -77,86 +78,84 @@ async def related_resources_from_run_context(
77
78
 
78
79
  related_objects: List[ResourceCacheEntry] = []
79
80
 
80
- async with get_client() as client:
81
+ async def dummy_read():
82
+ return {}
81
83
 
82
- async def dummy_read():
83
- return {}
84
-
85
- if flow_run_context:
86
- related_objects.append(
87
- {
88
- "kind": "flow-run",
89
- "role": "flow-run",
90
- "object": flow_run_context.flow_run,
91
- },
92
- )
93
- else:
94
- related_objects.append(
95
- await _get_and_cache_related_object(
96
- kind="flow-run",
97
- role="flow-run",
98
- client_method=client.read_flow_run,
99
- obj_id=flow_run_id,
100
- cache=RESOURCE_CACHE,
101
- )
84
+ if flow_run_context:
85
+ related_objects.append(
86
+ {
87
+ "kind": "flow-run",
88
+ "role": "flow-run",
89
+ "object": flow_run_context.flow_run,
90
+ },
91
+ )
92
+ else:
93
+ related_objects.append(
94
+ await _get_and_cache_related_object(
95
+ kind="flow-run",
96
+ role="flow-run",
97
+ client_method=client.read_flow_run,
98
+ obj_id=flow_run_id,
99
+ cache=RESOURCE_CACHE,
102
100
  )
101
+ )
103
102
 
104
- if task_run_context:
105
- related_objects.append(
106
- {
107
- "kind": "task-run",
108
- "role": "task-run",
109
- "object": task_run_context.task_run,
110
- },
111
- )
103
+ if task_run_context:
104
+ related_objects.append(
105
+ {
106
+ "kind": "task-run",
107
+ "role": "task-run",
108
+ "object": task_run_context.task_run,
109
+ },
110
+ )
112
111
 
113
- flow_run = related_objects[0]["object"]
112
+ flow_run = related_objects[0]["object"]
114
113
 
115
- if isinstance(flow_run, FlowRun):
116
- related_objects += list(
117
- await asyncio.gather(
114
+ if isinstance(flow_run, FlowRun):
115
+ related_objects += list(
116
+ await asyncio.gather(
117
+ _get_and_cache_related_object(
118
+ kind="flow",
119
+ role="flow",
120
+ client_method=client.read_flow,
121
+ obj_id=flow_run.flow_id,
122
+ cache=RESOURCE_CACHE,
123
+ ),
124
+ (
125
+ _get_and_cache_related_object(
126
+ kind="deployment",
127
+ role="deployment",
128
+ client_method=client.read_deployment,
129
+ obj_id=flow_run.deployment_id,
130
+ cache=RESOURCE_CACHE,
131
+ )
132
+ if flow_run.deployment_id
133
+ else dummy_read()
134
+ ),
135
+ (
136
+ _get_and_cache_related_object(
137
+ kind="work-queue",
138
+ role="work-queue",
139
+ client_method=client.read_work_queue,
140
+ obj_id=flow_run.work_queue_id,
141
+ cache=RESOURCE_CACHE,
142
+ )
143
+ if flow_run.work_queue_id
144
+ else dummy_read()
145
+ ),
146
+ (
118
147
  _get_and_cache_related_object(
119
- kind="flow",
120
- role="flow",
121
- client_method=client.read_flow,
122
- obj_id=flow_run.flow_id,
148
+ kind="work-pool",
149
+ role="work-pool",
150
+ client_method=client.read_work_pool,
151
+ obj_id=flow_run.work_pool_name,
123
152
  cache=RESOURCE_CACHE,
124
- ),
125
- (
126
- _get_and_cache_related_object(
127
- kind="deployment",
128
- role="deployment",
129
- client_method=client.read_deployment,
130
- obj_id=flow_run.deployment_id,
131
- cache=RESOURCE_CACHE,
132
- )
133
- if flow_run.deployment_id
134
- else dummy_read()
135
- ),
136
- (
137
- _get_and_cache_related_object(
138
- kind="work-queue",
139
- role="work-queue",
140
- client_method=client.read_work_queue,
141
- obj_id=flow_run.work_queue_id,
142
- cache=RESOURCE_CACHE,
143
- )
144
- if flow_run.work_queue_id
145
- else dummy_read()
146
- ),
147
- (
148
- _get_and_cache_related_object(
149
- kind="work-pool",
150
- role="work-pool",
151
- client_method=client.read_work_pool,
152
- obj_id=flow_run.work_pool_name,
153
- cache=RESOURCE_CACHE,
154
- )
155
- if flow_run.work_pool_name
156
- else dummy_read()
157
- ),
158
- )
153
+ )
154
+ if flow_run.work_pool_name
155
+ else dummy_read()
156
+ ),
159
157
  )
158
+ )
160
159
 
161
160
  related = []
162
161
  tags = set()
@@ -7,6 +7,7 @@ from pydantic_extra_types.pendulum_dt import DateTime
7
7
 
8
8
  from .clients import (
9
9
  AssertingEventsClient,
10
+ AssertingPassthroughEventsClient,
10
11
  PrefectCloudEventsClient,
11
12
  PrefectEventsClient,
12
13
  )
@@ -49,6 +50,7 @@ def emit_event(
49
50
  return None
50
51
 
51
52
  operational_clients = [
53
+ AssertingPassthroughEventsClient,
52
54
  AssertingEventsClient,
53
55
  PrefectCloudEventsClient,
54
56
  PrefectEventsClient,
prefect/events/worker.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from contextlib import asynccontextmanager
2
2
  from contextvars import Context, copy_context
3
- from typing import Any, Dict, Optional, Tuple, Type
3
+ from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Type
4
4
  from uuid import UUID
5
5
 
6
6
  from typing_extensions import Self
@@ -22,6 +22,9 @@ from .clients import (
22
22
  from .related import related_resources_from_run_context
23
23
  from .schemas.events import Event
24
24
 
25
+ if TYPE_CHECKING:
26
+ from prefect.client.orchestration import PrefectClient
27
+
25
28
 
26
29
  def should_emit_events() -> bool:
27
30
  return (
@@ -55,14 +58,18 @@ class EventsWorker(QueueService[Event]):
55
58
  self.client_type = client_type
56
59
  self.client_options = client_options
57
60
  self._client: EventsClient
61
+ self._orchestration_client: "PrefectClient"
58
62
  self._context_cache: Dict[UUID, Context] = {}
59
63
 
60
64
  @asynccontextmanager
61
65
  async def _lifespan(self):
62
66
  self._client = self.client_type(**{k: v for k, v in self.client_options})
67
+ from prefect.client.orchestration import get_client
63
68
 
69
+ self._orchestration_client = get_client()
64
70
  async with self._client:
65
- yield
71
+ async with self._orchestration_client:
72
+ yield
66
73
 
67
74
  def _prepare_item(self, event: Event) -> Event:
68
75
  self._context_cache[event.id] = copy_context()
@@ -77,7 +84,9 @@ class EventsWorker(QueueService[Event]):
77
84
 
78
85
  async def attach_related_resources_from_context(self, event: Event):
79
86
  exclude = {resource.id for resource in event.involved_resources}
80
- event.related += await related_resources_from_run_context(exclude=exclude)
87
+ event.related += await related_resources_from_run_context(
88
+ client=self._orchestration_client, exclude=exclude
89
+ )
81
90
 
82
91
  @classmethod
83
92
  def instance(
prefect/exceptions.py CHANGED
@@ -412,3 +412,9 @@ class PrefectImportError(ImportError):
412
412
 
413
413
  def __init__(self, message: str) -> None:
414
414
  super().__init__(message)
415
+
416
+
417
+ class SerializationError(PrefectException):
418
+ """
419
+ Raised when an object cannot be serialized.
420
+ """
prefect/flow_engine.py CHANGED
@@ -30,6 +30,7 @@ from prefect.client.schemas import FlowRun, TaskRun
30
30
  from prefect.client.schemas.filters import FlowRunFilter
31
31
  from prefect.client.schemas.sorting import FlowRunSort
32
32
  from prefect.concurrency.context import ConcurrencyContext
33
+ from prefect.concurrency.v1.context import ConcurrencyContext as ConcurrencyContextV1
33
34
  from prefect.context import FlowRunContext, SyncClientContext, TagsContext
34
35
  from prefect.exceptions import (
35
36
  Abort,
@@ -204,6 +205,7 @@ class FlowRunEngine(Generic[P, R]):
204
205
  result_factory=run_coro_as_sync(ResultFactory.from_flow(self.flow)),
205
206
  )
206
207
  self.short_circuit = True
208
+ self.call_hooks()
207
209
 
208
210
  new_state = Running()
209
211
  state = self.set_state(new_state)
@@ -267,6 +269,7 @@ class FlowRunEngine(Generic[P, R]):
267
269
  return_value_to_state(
268
270
  resolved_result,
269
271
  result_factory=result_factory,
272
+ write_result=True,
270
273
  )
271
274
  )
272
275
  self.set_state(terminal_state)
@@ -286,6 +289,7 @@ class FlowRunEngine(Generic[P, R]):
286
289
  message=msg or "Flow run encountered an exception:",
287
290
  result_factory=result_factory
288
291
  or getattr(context, "result_factory", None),
292
+ write_result=True,
289
293
  )
290
294
  )
291
295
  state = self.set_state(terminal_state)
@@ -506,6 +510,7 @@ class FlowRunEngine(Generic[P, R]):
506
510
  task_runner=task_runner,
507
511
  )
508
512
  )
513
+ stack.enter_context(ConcurrencyContextV1())
509
514
  stack.enter_context(ConcurrencyContext())
510
515
 
511
516
  # set the logger to the flow run logger
prefect/flows.py CHANGED
@@ -642,7 +642,8 @@ class Flow(Generic[P, R]):
642
642
  cron: Optional[Union[Iterable[str], str]] = None,
643
643
  rrule: Optional[Union[Iterable[str], str]] = None,
644
644
  paused: Optional[bool] = None,
645
- schedules: Optional[List["FlexibleScheduleList"]] = None,
645
+ schedules: Optional["FlexibleScheduleList"] = None,
646
+ concurrency_limit: Optional[int] = None,
646
647
  parameters: Optional[dict] = None,
647
648
  triggers: Optional[List[Union[DeploymentTriggerTypes, TriggerTypes]]] = None,
648
649
  description: Optional[str] = None,
@@ -666,6 +667,7 @@ class Flow(Generic[P, R]):
666
667
  paused: Whether or not to set this deployment as paused.
667
668
  schedules: A list of schedule objects defining when to execute runs of this deployment.
668
669
  Used to define multiple schedules or additional scheduling options such as `timezone`.
670
+ concurrency_limit: The maximum number of runs of this deployment that can run at the same time.
669
671
  parameters: A dictionary of default parameter values to pass to runs of this deployment.
670
672
  triggers: A list of triggers that will kick off runs of this deployment.
671
673
  description: A description for the created deployment. Defaults to the flow's
@@ -718,6 +720,7 @@ class Flow(Generic[P, R]):
718
720
  rrule=rrule,
719
721
  paused=paused,
720
722
  schedules=schedules,
723
+ concurrency_limit=concurrency_limit,
721
724
  tags=tags,
722
725
  triggers=triggers,
723
726
  parameters=parameters or {},
@@ -727,7 +730,7 @@ class Flow(Generic[P, R]):
727
730
  work_pool_name=work_pool_name,
728
731
  work_queue_name=work_queue_name,
729
732
  job_variables=job_variables,
730
- )
733
+ ) # type: ignore # TODO: remove sync_compatible
731
734
  else:
732
735
  return RunnerDeployment.from_flow(
733
736
  self,
@@ -737,6 +740,7 @@ class Flow(Generic[P, R]):
737
740
  rrule=rrule,
738
741
  paused=paused,
739
742
  schedules=schedules,
743
+ concurrency_limit=concurrency_limit,
740
744
  tags=tags,
741
745
  triggers=triggers,
742
746
  parameters=parameters or {},
@@ -1055,6 +1059,7 @@ class Flow(Generic[P, R]):
1055
1059
  rrule: Optional[str] = None,
1056
1060
  paused: Optional[bool] = None,
1057
1061
  schedules: Optional[List[DeploymentScheduleCreate]] = None,
1062
+ concurrency_limit: Optional[int] = None,
1058
1063
  triggers: Optional[List[Union[DeploymentTriggerTypes, TriggerTypes]]] = None,
1059
1064
  parameters: Optional[dict] = None,
1060
1065
  description: Optional[str] = None,
@@ -1101,6 +1106,7 @@ class Flow(Generic[P, R]):
1101
1106
  paused: Whether or not to set this deployment as paused.
1102
1107
  schedules: A list of schedule objects defining when to execute runs of this deployment.
1103
1108
  Used to define multiple schedules or additional scheduling options like `timezone`.
1109
+ concurrency_limit: The maximum number of runs that can be executed concurrently.
1104
1110
  parameters: A dictionary of default parameter values to pass to runs of this deployment.
1105
1111
  description: A description for the created deployment. Defaults to the flow's
1106
1112
  description if not provided.
@@ -1175,6 +1181,7 @@ class Flow(Generic[P, R]):
1175
1181
  cron=cron,
1176
1182
  rrule=rrule,
1177
1183
  schedules=schedules,
1184
+ concurrency_limit=concurrency_limit,
1178
1185
  paused=paused,
1179
1186
  triggers=triggers,
1180
1187
  parameters=parameters,
@@ -157,7 +157,10 @@ class APILogHandler(logging.Handler):
157
157
  if log_handling_when_missing_flow == "warn":
158
158
  # Warn when a logger is used outside of a run context, the stack level here
159
159
  # gets us to the user logging call
160
- warnings.warn(str(exc), stacklevel=8)
160
+ warnings.warn(
161
+ f"{exc} Set PREFECT_LOGGING_TO_API_WHEN_MISSING_FLOW=ignore to suppress this warning.",
162
+ stacklevel=8,
163
+ )
161
164
  return
162
165
  elif log_handling_when_missing_flow == "ignore":
163
166
  return
prefect/main.py CHANGED
@@ -6,7 +6,6 @@ from prefect.flows import flow, Flow, serve
6
6
  from prefect.transactions import Transaction
7
7
  from prefect.tasks import task, Task
8
8
  from prefect.context import tags
9
- from prefect.manifests import Manifest
10
9
  from prefect.utilities.annotations import unmapped, allow_failure
11
10
  from prefect.results import BaseResult
12
11
  from prefect.flow_runs import pause_flow_run, resume_flow_run, suspend_flow_run
@@ -26,10 +25,14 @@ import prefect.context
26
25
  # Perform any forward-ref updates needed for Pydantic models
27
26
  import prefect.client.schemas
28
27
 
29
- prefect.context.FlowRunContext.model_rebuild()
30
- prefect.context.TaskRunContext.model_rebuild()
31
- prefect.client.schemas.State.model_rebuild()
32
- prefect.client.schemas.StateCreate.model_rebuild()
28
+ prefect.context.FlowRunContext.model_rebuild(
29
+ _types_namespace={"Flow": Flow, "BaseResult": BaseResult}
30
+ )
31
+ prefect.context.TaskRunContext.model_rebuild(_types_namespace={"Task": Task})
32
+ prefect.client.schemas.State.model_rebuild(_types_namespace={"BaseResult": BaseResult})
33
+ prefect.client.schemas.StateCreate.model_rebuild(
34
+ _types_namespace={"BaseResult": BaseResult}
35
+ )
33
36
  Transaction.model_rebuild()
34
37
 
35
38
  # Configure logging
@@ -55,7 +58,6 @@ __all__ = [
55
58
  "Flow",
56
59
  "get_client",
57
60
  "get_run_logger",
58
- "Manifest",
59
61
  "State",
60
62
  "tags",
61
63
  "task",
prefect/records/base.py CHANGED
@@ -1,22 +1,88 @@
1
+ import abc
1
2
  import os
2
3
  import socket
3
4
  import threading
4
5
  from contextlib import contextmanager
5
6
  from dataclasses import dataclass
6
- from typing import Optional
7
+ from typing import TYPE_CHECKING, Optional
7
8
 
8
- from prefect.results import BaseResult
9
+ if TYPE_CHECKING:
10
+ from prefect.results import BaseResult
11
+ from prefect.transactions import IsolationLevel
9
12
 
10
13
 
11
- class RecordStore:
12
- def read(self, key: str):
13
- raise NotImplementedError
14
+ @dataclass
15
+ class TransactionRecord:
16
+ """
17
+ A dataclass representation of a transaction record.
18
+ """
14
19
 
15
- def write(self, key: str, value: dict):
16
- raise NotImplementedError
20
+ key: str
21
+ result: "BaseResult"
22
+
23
+
24
+ class RecordStore(abc.ABC):
25
+ @abc.abstractmethod
26
+ def read(
27
+ self, key: str, holder: Optional[str] = None
28
+ ) -> Optional[TransactionRecord]:
29
+ """
30
+ Read the transaction record with the given key.
31
+
32
+ Args:
33
+ key: Unique identifier for the transaction record.
34
+ holder: Unique identifier for the holder of the lock. If a lock exists on
35
+ the record being written, the read will be blocked until the lock is
36
+ released if the provided holder does not match the holder of the lock.
37
+ If not provided, a default holder based on the current host, process,
38
+ and thread will be used.
39
+
40
+ Returns:
41
+ TransactionRecord: The transaction record with the given key.
42
+ """
43
+ ...
44
+
45
+ @abc.abstractmethod
46
+ def write(self, key: str, result: "BaseResult", holder: Optional[str] = None):
47
+ """
48
+ Write the transaction record with the given key.
49
+
50
+ Args:
51
+ key: Unique identifier for the transaction record.
52
+ record: The transaction record to write.
53
+ holder: Unique identifier for the holder of the lock. If a lock exists on
54
+ the record being written, the write will be rejected if the provided
55
+ holder does not match the holder of the lock. If not provided,
56
+ a default holder based on the current host, process, and thread will
57
+ be used.
58
+ """
59
+ ...
17
60
 
61
+ @abc.abstractmethod
18
62
  def exists(self, key: str) -> bool:
19
- return False
63
+ """
64
+ Check if the transaction record with the given key exists.
65
+
66
+ Args:
67
+ key: Unique identifier for the transaction record.
68
+
69
+ Returns:
70
+ bool: True if the record exists; False otherwise.
71
+ """
72
+ ...
73
+
74
+ @abc.abstractmethod
75
+ def supports_isolation_level(self, isolation_level: "IsolationLevel") -> bool:
76
+ """
77
+ Check if the record store supports the given isolation level.
78
+
79
+ Args:
80
+ isolation_level: The isolation level to check.
81
+
82
+ Returns:
83
+ bool: True if the record store supports the isolation level; False otherwise.
84
+ """
85
+ ...
20
86
 
21
87
  def acquire_lock(
22
88
  self,
@@ -155,13 +221,3 @@ class RecordStore:
155
221
  yield
156
222
  finally:
157
223
  self.release_lock(key=key, holder=holder)
158
-
159
-
160
- @dataclass
161
- class TransactionRecord:
162
- """
163
- A dataclass representation of a transaction record.
164
- """
165
-
166
- key: str
167
- result: Optional[BaseResult] = None