prefect-client 2.14.20__py3-none-any.whl → 2.14.21__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.
@@ -714,7 +714,8 @@ class CustomWebhookNotificationBlock(NotificationBlock):
714
714
 
715
715
  # make request with httpx
716
716
  client = httpx.AsyncClient(headers={"user-agent": "Prefect Notifications"})
717
- resp = await client.request(**self._build_request_args(body, subject))
717
+ async with client:
718
+ resp = await client.request(**self._build_request_args(body, subject))
718
719
  resp.raise_for_status()
719
720
 
720
721
 
@@ -97,6 +97,14 @@ class DeploymentStatus(AutoEnum):
97
97
  NOT_READY = AutoEnum.auto()
98
98
 
99
99
 
100
+ class WorkQueueStatus(AutoEnum):
101
+ """Enumeration of work queue statuses."""
102
+
103
+ READY = AutoEnum.auto()
104
+ NOT_READY = AutoEnum.auto()
105
+ PAUSED = AutoEnum.auto()
106
+
107
+
100
108
  class StateDetails(PrefectBaseModel):
101
109
  flow_run_id: UUID = None
102
110
  task_run_id: UUID = None
@@ -1222,6 +1230,9 @@ class WorkQueue(ObjectBaseModel):
1222
1230
  last_polled: Optional[DateTimeTZ] = Field(
1223
1231
  default=None, description="The last time an agent polled this queue for work."
1224
1232
  )
1233
+ status: Optional[WorkQueueStatus] = Field(
1234
+ default=None, description="The queue status."
1235
+ )
1225
1236
 
1226
1237
  @validator("name", check_fields=False)
1227
1238
  def validate_name_characters(cls, v):
@@ -1,5 +1,5 @@
1
1
  import asyncio
2
- from typing import Generic, Type, TypeVar
2
+ from typing import Generic, List, Type, TypeVar
3
3
 
4
4
  import orjson
5
5
  import websockets
@@ -8,18 +8,23 @@ from starlette.status import WS_1008_POLICY_VIOLATION
8
8
  from typing_extensions import Self
9
9
 
10
10
  from prefect._internal.schemas.bases import IDBaseModel
11
+ from prefect.logging import get_logger
11
12
  from prefect.settings import PREFECT_API_KEY, PREFECT_API_URL
12
13
 
14
+ logger = get_logger(__name__)
15
+
13
16
  S = TypeVar("S", bound=IDBaseModel)
14
17
 
15
18
 
16
19
  class Subscription(Generic[S]):
17
- def __init__(self, model: Type[S], path: str):
20
+ def __init__(self, model: Type[S], path: str, keys: List[str]):
18
21
  self.model = model
19
22
 
20
23
  base_url = PREFECT_API_URL.value().replace("http", "ws", 1)
21
24
  self.subscription_url = f"{base_url}{path}"
22
25
 
26
+ self.keys = keys
27
+
23
28
  self._connect = websockets.connect(
24
29
  self.subscription_url,
25
30
  subprotocols=["prefect"],
@@ -57,13 +62,19 @@ class Subscription(Generic[S]):
57
62
 
58
63
  websocket = await self._connect.__aenter__()
59
64
 
60
- await websocket.send(
61
- orjson.dumps({"type": "auth", "token": PREFECT_API_KEY.value()}).decode()
62
- )
63
-
64
65
  try:
66
+ await websocket.send(
67
+ orjson.dumps(
68
+ {"type": "auth", "token": PREFECT_API_KEY.value()}
69
+ ).decode()
70
+ )
71
+
65
72
  auth = orjson.loads(await websocket.recv())
66
73
  assert auth["type"] == "auth_success"
74
+
75
+ await websocket.send(
76
+ orjson.dumps({"type": "subscribe", "keys": self.keys}).decode()
77
+ )
67
78
  except (
68
79
  AssertionError,
69
80
  websockets.exceptions.ConnectionClosedError,
prefect/context.py CHANGED
@@ -236,7 +236,7 @@ class EngineContext(RunContext):
236
236
  autonomous_task_run: Optional[TaskRun] = None
237
237
  task_runner: BaseTaskRunner
238
238
  log_prints: bool = False
239
- parameters: Dict[str, Any]
239
+ parameters: Optional[Dict[str, Any]] = None
240
240
 
241
241
  # Result handling
242
242
  result_factory: ResultFactory
prefect/engine.py CHANGED
@@ -1341,12 +1341,13 @@ async def resume_flow_run(flow_run_id, run_input: Optional[Dict] = None):
1341
1341
  run_input: a dictionary of inputs to provide to the flow run.
1342
1342
  """
1343
1343
  client = get_client()
1344
- flow_run = await client.read_flow_run(flow_run_id)
1344
+ async with client:
1345
+ flow_run = await client.read_flow_run(flow_run_id)
1345
1346
 
1346
- if not flow_run.state.is_paused():
1347
- raise NotPausedError("Cannot resume a run that isn't paused!")
1347
+ if not flow_run.state.is_paused():
1348
+ raise NotPausedError("Cannot resume a run that isn't paused!")
1348
1349
 
1349
- response = await client.resume_flow_run(flow_run_id, run_input=run_input)
1350
+ response = await client.resume_flow_run(flow_run_id, run_input=run_input)
1350
1351
 
1351
1352
  if response.status == SetStateStatus.REJECT:
1352
1353
  if response.state.type == StateType.FAILED:
@@ -1369,7 +1370,17 @@ def enter_task_run_engine(
1369
1370
 
1370
1371
  if not flow_run_context:
1371
1372
  if PREFECT_EXPERIMENTAL_ENABLE_TASK_SCHEDULING.value():
1372
- return _create_autonomous_task_run(task=task, parameters=parameters)
1373
+ create_autonomous_task_run = create_call(
1374
+ _create_autonomous_task_run, task=task, parameters=parameters
1375
+ )
1376
+ if task.isasync:
1377
+ return from_async.wait_for_call_in_loop_thread(
1378
+ create_autonomous_task_run
1379
+ )
1380
+ else:
1381
+ return from_sync.wait_for_call_in_loop_thread(
1382
+ create_autonomous_task_run
1383
+ )
1373
1384
 
1374
1385
  raise RuntimeError(
1375
1386
  "Tasks cannot be run outside of a flow"
@@ -1738,7 +1749,10 @@ async def submit_task_run(
1738
1749
  ),
1739
1750
  )
1740
1751
 
1741
- if task_runner.concurrency_type != TaskConcurrencyType.SEQUENTIAL:
1752
+ if (
1753
+ task_runner.concurrency_type != TaskConcurrencyType.SEQUENTIAL
1754
+ and not flow_run_context.autonomous_task_run
1755
+ ):
1742
1756
  logger.info(f"Submitted task run {task_run.name!r} for execution.")
1743
1757
 
1744
1758
  return future
@@ -2200,7 +2214,6 @@ async def orchestrate_task_run(
2200
2214
  level=logging.INFO if state.is_completed() else logging.ERROR,
2201
2215
  msg=f"Finished in state {display_state}",
2202
2216
  )
2203
- logger.warning(f"Task run {task_run.name!r} finished in state {display_state}")
2204
2217
  return state
2205
2218
 
2206
2219
 
@@ -2914,7 +2927,6 @@ def _emit_task_run_state_change_event(
2914
2927
  )
2915
2928
 
2916
2929
 
2917
- @sync_compatible
2918
2930
  async def _create_autonomous_task_run(
2919
2931
  task: Task, parameters: Dict[str, Any]
2920
2932
  ) -> TaskRun:
@@ -8,7 +8,7 @@ from prefect._internal.schemas.fields import DateTimeTZ
8
8
 
9
9
  from .clients import AssertingEventsClient, PrefectCloudEventsClient
10
10
  from .schemas import Event, RelatedResource
11
- from .worker import EventsWorker
11
+ from .worker import EventsWorker, emit_events_to_cloud
12
12
 
13
13
  TIGHT_TIMING = timedelta(minutes=5)
14
14
 
@@ -42,6 +42,9 @@ def emit_event(
42
42
  The event that was emitted if worker is using a client that emit
43
43
  events, otherwise None.
44
44
  """
45
+ if not emit_events_to_cloud():
46
+ return None
47
+
45
48
  operational_clients = [AssertingEventsClient, PrefectCloudEventsClient]
46
49
  worker_instance = EventsWorker.instance()
47
50
 
prefect/events/worker.py CHANGED
@@ -14,6 +14,15 @@ from .related import related_resources_from_run_context
14
14
  from .schemas import Event
15
15
 
16
16
 
17
+ def emit_events_to_cloud() -> bool:
18
+ api = PREFECT_API_URL.value()
19
+ return (
20
+ experiment_enabled("events_client")
21
+ and api
22
+ and api.startswith(PREFECT_CLOUD_API_URL.value())
23
+ )
24
+
25
+
17
26
  class EventsWorker(QueueService[Event]):
18
27
  def __init__(
19
28
  self, client_type: Type[EventsClient], client_options: Tuple[Tuple[str, Any]]
@@ -52,12 +61,7 @@ class EventsWorker(QueueService[Event]):
52
61
 
53
62
  # Select a client type for this worker based on settings
54
63
  if client_type is None:
55
- api = PREFECT_API_URL.value()
56
- if (
57
- experiment_enabled("events_client")
58
- and api
59
- and api.startswith(PREFECT_CLOUD_API_URL.value())
60
- ):
64
+ if emit_events_to_cloud():
61
65
  client_type = PrefectCloudEventsClient
62
66
  client_kwargs = {
63
67
  "api_url": PREFECT_API_URL.value(),
prefect/results.py CHANGED
@@ -185,10 +185,9 @@ class ResultFactory(pydantic.BaseModel):
185
185
  persist_result=(
186
186
  flow.persist_result
187
187
  if flow.persist_result is not None
188
- else
189
188
  # !! Child flows persist their result by default if the it or the
190
189
  # parent flow uses a feature that requires it
191
- (
190
+ else (
192
191
  flow_features_require_result_persistence(flow)
193
192
  or flow_features_require_child_result_persistence(ctx.flow)
194
193
  or get_default_persist_setting()
@@ -209,10 +208,9 @@ class ResultFactory(pydantic.BaseModel):
209
208
  persist_result=(
210
209
  flow.persist_result
211
210
  if flow.persist_result is not None
212
- else
213
211
  # !! Flows persist their result by default if uses a feature that
214
212
  # requires it
215
- (
213
+ else (
216
214
  flow_features_require_result_persistence(flow)
217
215
  or get_default_persist_setting()
218
216
  )
@@ -246,10 +244,9 @@ class ResultFactory(pydantic.BaseModel):
246
244
  persist_result = (
247
245
  task.persist_result
248
246
  if task.persist_result is not None
249
- else
250
247
  # !! Tasks persist their result by default if their parent flow uses a
251
248
  # feature that requires it or the task uses a feature that requires it
252
- (
249
+ else (
253
250
  (
254
251
  flow_features_require_child_result_persistence(ctx.flow)
255
252
  if ctx
prefect/runner/server.py CHANGED
@@ -87,7 +87,7 @@ async def _build_endpoint_for_deployment(
87
87
  deployment: "DeploymentResponse", runner: "Runner"
88
88
  ) -> Callable:
89
89
  async def _create_flow_run_for_deployment(
90
- body: Optional[Dict[Any, Any]] = None
90
+ body: Optional[Dict[Any, Any]] = None,
91
91
  ) -> JSONResponse:
92
92
  body = body or {}
93
93
  if deployment.enforce_parameter_schema and deployment.parameter_openapi_schema:
prefect/settings.py CHANGED
@@ -1416,6 +1416,22 @@ PREFECT_TASK_SCHEDULING_DELETE_FAILED_SUBMISSIONS = Setting(
1416
1416
  Whether or not to delete failed task submissions from the database.
1417
1417
  """
1418
1418
 
1419
+ PREFECT_TASK_SCHEDULING_MAX_SCHEDULED_QUEUE_SIZE = Setting(
1420
+ int,
1421
+ default=1000,
1422
+ )
1423
+ """
1424
+ The maximum number of scheduled tasks to queue for submission.
1425
+ """
1426
+
1427
+ PREFECT_TASK_SCHEDULING_MAX_RETRY_QUEUE_SIZE = Setting(
1428
+ int,
1429
+ default=100,
1430
+ )
1431
+ """
1432
+ The maximum number of retries to queue for submission.
1433
+ """
1434
+
1419
1435
  PREFECT_EXPERIMENTAL_ENABLE_EXTRA_RUNNER_ENDPOINTS = Setting(bool, default=False)
1420
1436
  """
1421
1437
  Whether or not to enable experimental worker webserver endpoints.
@@ -1447,6 +1463,11 @@ PREFECT_EXPERIMENTAL_ENABLE_TASK_SCHEDULING = Setting(bool, default=False)
1447
1463
  Whether or not to enable experimental task scheduling.
1448
1464
  """
1449
1465
 
1466
+ PREFECT_EXPERIMENTAL_ENABLE_WORK_QUEUE_STATUS = Setting(bool, default=False)
1467
+ """
1468
+ Whether or not to enable experimental work queue status in-place of work queue health.
1469
+ """
1470
+
1450
1471
  # Defaults -----------------------------------------------------------------------------
1451
1472
 
1452
1473
  PREFECT_DEFAULT_RESULT_STORAGE_BLOCK = Setting(
prefect/task_engine.py CHANGED
@@ -8,7 +8,6 @@ from typing import (
8
8
  )
9
9
 
10
10
  import anyio
11
- from anyio import start_blocking_portal
12
11
  from typing_extensions import Literal
13
12
 
14
13
  from prefect._internal.concurrency.api import create_call, from_async, from_sync
@@ -21,7 +20,7 @@ from prefect.engine import (
21
20
  )
22
21
  from prefect.futures import PrefectFuture
23
22
  from prefect.results import ResultFactory
24
- from prefect.task_runners import BaseTaskRunner, SequentialTaskRunner
23
+ from prefect.task_runners import BaseTaskRunner
25
24
  from prefect.tasks import Task
26
25
  from prefect.utilities.asyncutils import sync_compatible
27
26
 
@@ -32,28 +31,28 @@ EngineReturnType = Literal["future", "state", "result"]
32
31
  async def submit_autonomous_task_to_engine(
33
32
  task: Task,
34
33
  task_run: TaskRun,
34
+ task_runner: Type[BaseTaskRunner],
35
35
  parameters: Optional[Dict] = None,
36
36
  wait_for: Optional[Iterable[PrefectFuture]] = None,
37
37
  mapped: bool = False,
38
38
  return_type: EngineReturnType = "future",
39
- task_runner: Optional[Type[BaseTaskRunner]] = None,
39
+ client=None,
40
40
  ) -> Any:
41
- parameters = parameters or {}
42
41
  async with AsyncExitStack() as stack:
42
+ if not task_runner._started:
43
+ task_runner_ctx = await stack.enter_async_context(task_runner.start())
44
+ else:
45
+ task_runner_ctx = task_runner
46
+ parameters = parameters or {}
43
47
  with EngineContext(
44
48
  flow=None,
45
49
  flow_run=None,
46
50
  autonomous_task_run=task_run,
47
- task_runner=await stack.enter_async_context(
48
- (task_runner if task_runner else SequentialTaskRunner()).start()
49
- ),
50
- client=await stack.enter_async_context(get_client()),
51
+ task_runner=task_runner_ctx,
52
+ client=client or await stack.enter_async_context(get_client()),
51
53
  parameters=parameters,
52
54
  result_factory=await ResultFactory.from_task(task),
53
55
  background_tasks=await stack.enter_async_context(anyio.create_task_group()),
54
- sync_portal=(
55
- stack.enter_context(start_blocking_portal()) if task.isasync else None
56
- ),
57
56
  ) as flow_run_context:
58
57
  begin_run = create_call(
59
58
  begin_task_map if mapped else get_task_call_return_value,
prefect/task_server.py CHANGED
@@ -1,8 +1,9 @@
1
1
  import asyncio
2
2
  import signal
3
3
  import sys
4
+ from contextlib import AsyncExitStack
4
5
  from functools import partial
5
- from typing import Iterable, Optional
6
+ from typing import Iterable, Optional, Type
6
7
 
7
8
  import anyio
8
9
  import anyio.abc
@@ -19,40 +20,44 @@ from prefect.settings import (
19
20
  PREFECT_TASK_SCHEDULING_DELETE_FAILED_SUBMISSIONS,
20
21
  )
21
22
  from prefect.task_engine import submit_autonomous_task_to_engine
23
+ from prefect.task_runners import BaseTaskRunner, ConcurrentTaskRunner
22
24
  from prefect.utilities.asyncutils import asyncnullcontext, sync_compatible
23
- from prefect.utilities.collections import distinct
24
25
  from prefect.utilities.processutils import _register_signal
25
26
 
26
27
  logger = get_logger("task_server")
27
28
 
28
29
 
29
30
  class TaskServer:
30
- """This class is responsible for serving tasks that may be executed autonomously
31
- (i.e., without a parent flow run).
31
+ """This class is responsible for serving tasks that may be executed autonomously by a
32
+ task runner in the engine.
32
33
 
33
- When `start()` is called, the task server will subscribe to the task run scheduling
34
- topic and poll for scheduled task runs. When a scheduled task run is found, it
35
- will submit the task run to the engine for execution, using `submit_autonomous_task_to_engine`
36
- to construct a minimal `EngineContext` for the task run.
34
+ When `start()` is called, the task server will open a websocket connection to a
35
+ server-side queue of scheduled task runs. When a scheduled task run is found, the
36
+ scheduled task run is submitted to the engine for execution with a minimal `EngineContext`
37
+ so that the task run can be governed by orchestration rules.
37
38
 
38
39
  Args:
39
40
  - tasks: A list of tasks to serve. These tasks will be submitted to the engine
40
41
  when a scheduled task run is found.
41
- - tags: A list of tags to apply to the task server. Defaults to `["autonomous"]`.
42
+ - task_runner: The task runner to use for executing the tasks. Defaults to
43
+ `ConcurrentTaskRunner`.
42
44
  """
43
45
 
44
46
  def __init__(
45
47
  self,
46
48
  *tasks: Task,
47
- tags: Optional[Iterable[str]] = None,
49
+ task_runner: Optional[Type[BaseTaskRunner]] = None,
50
+ extra_tags: Optional[Iterable[str]] = None,
48
51
  ):
49
52
  self.tasks: list[Task] = tasks
50
- self.tags: Iterable[str] = tags or ["autonomous"]
53
+ self.task_runner: Type[BaseTaskRunner] = task_runner or ConcurrentTaskRunner()
54
+ self.extra_tags: Iterable[str] = extra_tags or []
51
55
  self.last_polled: Optional[pendulum.DateTime] = None
52
- self.started = False
53
- self.stopping = False
56
+ self.started: bool = False
57
+ self.stopping: bool = False
54
58
 
55
59
  self._client = get_client()
60
+ self._exit_stack = AsyncExitStack()
56
61
 
57
62
  if not asyncio.get_event_loop().is_running():
58
63
  raise RuntimeError(
@@ -89,14 +94,15 @@ class TaskServer:
89
94
  " calling .start()"
90
95
  )
91
96
 
92
- logger.info("Stopping task server...")
93
97
  self.started = False
94
98
  self.stopping = True
95
99
 
96
100
  async def _subscribe_to_task_scheduling(self):
97
- subscription = Subscription(TaskRun, "/task_runs/subscriptions/scheduled")
98
- logger.debug(f"Created: {subscription}")
99
- async for task_run in subscription:
101
+ async for task_run in Subscription(
102
+ TaskRun,
103
+ "/task_runs/subscriptions/scheduled",
104
+ [task.task_key for task in self.tasks],
105
+ ):
100
106
  logger.info(f"Received task run: {task_run.id} - {task_run.name}")
101
107
  await self._submit_pending_task_run(task_run)
102
108
 
@@ -142,22 +148,23 @@ class TaskServer:
142
148
  f"Submitting run {task_run.name!r} of task {task.name!r} to engine"
143
149
  )
144
150
 
145
- task_run.tags = distinct(task_run.tags + list(self.tags))
146
-
147
151
  self._runs_task_group.start_soon(
148
152
  partial(
149
153
  submit_autonomous_task_to_engine,
150
154
  task=task,
151
155
  task_run=task_run,
152
156
  parameters=parameters,
157
+ task_runner=self.task_runner,
158
+ client=self._client,
153
159
  )
154
160
  )
155
161
 
156
162
  async def __aenter__(self):
157
163
  logger.debug("Starting task server...")
158
- self._client = get_client()
159
- await self._client.__aenter__()
160
- await self._runs_task_group.__aenter__()
164
+
165
+ self._client = await self._exit_stack.enter_async_context(get_client())
166
+ await self._exit_stack.enter_async_context(self._runs_task_group)
167
+ await self._exit_stack.enter_async_context(self.task_runner.start())
161
168
 
162
169
  self.started = True
163
170
  return self
@@ -165,20 +172,25 @@ class TaskServer:
165
172
  async def __aexit__(self, *exc_info):
166
173
  logger.debug("Stopping task server...")
167
174
  self.started = False
168
- if self._runs_task_group:
169
- await self._runs_task_group.__aexit__(*exc_info)
170
- if self._client:
171
- await self._client.__aexit__(*exc_info)
175
+
176
+ await self._exit_stack.__aexit__(*exc_info)
172
177
 
173
178
 
174
179
  @sync_compatible
175
- async def serve(*tasks: Task, tags: Optional[Iterable[str]] = None):
176
- """Serve the provided tasks so that they may be executed autonomously.
180
+ async def serve(
181
+ *tasks: Task,
182
+ task_runner: Optional[Type[BaseTaskRunner]] = None,
183
+ extra_tags: Optional[Iterable[str]] = None,
184
+ ):
185
+ """Serve the provided tasks so that they may be submitted and executed to the engine.
186
+ Tasks do not need to be within a flow run context to be submitted and executed.
187
+ Ideally, you should `.submit` the same task object that you pass to `serve`.
177
188
 
178
189
  Args:
179
190
  - tasks: A list of tasks to serve. When a scheduled task run is found for a
180
191
  given task, the task run will be submitted to the engine for execution.
181
- - tags: A list of tags to apply to the task server. Defaults to `["autonomous"]`.
192
+ - task_runner: The task runner to use for executing the tasks. Defaults to
193
+ `ConcurrentTaskRunner`.
182
194
 
183
195
  Example:
184
196
  ```python
@@ -204,5 +216,9 @@ async def serve(*tasks: Task, tags: Optional[Iterable[str]] = None):
204
216
  " to True."
205
217
  )
206
218
 
207
- task_server = TaskServer(*tasks, tags=tags)
208
- await task_server.start()
219
+ task_server = TaskServer(*tasks, task_runner=task_runner)
220
+ try:
221
+ await task_server.start()
222
+
223
+ except (asyncio.CancelledError, KeyboardInterrupt):
224
+ logger.info("Task server interrupted, stopping...")
@@ -28,6 +28,8 @@ import anyio.abc
28
28
  import sniffio
29
29
  from typing_extensions import Literal, ParamSpec, TypeGuard
30
30
 
31
+ from prefect.logging import get_logger
32
+
31
33
  T = TypeVar("T")
32
34
  P = ParamSpec("P")
33
35
  R = TypeVar("R")
@@ -40,6 +42,8 @@ EVENT_LOOP_GC_REFS = {}
40
42
 
41
43
  PREFECT_THREAD_LIMITER: Optional[anyio.CapacityLimiter] = None
42
44
 
45
+ logger = get_logger()
46
+
43
47
 
44
48
  def get_thread_limiter():
45
49
  global PREFECT_THREAD_LIMITER
@@ -334,7 +338,13 @@ async def add_event_loop_shutdown_callback(coroutine_fn: Callable[[], Awaitable]
334
338
  EVENT_LOOP_GC_REFS[key] = on_shutdown(key)
335
339
 
336
340
  # Begin iterating so it will be cleaned up as an incomplete generator
337
- await EVENT_LOOP_GC_REFS[key].__anext__()
341
+ try:
342
+ await EVENT_LOOP_GC_REFS[key].__anext__()
343
+ # There is a poorly understood edge case we've seen in CI where the key is
344
+ # removed from the dict before we begin generator iteration.
345
+ except KeyError:
346
+ logger.warn("The event loop shutdown callback was not properly registered. ")
347
+ pass
338
348
 
339
349
 
340
350
  class GatherIncomplete(RuntimeError):
@@ -3,6 +3,7 @@ Utilities for working with file systems
3
3
  """
4
4
  import os
5
5
  import pathlib
6
+ import threading
6
7
  from contextlib import contextmanager
7
8
  from pathlib import Path, PureWindowsPath
8
9
  from typing import Union
@@ -51,6 +52,9 @@ def filter_files(
51
52
  return included_files
52
53
 
53
54
 
55
+ chdir_lock = threading.Lock()
56
+
57
+
54
58
  @contextmanager
55
59
  def tmpchdir(path: str):
56
60
  """
@@ -62,11 +66,12 @@ def tmpchdir(path: str):
62
66
 
63
67
  owd = os.getcwd()
64
68
 
65
- try:
66
- os.chdir(path)
67
- yield path
68
- finally:
69
- os.chdir(owd)
69
+ with chdir_lock:
70
+ try:
71
+ os.chdir(path)
72
+ yield path
73
+ finally:
74
+ os.chdir(owd)
70
75
 
71
76
 
72
77
  def filename(path: str) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: prefect-client
3
- Version: 2.14.20
3
+ Version: 2.14.21
4
4
  Summary: Workflow orchestration and management.
5
5
  Home-page: https://www.prefect.io
6
6
  Author: Prefect Technologies, Inc.
@@ -2,8 +2,8 @@ prefect/.prefectignore,sha256=awSprvKT0vI8a64mEOLrMxhxqcO-b0ERQeYpA2rNKVQ,390
2
2
  prefect/__init__.py,sha256=CbIj8-fhzFJKbvXPadpc73SwIhNXiR_SVzQW4_k52jY,5339
3
3
  prefect/_version.py,sha256=fQguBh1dzT7Baahj504O5RrsLlSyg3Zrx42OpgdPnFc,22378
4
4
  prefect/agent.py,sha256=b557LEcKxcBrgAGOlEDlOPclAkucDj1RhzywBSYxYpI,27487
5
- prefect/context.py,sha256=vg88xl2Awzf755FJM9jHlNEJB_bvbdxIUHkKmTELvBQ,18088
6
- prefect/engine.py,sha256=HCOhrvZuap10vGEXHhT5I36X9FIXvHQ64HyGsAwSAUE,108831
5
+ prefect/context.py,sha256=ZSg6bjqDIBdntnnrPh2xl2DuNGOw1tl0kcelceKq_B8,18105
6
+ prefect/engine.py,sha256=OMUsYFhw-RtIOVQE08uyXoTQmFSQ41DED5V7ZFKhGUk,109207
7
7
  prefect/exceptions.py,sha256=84rpsDLp0cn_v2gE1TnK_NZXh27NJtzgZQtARVKyVEE,10953
8
8
  prefect/filesystems.py,sha256=AXFFsga4JIp06Hsw7970B6Z0s5HlR7UpUfqAFZl11k4,34782
9
9
  prefect/flow_runs.py,sha256=-XcKLrAZG35PnQxp5ReWDQ97kdgaNEtuB3fdwWZb9T0,2801
@@ -13,13 +13,13 @@ prefect/manifests.py,sha256=xfwEEozSEqPK2Lro4dfgdTnjVbQx-aCECNBnf7vO7ZQ,808
13
13
  prefect/plugins.py,sha256=0C-D3-dKi06JZ44XEGmLjCiAkefbE_lKX-g3urzdbQ4,4163
14
14
  prefect/profiles.toml,sha256=1Tz7nKBDTDXL_6KPJSeB7ok0Vx_aQJ_p0AUmbnzDLzw,39
15
15
  prefect/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- prefect/results.py,sha256=w_tVJ9i_XB1jgJuu56L504ecPBw5eeGtkWwLADIeDKk,22288
16
+ prefect/results.py,sha256=ophLZOhOx_iowhI8xYZ26LabysMSMvUGu_zUp2CWoOk,22236
17
17
  prefect/serializers.py,sha256=sSbe40Ipj-d6VuzBae5k2ao9lkMUZpIXcLtD7f2a7cE,10852
18
- prefect/settings.py,sha256=urRa3rG-jD1BuRjQ2Hm6ROk4-ofKtmPqgjtFD67zoxk,65846
18
+ prefect/settings.py,sha256=zYMOJohm-JeC-4KwyBNacgQSbt5FEWOLiBE1hmqGPZk,66328
19
19
  prefect/states.py,sha256=-Ud4AUom3Qu-HQ4hOLvfVZuuF-b_ibaqtzmL7V949Ac,20839
20
- prefect/task_engine.py,sha256=IqImIWtqT_DXBPKdpbWnq8dyxYXerF68wGIPGFcH98s,2475
20
+ prefect/task_engine.py,sha256=299Y6EN1VdCuLtEe6fB7waBzGO2HJRKwVJyH5cXfkUI,2363
21
21
  prefect/task_runners.py,sha256=HXUg5UqhZRN2QNBqMdGE1lKhwFhT8TaRN75ScgLbnw8,11012
22
- prefect/task_server.py,sha256=xZDW2-xZ9S23yv_ijgrcQCdJUmPlgPrtpTqbZFwW1Lw,7450
22
+ prefect/task_server.py,sha256=9r4bCjAB6oqC-4p0_FqEiao3ouFYKq0yWoaNclaS-mA,8132
23
23
  prefect/tasks.py,sha256=lDTr2puBBKj53cAcc96rFCyZkeX0VQh83eYGszyyb0I,47049
24
24
  prefect/variables.py,sha256=57h-cJ15ZXWrdQiOnoEQmUVlAe59hmIaa57ZcGNBzao,914
25
25
  prefect/_internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -98,7 +98,7 @@ prefect/blocks/abstract.py,sha256=Q-Pcbg_RbJNxdFITWg3zsKFVwXdcmbX1jBQwZqWSGCU,15
98
98
  prefect/blocks/core.py,sha256=V6-0QpjJSkLHI8OgQs8_kYCS2Srvol8K3YAj1LHqopA,43500
99
99
  prefect/blocks/fields.py,sha256=ANOzbNyDCBIvm6ktgbLTMs7JW2Sf6CruyATjAW61ks0,1607
100
100
  prefect/blocks/kubernetes.py,sha256=SrvRIj9YY1jyuE997y71U4xvMfRZDr_9BQwoE98MbZQ,3866
101
- prefect/blocks/notifications.py,sha256=7L-kF8GXAkzOqA_aaMU_fkl5om5zY48USJKRJGC8hfA,26761
101
+ prefect/blocks/notifications.py,sha256=wiXpntlZmTTYWGNqYwyNg1XB1aDDtoCJkwVQLhT0sg8,26792
102
102
  prefect/blocks/system.py,sha256=Nlp-3315Hye3FJ5uhDovSPGBIEKi5UbCkAcy3hDxhKk,3057
103
103
  prefect/blocks/webhook.py,sha256=hhyWck7mAPfD_12bl40dJedNC9HIaqs7z13iYcZZ14o,2005
104
104
  prefect/client/__init__.py,sha256=yJ5FRF9RxNUio2V_HmyKCKw5G6CZO0h8cv6xA_Hkpcc,477
@@ -107,12 +107,12 @@ prefect/client/cloud.py,sha256=vlGivNaOIS0YNc0OnVKEx2L88SRU8pal8GYMoPHXyrU,3955
107
107
  prefect/client/collections.py,sha256=I9EgbTg4Fn57gn8vwP_WdDmgnATbx9gfkm2jjhCORjw,1037
108
108
  prefect/client/constants.py,sha256=Z_GG8KF70vbbXxpJuqW5pLnwzujTVeHbcYYRikNmGH0,29
109
109
  prefect/client/orchestration.py,sha256=i8vGi2x-soE_POndRFRlsq5EoIn2bRHBS_D8QsNkzNg,103706
110
- prefect/client/subscriptions.py,sha256=eaoM0aqa7TUIXIhoJ29qlPSg_9R94O6KI48-BvNkcgU,2624
110
+ prefect/client/subscriptions.py,sha256=L64hNHYXLr3a5hLVB5OxfWF28fehGw5ZN8Ovsmzgd9s,2922
111
111
  prefect/client/utilities.py,sha256=ejALWrVYuqW-A2zKJkAuRXDkhZ5e8fsiEkn-wI1tzF0,1998
112
112
  prefect/client/schemas/__init__.py,sha256=KlyqFV-hMulMkNstBn_0ijoHoIwJZaBj6B1r07UmgvE,607
113
113
  prefect/client/schemas/actions.py,sha256=R4MUsb_1GuEsYLoLnU8jIfCBobJJFbDydaZE24mkqTc,25206
114
114
  prefect/client/schemas/filters.py,sha256=r6gnxZREnmE8Glt2SF6vPxHr0SIeiFBjTrrN32cw-Mo,35514
115
- prefect/client/schemas/objects.py,sha256=meib2ZRmECf_Gn3TJL8l8_Yf8VFWbJK-IvBvrjRQWoE,53888
115
+ prefect/client/schemas/objects.py,sha256=hBPoFoFO2Ew2LIWDAT0krrS9tR3zduP9FxWJg7AKRik,54166
116
116
  prefect/client/schemas/responses.py,sha256=nSYhg2Kl477RdczNsA731vpcJqF93WDnM6eMya3F7qI,9152
117
117
  prefect/client/schemas/schedules.py,sha256=ncGWmmBzZvf5G4AL27E0kWGiJxGX-haR2_-GUNvFlv4,14829
118
118
  prefect/client/schemas/sorting.py,sha256=Y-ea8k_vTUKAPKIxqGebwLSXM7x1s5mJ_4-sDd1Ivi8,2276
@@ -139,8 +139,8 @@ prefect/events/filters.py,sha256=vSWHGDCCsi_znQs3gZomCxh-Q498ukn_QHJ7H8q16do,692
139
139
  prefect/events/instrument.py,sha256=uNiD7AnkfuiwTsCMgNyJURmY9H2tXNfLCb3EC5FL0Qw,3805
140
140
  prefect/events/related.py,sha256=N0o19kTlos1V4L4AgO79Z_k06ZW9bfjSH8Xa9h7lugg,6746
141
141
  prefect/events/schemas.py,sha256=hXluVgDtEsjYghZhf-MjPhHntmMR6NcJJxU99ZRv2zQ,11643
142
- prefect/events/utilities.py,sha256=JApOECe-09SU8QabSnyykdl8fzsXE28RVSCBcFDseH4,2374
143
- prefect/events/worker.py,sha256=4Uw-_fiLa449gD2QsEOhubQwxpEyQn8PUo9N_zsJY1M,2684
142
+ prefect/events/utilities.py,sha256=gUEJA_kVuYASCqDpGX0HwDW0yczMX0AdgmxXbxhzWbM,2452
143
+ prefect/events/worker.py,sha256=Z6MZmcCyXZtWi4vEtnFyvnzIEBW7HD14lEH1Crye3gY,2716
144
144
  prefect/infrastructure/__init__.py,sha256=Fm1Rhc4I7ZfJePpUAl1F4iNEtcDugoT650WXXt6xoCM,770
145
145
  prefect/infrastructure/base.py,sha256=BvgY2HY1u_JEeb6u2HhnN7k86aicfOTheD-urU7bcTI,10196
146
146
  prefect/infrastructure/container.py,sha256=_CbrVW2ZYmkrc-jLNNuZyblmmX8uNVYUKHeTZWH0jcg,30914
@@ -169,7 +169,7 @@ prefect/packaging/orion.py,sha256=ctWh8s3UztYfOTsZ0sfumebI0dbNDOTriDNXohtEC-k,19
169
169
  prefect/packaging/serializers.py,sha256=1x5GjcBSYrE-YMmrpYYZi2ObTs7MM6YEM3LS0e6mHAk,6321
170
170
  prefect/runner/__init__.py,sha256=d3DFUXy5BYd8Z4cppNN_6RTSddmr-KfnQ5Yw5vh8WL8,96
171
171
  prefect/runner/runner.py,sha256=naKqAUl5cboL0xedksuNWgabAzAAy-AxNcL25N_C8KQ,47326
172
- prefect/runner/server.py,sha256=AqbvszD2OQkQe_5ydlyXZGYriSZiYDE7vpbRATstJ-Q,10648
172
+ prefect/runner/server.py,sha256=zSUyzBgijEl0nwFGBtBH6CIUjFBNfeaWKB1WkyRvVqw,10649
173
173
  prefect/runner/storage.py,sha256=iZey8Am51c1fZFpS9iVXWYpKiM_lSocvaJEOZVExhvA,22428
174
174
  prefect/runner/submit.py,sha256=w53VdsqfwjW-M3e8hUAAoVlNrXsvGuuyGpEN0wi3vX0,8537
175
175
  prefect/runner/utils.py,sha256=G8qv6AwAa43HcgLOo5vDhoXna1xP0HlaMVYEbAv0Pck,3318
@@ -186,14 +186,14 @@ prefect/software/pip.py,sha256=nzeTdvHnMl-u7Jr1bckgYSesxuTrXBkbFZJRDMrp9hk,4038
186
186
  prefect/software/python.py,sha256=reuEJFZPJ5PrDMfK3BuPpYieHNkOXJAyCAaopQcjDqE,1767
187
187
  prefect/utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
188
188
  prefect/utilities/annotations.py,sha256=p33yhh1Zx8BZUlTtl8gKRbpwWU9FVnZ8cfYrcd5KxDI,3103
189
- prefect/utilities/asyncutils.py,sha256=WZArC1fgtHxzwDny0F8_7h1WuwQd8VSef2ZUEy2SBnY,15333
189
+ prefect/utilities/asyncutils.py,sha256=SoG5pdD3_q0Y_NR4MaqNv6MWizY8Z2Ggfe0ZExupDd4,15673
190
190
  prefect/utilities/callables.py,sha256=zev1GNU_VHjVb_r_vAzTJ4oSXfWODPPnj7yDtaPipFY,11539
191
191
  prefect/utilities/collections.py,sha256=BAldQs4jRG-4EYhuKDREHhMVOzLRvHty_A4VfuhnqfQ,15435
192
192
  prefect/utilities/compat.py,sha256=mNQZDnzyKaOqy-OV-DnmH_dc7CNF5nQgW_EsA4xMr7g,906
193
193
  prefect/utilities/context.py,sha256=nb_Kui1q9cYK5fLy84baoBzko5-mOToQkd1AnZhwyq8,418
194
194
  prefect/utilities/dispatch.py,sha256=dcezbuJRsD_YYfJrsk5pGiqzJsdUhb9RxJ_lq8nXeds,5466
195
195
  prefect/utilities/dockerutils.py,sha256=4sd21GswRcTB-A8pwTfFTvyEenJaH8Xk2aOKFIIUpxs,19926
196
- prefect/utilities/filesystem.py,sha256=JIVhlG10PAFO_S85T1i2tuwJLiz40TTrcLsIZczR0GY,4359
196
+ prefect/utilities/filesystem.py,sha256=M_TeZ1MftjBf7hDLWk-Iphir369TpJ1binMsBKiO9YE,4449
197
197
  prefect/utilities/hashing.py,sha256=EOwZLmoIZImuSTxAvVqInabxJ-4RpEfYeg9e2EDQF8o,1752
198
198
  prefect/utilities/importtools.py,sha256=isblzKv7EPo7HtnlKYpL4t-GJdtTjUSMmvXgXSMEVZM,11764
199
199
  prefect/utilities/math.py,sha256=wLwcKVidpNeWQi1TUIWWLHGjlz9UgboX9FUGhx_CQzo,2821
@@ -213,8 +213,8 @@ prefect/workers/block.py,sha256=lvKlaWdA-DCCXDX23HHK9M5urEq4x2wmpKtU9ft3a7k,7767
213
213
  prefect/workers/process.py,sha256=Kxj_eZYh6R8t8253LYIIafiG7dodCF8RZABwd3Ng_R0,10253
214
214
  prefect/workers/server.py,sha256=WVZJxR8nTMzK0ov0BD0xw5OyQpT26AxlXbsGQ1OrxeQ,1551
215
215
  prefect/workers/utilities.py,sha256=VfPfAlGtTuDj0-Kb8WlMgAuOfgXCdrGAnKMapPSBrwc,2483
216
- prefect_client-2.14.20.dist-info/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
217
- prefect_client-2.14.20.dist-info/METADATA,sha256=-tM7M4vqRCG31YMFta1-ZuXEOKVNlXRwmvQSBOuPrX0,8143
218
- prefect_client-2.14.20.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
219
- prefect_client-2.14.20.dist-info/top_level.txt,sha256=MJZYJgFdbRc2woQCeB4vM6T33tr01TmkEhRcns6H_H4,8
220
- prefect_client-2.14.20.dist-info/RECORD,,
216
+ prefect_client-2.14.21.dist-info/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
217
+ prefect_client-2.14.21.dist-info/METADATA,sha256=SIxa2qbMN2dfmdaxhZXytaZ6wZ4r-GxEEL9Ay--7SOk,8143
218
+ prefect_client-2.14.21.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
219
+ prefect_client-2.14.21.dist-info/top_level.txt,sha256=MJZYJgFdbRc2woQCeB4vM6T33tr01TmkEhRcns6H_H4,8
220
+ prefect_client-2.14.21.dist-info/RECORD,,