prefect-client 3.0.0rc17__py3-none-any.whl → 3.0.0rc19__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 (45) hide show
  1. prefect/_internal/concurrency/services.py +14 -0
  2. prefect/_internal/schemas/bases.py +1 -0
  3. prefect/blocks/core.py +36 -29
  4. prefect/client/orchestration.py +97 -2
  5. prefect/client/schemas/actions.py +14 -4
  6. prefect/client/schemas/filters.py +20 -0
  7. prefect/client/schemas/objects.py +3 -0
  8. prefect/client/schemas/responses.py +3 -0
  9. prefect/client/schemas/sorting.py +2 -0
  10. prefect/concurrency/v1/__init__.py +0 -0
  11. prefect/concurrency/v1/asyncio.py +143 -0
  12. prefect/concurrency/v1/context.py +27 -0
  13. prefect/concurrency/v1/events.py +61 -0
  14. prefect/concurrency/v1/services.py +116 -0
  15. prefect/concurrency/v1/sync.py +92 -0
  16. prefect/context.py +2 -2
  17. prefect/deployments/flow_runs.py +0 -7
  18. prefect/deployments/runner.py +11 -0
  19. prefect/events/clients.py +41 -0
  20. prefect/events/related.py +72 -73
  21. prefect/events/utilities.py +2 -0
  22. prefect/events/worker.py +12 -3
  23. prefect/flow_engine.py +2 -0
  24. prefect/flows.py +7 -0
  25. prefect/records/__init__.py +1 -1
  26. prefect/records/base.py +223 -0
  27. prefect/records/filesystem.py +207 -0
  28. prefect/records/memory.py +178 -0
  29. prefect/records/result_store.py +19 -14
  30. prefect/results.py +11 -0
  31. prefect/runner/runner.py +7 -4
  32. prefect/settings.py +0 -8
  33. prefect/task_engine.py +98 -209
  34. prefect/task_worker.py +7 -39
  35. prefect/tasks.py +2 -9
  36. prefect/transactions.py +67 -19
  37. prefect/utilities/asyncutils.py +3 -3
  38. prefect/utilities/callables.py +1 -3
  39. prefect/utilities/engine.py +7 -6
  40. {prefect_client-3.0.0rc17.dist-info → prefect_client-3.0.0rc19.dist-info}/METADATA +3 -4
  41. {prefect_client-3.0.0rc17.dist-info → prefect_client-3.0.0rc19.dist-info}/RECORD +44 -36
  42. prefect/records/store.py +0 -9
  43. {prefect_client-3.0.0rc17.dist-info → prefect_client-3.0.0rc19.dist-info}/LICENSE +0 -0
  44. {prefect_client-3.0.0rc17.dist-info → prefect_client-3.0.0rc19.dist-info}/WHEEL +0 -0
  45. {prefect_client-3.0.0rc17.dist-info → prefect_client-3.0.0rc19.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,116 @@
1
+ import asyncio
2
+ import concurrent.futures
3
+ from contextlib import asynccontextmanager
4
+ from json import JSONDecodeError
5
+ from typing import (
6
+ TYPE_CHECKING,
7
+ AsyncGenerator,
8
+ FrozenSet,
9
+ Optional,
10
+ Tuple,
11
+ )
12
+ from uuid import UUID
13
+
14
+ import httpx
15
+ from starlette import status
16
+
17
+ from prefect._internal.concurrency import logger
18
+ from prefect._internal.concurrency.services import QueueService
19
+ from prefect.client.orchestration import get_client
20
+ from prefect.utilities.timeout import timeout_async
21
+
22
+ if TYPE_CHECKING:
23
+ from prefect.client.orchestration import PrefectClient
24
+
25
+
26
+ class ConcurrencySlotAcquisitionServiceError(Exception):
27
+ """Raised when an error occurs while acquiring concurrency slots."""
28
+
29
+
30
+ class ConcurrencySlotAcquisitionService(QueueService):
31
+ def __init__(self, concurrency_limit_names: FrozenSet[str]):
32
+ super().__init__(concurrency_limit_names)
33
+ self._client: "PrefectClient"
34
+ self.concurrency_limit_names = sorted(list(concurrency_limit_names))
35
+
36
+ @asynccontextmanager
37
+ async def _lifespan(self) -> AsyncGenerator[None, None]:
38
+ async with get_client() as client:
39
+ self._client = client
40
+ yield
41
+
42
+ async def _handle(
43
+ self,
44
+ item: Tuple[
45
+ UUID,
46
+ concurrent.futures.Future,
47
+ Optional[float],
48
+ ],
49
+ ) -> None:
50
+ task_run_id, future, timeout_seconds = item
51
+ try:
52
+ response = await self.acquire_slots(task_run_id, timeout_seconds)
53
+ except Exception as exc:
54
+ # If the request to the increment endpoint fails in a non-standard
55
+ # way, we need to set the future's result so that the caller can
56
+ # handle the exception and then re-raise.
57
+ future.set_result(exc)
58
+ raise exc
59
+ else:
60
+ future.set_result(response)
61
+
62
+ async def acquire_slots(
63
+ self,
64
+ task_run_id: UUID,
65
+ timeout_seconds: Optional[float] = None,
66
+ ) -> httpx.Response:
67
+ with timeout_async(seconds=timeout_seconds):
68
+ while True:
69
+ try:
70
+ response = await self._client.increment_v1_concurrency_slots(
71
+ task_run_id=task_run_id,
72
+ names=self.concurrency_limit_names,
73
+ )
74
+ except Exception as exc:
75
+ if (
76
+ isinstance(exc, httpx.HTTPStatusError)
77
+ and exc.response.status_code == status.HTTP_423_LOCKED
78
+ ):
79
+ retry_after = exc.response.headers.get("Retry-After")
80
+ if retry_after:
81
+ retry_after = float(retry_after)
82
+ await asyncio.sleep(retry_after)
83
+ else:
84
+ # We received a 423 but no Retry-After header. This
85
+ # should indicate that the server told us to abort
86
+ # because the concurrency limit is set to 0, i.e.
87
+ # effectively disabled.
88
+ try:
89
+ reason = exc.response.json()["detail"]
90
+ except (JSONDecodeError, KeyError):
91
+ logger.error(
92
+ "Failed to parse response from concurrency limit 423 Locked response: %s",
93
+ exc.response.content,
94
+ )
95
+ reason = "Concurrency limit is locked (server did not specify the reason)"
96
+ raise ConcurrencySlotAcquisitionServiceError(
97
+ reason
98
+ ) from exc
99
+
100
+ else:
101
+ raise exc # type: ignore
102
+ else:
103
+ return response
104
+
105
+ def send(self, item: Tuple[UUID, Optional[float]]) -> concurrent.futures.Future:
106
+ with self._lock:
107
+ if self._stopped:
108
+ raise RuntimeError("Cannot put items in a stopped service instance.")
109
+
110
+ logger.debug("Service %r enqueuing item %r", self, item)
111
+ future: concurrent.futures.Future = concurrent.futures.Future()
112
+
113
+ task_run_id, timeout_seconds = item
114
+ self._queue.put_nowait((task_run_id, future, timeout_seconds))
115
+
116
+ return future
@@ -0,0 +1,92 @@
1
+ from contextlib import contextmanager
2
+ from typing import (
3
+ Generator,
4
+ List,
5
+ Optional,
6
+ TypeVar,
7
+ Union,
8
+ cast,
9
+ )
10
+ from uuid import UUID
11
+
12
+ import pendulum
13
+
14
+ from ...client.schemas.responses import MinimalConcurrencyLimitResponse
15
+ from ..sync import _call_async_function_from_sync
16
+
17
+ try:
18
+ from pendulum import Interval
19
+ except ImportError:
20
+ # pendulum < 3
21
+ from pendulum.period import Period as Interval # type: ignore
22
+
23
+ from .asyncio import (
24
+ _acquire_concurrency_slots,
25
+ _release_concurrency_slots,
26
+ )
27
+ from .events import (
28
+ _emit_concurrency_acquisition_events,
29
+ _emit_concurrency_release_events,
30
+ )
31
+
32
+ T = TypeVar("T")
33
+
34
+
35
+ @contextmanager
36
+ def concurrency(
37
+ names: Union[str, List[str]],
38
+ task_run_id: UUID,
39
+ timeout_seconds: Optional[float] = None,
40
+ ) -> Generator[None, None, None]:
41
+ """
42
+ A context manager that acquires and releases concurrency slots from the
43
+ given concurrency limits.
44
+
45
+ Args:
46
+ names: The names of the concurrency limits to acquire.
47
+ task_run_id: The task run ID acquiring the limits.
48
+ timeout_seconds: The number of seconds to wait to acquire the limits before
49
+ raising a `TimeoutError`. A timeout of `None` will wait indefinitely.
50
+
51
+ Raises:
52
+ TimeoutError: If the limits are not acquired within the given timeout.
53
+
54
+ Example:
55
+ A simple example of using the sync `concurrency` context manager:
56
+ ```python
57
+ from prefect.concurrency.v1.sync import concurrency
58
+
59
+ def resource_heavy():
60
+ with concurrency("test"):
61
+ print("Resource heavy task")
62
+
63
+ def main():
64
+ resource_heavy()
65
+ ```
66
+ """
67
+ if not names:
68
+ yield
69
+ return
70
+
71
+ names = names if isinstance(names, list) else [names]
72
+
73
+ limits: List[MinimalConcurrencyLimitResponse] = _call_async_function_from_sync(
74
+ _acquire_concurrency_slots,
75
+ names,
76
+ timeout_seconds=timeout_seconds,
77
+ task_run_id=task_run_id,
78
+ )
79
+ acquisition_time = pendulum.now("UTC")
80
+ emitted_events = _emit_concurrency_acquisition_events(limits, task_run_id)
81
+
82
+ try:
83
+ yield
84
+ finally:
85
+ occupancy_period = cast(Interval, pendulum.now("UTC") - acquisition_time)
86
+ _call_async_function_from_sync(
87
+ _release_concurrency_slots,
88
+ names,
89
+ task_run_id,
90
+ occupancy_period.total_seconds(),
91
+ )
92
+ _emit_concurrency_release_events(limits, emitted_events, task_run_id)
prefect/context.py CHANGED
@@ -131,7 +131,7 @@ class ContextModel(BaseModel):
131
131
  extra="forbid",
132
132
  )
133
133
 
134
- def __enter__(self):
134
+ def __enter__(self) -> Self:
135
135
  if self._token is not None:
136
136
  raise RuntimeError(
137
137
  "Context already entered. Context enter calls cannot be nested."
@@ -290,7 +290,7 @@ class AsyncClientContext(ContextModel):
290
290
  if ctx:
291
291
  yield ctx
292
292
  else:
293
- with cls() as ctx:
293
+ async with cls() as ctx:
294
294
  yield ctx
295
295
 
296
296
 
@@ -5,7 +5,6 @@ from uuid import UUID
5
5
  import anyio
6
6
  import pendulum
7
7
 
8
- from prefect._internal.compatibility.deprecated import deprecated_parameter
9
8
  from prefect.client.schemas import FlowRun
10
9
  from prefect.client.utilities import inject_client
11
10
  from prefect.context import FlowRunContext, TaskRunContext
@@ -24,11 +23,6 @@ logger = get_logger(__name__)
24
23
 
25
24
 
26
25
  @sync_compatible
27
- @deprecated_parameter(
28
- "infra_overrides",
29
- start_date="Apr 2024",
30
- help="Use `job_variables` instead.",
31
- )
32
26
  @inject_client
33
27
  async def run_deployment(
34
28
  name: Union[str, UUID],
@@ -42,7 +36,6 @@ async def run_deployment(
42
36
  idempotency_key: Optional[str] = None,
43
37
  work_queue_name: Optional[str] = None,
44
38
  as_subflow: Optional[bool] = True,
45
- infra_overrides: Optional[dict] = None,
46
39
  job_variables: Optional[dict] = None,
47
40
  ) -> "FlowRun":
48
41
  """
@@ -143,6 +143,10 @@ class RunnerDeployment(BaseModel):
143
143
  default=None,
144
144
  description="The schedules that should cause this deployment to run.",
145
145
  )
146
+ concurrency_limit: Optional[int] = Field(
147
+ default=None,
148
+ description="The maximum number of concurrent runs of this deployment.",
149
+ )
146
150
  paused: Optional[bool] = Field(
147
151
  default=None, description="Whether or not the deployment is paused."
148
152
  )
@@ -274,6 +278,7 @@ class RunnerDeployment(BaseModel):
274
278
  version=self.version,
275
279
  paused=self.paused,
276
280
  schedules=self.schedules,
281
+ concurrency_limit=self.concurrency_limit,
277
282
  parameters=self.parameters,
278
283
  description=self.description,
279
284
  tags=self.tags,
@@ -432,6 +437,7 @@ class RunnerDeployment(BaseModel):
432
437
  rrule: Optional[Union[Iterable[str], str]] = None,
433
438
  paused: Optional[bool] = None,
434
439
  schedules: Optional["FlexibleScheduleList"] = None,
440
+ concurrency_limit: Optional[int] = None,
435
441
  parameters: Optional[dict] = None,
436
442
  triggers: Optional[List[Union[DeploymentTriggerTypes, TriggerTypes]]] = None,
437
443
  description: Optional[str] = None,
@@ -485,6 +491,7 @@ class RunnerDeployment(BaseModel):
485
491
  name=Path(name).stem,
486
492
  flow_name=flow.name,
487
493
  schedules=constructed_schedules,
494
+ concurrency_limit=concurrency_limit,
488
495
  paused=paused,
489
496
  tags=tags or [],
490
497
  triggers=triggers or [],
@@ -558,6 +565,7 @@ class RunnerDeployment(BaseModel):
558
565
  rrule: Optional[Union[Iterable[str], str]] = None,
559
566
  paused: Optional[bool] = None,
560
567
  schedules: Optional["FlexibleScheduleList"] = None,
568
+ concurrency_limit: Optional[int] = None,
561
569
  parameters: Optional[dict] = None,
562
570
  triggers: Optional[List[Union[DeploymentTriggerTypes, TriggerTypes]]] = None,
563
571
  description: Optional[str] = None,
@@ -614,6 +622,7 @@ class RunnerDeployment(BaseModel):
614
622
  name=Path(name).stem,
615
623
  flow_name=flow.name,
616
624
  schedules=constructed_schedules,
625
+ concurrency_limit=concurrency_limit,
617
626
  paused=paused,
618
627
  tags=tags or [],
619
628
  triggers=triggers or [],
@@ -646,6 +655,7 @@ class RunnerDeployment(BaseModel):
646
655
  rrule: Optional[Union[Iterable[str], str]] = None,
647
656
  paused: Optional[bool] = None,
648
657
  schedules: Optional["FlexibleScheduleList"] = None,
658
+ concurrency_limit: Optional[int] = None,
649
659
  parameters: Optional[dict] = None,
650
660
  triggers: Optional[List[Union[DeploymentTriggerTypes, TriggerTypes]]] = None,
651
661
  description: Optional[str] = None,
@@ -710,6 +720,7 @@ class RunnerDeployment(BaseModel):
710
720
  name=Path(name).stem,
711
721
  flow_name=flow.name,
712
722
  schedules=constructed_schedules,
723
+ concurrency_limit=concurrency_limit,
713
724
  paused=paused,
714
725
  tags=tags or [],
715
726
  triggers=triggers or [],
prefect/events/clients.py CHANGED
@@ -346,6 +346,47 @@ class PrefectEventsClient(EventsClient):
346
346
  await asyncio.sleep(1)
347
347
 
348
348
 
349
+ class AssertingPassthroughEventsClient(PrefectEventsClient):
350
+ """A Prefect Events client that BOTH records all events sent to it for inspection
351
+ during tests AND sends them to a Prefect server."""
352
+
353
+ last: ClassVar["Optional[AssertingPassthroughEventsClient]"] = None
354
+ all: ClassVar[List["AssertingPassthroughEventsClient"]] = []
355
+
356
+ args: Tuple
357
+ kwargs: Dict[str, Any]
358
+ events: List[Event]
359
+
360
+ def __init__(self, *args, **kwargs):
361
+ super().__init__(*args, **kwargs)
362
+ AssertingPassthroughEventsClient.last = self
363
+ AssertingPassthroughEventsClient.all.append(self)
364
+ self.args = args
365
+ self.kwargs = kwargs
366
+
367
+ @classmethod
368
+ def reset(cls) -> None:
369
+ cls.last = None
370
+ cls.all = []
371
+
372
+ def pop_events(self) -> List[Event]:
373
+ events = self.events
374
+ self.events = []
375
+ return events
376
+
377
+ async def _emit(self, event: Event) -> None:
378
+ # actually send the event to the server
379
+ await super()._emit(event)
380
+
381
+ # record the event for inspection
382
+ self.events.append(event)
383
+
384
+ async def __aenter__(self) -> Self:
385
+ await super().__aenter__()
386
+ self.events = []
387
+ return self
388
+
389
+
349
390
  class PrefectCloudEventsClient(PrefectEventsClient):
350
391
  """A Prefect Events client that streams events to a Prefect Cloud Workspace"""
351
392
 
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/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,
@@ -506,6 +507,7 @@ class FlowRunEngine(Generic[P, R]):
506
507
  task_runner=task_runner,
507
508
  )
508
509
  )
510
+ stack.enter_context(ConcurrencyContextV1())
509
511
  stack.enter_context(ConcurrencyContext())
510
512
 
511
513
  # set the logger to the flow run logger
prefect/flows.py CHANGED
@@ -643,6 +643,7 @@ class Flow(Generic[P, R]):
643
643
  rrule: Optional[Union[Iterable[str], str]] = None,
644
644
  paused: Optional[bool] = None,
645
645
  schedules: Optional[List["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 {},
@@ -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,
@@ -1 +1 @@
1
- from .store import RecordStore
1
+ from .base import RecordStore