prefect-client 2.18.3__py3-none-any.whl → 2.19.1__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.
- prefect/__init__.py +1 -15
- prefect/_internal/compatibility/experimental.py +11 -2
- prefect/_internal/concurrency/cancellation.py +2 -0
- prefect/_internal/schemas/validators.py +10 -0
- prefect/_vendor/starlette/testclient.py +1 -1
- prefect/blocks/notifications.py +6 -6
- prefect/client/base.py +244 -1
- prefect/client/cloud.py +4 -2
- prefect/client/orchestration.py +515 -106
- prefect/client/schemas/actions.py +58 -8
- prefect/client/schemas/objects.py +15 -1
- prefect/client/schemas/responses.py +19 -0
- prefect/client/schemas/schedules.py +1 -1
- prefect/client/utilities.py +2 -2
- prefect/concurrency/asyncio.py +34 -4
- prefect/concurrency/sync.py +40 -6
- prefect/context.py +2 -2
- prefect/engine.py +2 -2
- prefect/events/clients.py +2 -2
- prefect/flows.py +91 -17
- prefect/infrastructure/process.py +0 -17
- prefect/logging/formatters.py +1 -4
- prefect/new_flow_engine.py +137 -168
- prefect/new_task_engine.py +137 -202
- prefect/runner/__init__.py +1 -1
- prefect/runner/runner.py +2 -107
- prefect/settings.py +21 -0
- prefect/tasks.py +76 -57
- prefect/types/__init__.py +27 -5
- prefect/utilities/annotations.py +1 -8
- prefect/utilities/asyncutils.py +4 -0
- prefect/utilities/engine.py +106 -1
- prefect/utilities/schema_tools/__init__.py +6 -1
- prefect/utilities/schema_tools/validation.py +25 -8
- prefect/utilities/timeout.py +34 -0
- prefect/workers/base.py +7 -3
- prefect/workers/process.py +0 -17
- {prefect_client-2.18.3.dist-info → prefect_client-2.19.1.dist-info}/METADATA +1 -1
- {prefect_client-2.18.3.dist-info → prefect_client-2.19.1.dist-info}/RECORD +42 -41
- {prefect_client-2.18.3.dist-info → prefect_client-2.19.1.dist-info}/LICENSE +0 -0
- {prefect_client-2.18.3.dist-info → prefect_client-2.19.1.dist-info}/WHEEL +0 -0
- {prefect_client-2.18.3.dist-info → prefect_client-2.19.1.dist-info}/top_level.txt +0 -0
prefect/new_task_engine.py
CHANGED
@@ -1,13 +1,14 @@
|
|
1
|
-
import asyncio
|
2
1
|
import inspect
|
3
2
|
import logging
|
4
|
-
|
3
|
+
import time
|
4
|
+
from contextlib import contextmanager
|
5
5
|
from dataclasses import dataclass, field
|
6
6
|
from typing import (
|
7
7
|
Any,
|
8
8
|
Callable,
|
9
9
|
Coroutine,
|
10
10
|
Dict,
|
11
|
+
Generator,
|
11
12
|
Generic,
|
12
13
|
Iterable,
|
13
14
|
Literal,
|
@@ -16,20 +17,13 @@ from typing import (
|
|
16
17
|
Union,
|
17
18
|
cast,
|
18
19
|
)
|
19
|
-
from uuid import uuid4
|
20
20
|
|
21
21
|
import pendulum
|
22
22
|
from typing_extensions import ParamSpec
|
23
23
|
|
24
24
|
from prefect import Task, get_client
|
25
|
-
from prefect.
|
26
|
-
AlarmCancelScope,
|
27
|
-
AsyncCancelScope,
|
28
|
-
CancelledError,
|
29
|
-
)
|
30
|
-
from prefect.client.orchestration import PrefectClient
|
25
|
+
from prefect.client.orchestration import SyncPrefectClient
|
31
26
|
from prefect.client.schemas import TaskRun
|
32
|
-
from prefect.client.schemas.objects import TaskRunResult
|
33
27
|
from prefect.context import FlowRunContext, TaskRunContext
|
34
28
|
from prefect.futures import PrefectFuture, resolve_futures_to_states
|
35
29
|
from prefect.logging.loggers import get_logger, task_run_logger
|
@@ -37,7 +31,6 @@ from prefect.results import ResultFactory
|
|
37
31
|
from prefect.server.schemas.states import State
|
38
32
|
from prefect.settings import PREFECT_TASKS_REFRESH_CACHE
|
39
33
|
from prefect.states import (
|
40
|
-
Pending,
|
41
34
|
Retrying,
|
42
35
|
Running,
|
43
36
|
StateDetails,
|
@@ -45,38 +38,18 @@ from prefect.states import (
|
|
45
38
|
exception_to_failed_state,
|
46
39
|
return_value_to_state,
|
47
40
|
)
|
48
|
-
from prefect.utilities.asyncutils import A, Async,
|
41
|
+
from prefect.utilities.asyncutils import A, Async, run_sync
|
49
42
|
from prefect.utilities.callables import parameters_to_args_kwargs
|
50
43
|
from prefect.utilities.engine import (
|
51
|
-
_dynamic_key_for_task_run,
|
52
44
|
_get_hook_name,
|
53
|
-
|
54
|
-
collect_task_run_inputs,
|
55
|
-
propose_state,
|
45
|
+
propose_state_sync,
|
56
46
|
)
|
47
|
+
from prefect.utilities.timeout import timeout, timeout_async
|
57
48
|
|
58
49
|
P = ParamSpec("P")
|
59
50
|
R = TypeVar("R")
|
60
51
|
|
61
52
|
|
62
|
-
@asynccontextmanager
|
63
|
-
async def timeout(seconds: Optional[float] = None):
|
64
|
-
try:
|
65
|
-
with AsyncCancelScope(timeout=seconds):
|
66
|
-
yield
|
67
|
-
except CancelledError:
|
68
|
-
raise TimeoutError(f"Task timed out after {seconds} second(s).")
|
69
|
-
|
70
|
-
|
71
|
-
@contextmanager
|
72
|
-
def timeout_sync(seconds: Optional[float] = None):
|
73
|
-
try:
|
74
|
-
with AlarmCancelScope(timeout=seconds):
|
75
|
-
yield
|
76
|
-
except CancelledError:
|
77
|
-
raise TimeoutError(f"Task timed out after {seconds} second(s).")
|
78
|
-
|
79
|
-
|
80
53
|
@dataclass
|
81
54
|
class TaskRunEngine(Generic[P, R]):
|
82
55
|
task: Union[Task[P, R], Task[P, Coroutine[Any, Any, R]]]
|
@@ -85,14 +58,14 @@ class TaskRunEngine(Generic[P, R]):
|
|
85
58
|
task_run: Optional[TaskRun] = None
|
86
59
|
retries: int = 0
|
87
60
|
_is_started: bool = False
|
88
|
-
_client: Optional[
|
61
|
+
_client: Optional[SyncPrefectClient] = None
|
89
62
|
|
90
63
|
def __post_init__(self):
|
91
64
|
if self.parameters is None:
|
92
65
|
self.parameters = {}
|
93
66
|
|
94
67
|
@property
|
95
|
-
def client(self) ->
|
68
|
+
def client(self) -> SyncPrefectClient:
|
96
69
|
if not self._is_started or self._client is None:
|
97
70
|
raise RuntimeError("Engine has not started.")
|
98
71
|
return self._client
|
@@ -114,10 +87,7 @@ class TaskRunEngine(Generic[P, R]):
|
|
114
87
|
self.task, self.task_run, self.state
|
115
88
|
)
|
116
89
|
|
117
|
-
|
118
|
-
"""Run the on_failure and on_completion hooks for a task, making sure to
|
119
|
-
catch and log any errors that occur.
|
120
|
-
"""
|
90
|
+
def get_hooks(self, state: State, as_async: bool = False) -> Iterable[Callable]:
|
121
91
|
task = self.task
|
122
92
|
task_run = self.task_run
|
123
93
|
|
@@ -130,18 +100,17 @@ class TaskRunEngine(Generic[P, R]):
|
|
130
100
|
elif state.is_completed() and task.on_completion:
|
131
101
|
hooks = task.on_completion
|
132
102
|
|
133
|
-
|
134
|
-
|
135
|
-
|
103
|
+
for hook in hooks or []:
|
104
|
+
hook_name = _get_hook_name(hook)
|
105
|
+
|
106
|
+
@contextmanager
|
107
|
+
def hook_context():
|
136
108
|
try:
|
137
109
|
self.logger.info(
|
138
110
|
f"Running hook {hook_name!r} in response to entering state"
|
139
111
|
f" {state.name!r}"
|
140
112
|
)
|
141
|
-
|
142
|
-
await hook(task, task_run, state)
|
143
|
-
else:
|
144
|
-
hook(task, task_run, state)
|
113
|
+
yield
|
145
114
|
except Exception:
|
146
115
|
self.logger.error(
|
147
116
|
f"An error was encountered while running hook {hook_name!r}",
|
@@ -152,6 +121,21 @@ class TaskRunEngine(Generic[P, R]):
|
|
152
121
|
f"Hook {hook_name!r} finished running successfully"
|
153
122
|
)
|
154
123
|
|
124
|
+
if as_async:
|
125
|
+
|
126
|
+
async def _hook_fn():
|
127
|
+
with hook_context():
|
128
|
+
result = hook(task, task_run, state)
|
129
|
+
if inspect.isawaitable(result):
|
130
|
+
await result
|
131
|
+
else:
|
132
|
+
|
133
|
+
def _hook_fn():
|
134
|
+
with hook_context():
|
135
|
+
hook(task, task_run, state)
|
136
|
+
|
137
|
+
yield _hook_fn
|
138
|
+
|
155
139
|
def _compute_state_details(
|
156
140
|
self, include_cache_expiration: bool = False
|
157
141
|
) -> StateDetails:
|
@@ -187,152 +171,92 @@ class TaskRunEngine(Generic[P, R]):
|
|
187
171
|
cache_expiration=cache_expiration,
|
188
172
|
)
|
189
173
|
|
190
|
-
|
174
|
+
def begin_run(self):
|
191
175
|
state_details = self._compute_state_details()
|
192
176
|
new_state = Running(state_details=state_details)
|
193
|
-
state =
|
177
|
+
state = self.set_state(new_state)
|
194
178
|
while state.is_pending():
|
195
|
-
|
196
|
-
state =
|
179
|
+
time.sleep(0.2)
|
180
|
+
state = self.set_state(new_state)
|
197
181
|
|
198
|
-
|
182
|
+
def set_state(self, state: State, force: bool = False) -> State:
|
199
183
|
if not self.task_run:
|
200
184
|
raise ValueError("Task run is not set")
|
201
|
-
new_state =
|
185
|
+
new_state = propose_state_sync(
|
202
186
|
self.client, state, task_run_id=self.task_run.id, force=force
|
203
|
-
)
|
187
|
+
)
|
188
|
+
# type: ignore
|
204
189
|
|
205
190
|
# currently this is a hack to keep a reference to the state object
|
206
191
|
# that has an in-memory result attached to it; using the API state
|
207
192
|
# could result in losing that reference
|
208
193
|
self.task_run.state = new_state
|
209
|
-
if new_state.is_final():
|
210
|
-
await self._run_hooks(new_state)
|
211
194
|
return new_state
|
212
195
|
|
213
|
-
|
196
|
+
def result(self, raise_on_failure: bool = True) -> "Union[R, State, None]":
|
214
197
|
_result = self.state.result(raise_on_failure=raise_on_failure, fetch=True)
|
215
198
|
# state.result is a `sync_compatible` function that may or may not return an awaitable
|
216
199
|
# depending on whether the parent frame is sync or not
|
217
200
|
if inspect.isawaitable(_result):
|
218
|
-
_result =
|
201
|
+
_result = run_sync(_result)
|
219
202
|
return _result
|
220
203
|
|
221
|
-
|
204
|
+
def handle_success(self, result: R) -> R:
|
222
205
|
result_factory = getattr(TaskRunContext.get(), "result_factory", None)
|
223
206
|
if result_factory is None:
|
224
207
|
raise ValueError("Result factory is not set")
|
225
|
-
terminal_state =
|
226
|
-
|
227
|
-
|
208
|
+
terminal_state = run_sync(
|
209
|
+
return_value_to_state(
|
210
|
+
run_sync(resolve_futures_to_states(result)),
|
211
|
+
result_factory=result_factory,
|
212
|
+
)
|
228
213
|
)
|
229
214
|
terminal_state.state_details = self._compute_state_details(
|
230
215
|
include_cache_expiration=True
|
231
216
|
)
|
232
|
-
|
217
|
+
self.set_state(terminal_state)
|
233
218
|
return result
|
234
219
|
|
235
|
-
|
220
|
+
def handle_retry(self, exc: Exception) -> bool:
|
236
221
|
"""
|
237
222
|
If the task has retries left, and the retry condition is met, set the task to retrying.
|
238
223
|
- If the task has no retries left, or the retry condition is not met, return False.
|
239
224
|
- If the task has retries left, and the retry condition is met, return True.
|
240
225
|
"""
|
241
226
|
if self.retries < self.task.retries and self.can_retry:
|
242
|
-
|
227
|
+
self.set_state(Retrying(), force=True)
|
243
228
|
self.retries = self.retries + 1
|
244
229
|
return True
|
245
230
|
return False
|
246
231
|
|
247
|
-
|
232
|
+
def handle_exception(self, exc: Exception) -> None:
|
248
233
|
# If the task fails, and we have retries left, set the task to retrying.
|
249
|
-
if not
|
234
|
+
if not self.handle_retry(exc):
|
250
235
|
# If the task has no retries left, or the retry condition is not met, set the task to failed.
|
251
236
|
context = TaskRunContext.get()
|
252
|
-
state =
|
253
|
-
|
254
|
-
|
255
|
-
|
237
|
+
state = run_sync(
|
238
|
+
exception_to_failed_state(
|
239
|
+
exc,
|
240
|
+
message="Task run encountered an exception",
|
241
|
+
result_factory=getattr(context, "result_factory", None),
|
242
|
+
)
|
256
243
|
)
|
257
|
-
|
244
|
+
self.set_state(state)
|
258
245
|
|
259
|
-
|
260
|
-
state =
|
246
|
+
def handle_crash(self, exc: BaseException) -> None:
|
247
|
+
state = run_sync(exception_to_crashed_state(exc))
|
261
248
|
self.logger.error(f"Crash detected! {state.message}")
|
262
249
|
self.logger.debug("Crash details:", exc_info=exc)
|
263
|
-
|
264
|
-
|
265
|
-
async def create_task_run(self, client: PrefectClient) -> TaskRun:
|
266
|
-
flow_run_ctx = FlowRunContext.get()
|
267
|
-
parameters = self.parameters or {}
|
268
|
-
try:
|
269
|
-
task_run_name = _resolve_custom_task_run_name(self.task, parameters)
|
270
|
-
except TypeError:
|
271
|
-
task_run_name = None
|
272
|
-
|
273
|
-
# prep input tracking
|
274
|
-
task_inputs = {
|
275
|
-
k: await collect_task_run_inputs(v) for k, v in parameters.items()
|
276
|
-
}
|
277
|
-
|
278
|
-
# anticipate nested runs
|
279
|
-
task_run_ctx = TaskRunContext.get()
|
280
|
-
if task_run_ctx:
|
281
|
-
task_inputs["wait_for"] = [TaskRunResult(id=task_run_ctx.task_run.id)] # type: ignore
|
282
|
-
|
283
|
-
# TODO: implement wait_for
|
284
|
-
# if wait_for:
|
285
|
-
# task_inputs["wait_for"] = await collect_task_run_inputs(wait_for)
|
286
|
-
|
287
|
-
if flow_run_ctx:
|
288
|
-
dynamic_key = _dynamic_key_for_task_run(
|
289
|
-
context=flow_run_ctx, task=self.task
|
290
|
-
)
|
291
|
-
else:
|
292
|
-
dynamic_key = uuid4().hex
|
293
|
-
task_run = await client.create_task_run(
|
294
|
-
task=self.task, # type: ignore
|
295
|
-
name=task_run_name,
|
296
|
-
flow_run_id=(
|
297
|
-
getattr(flow_run_ctx.flow_run, "id", None)
|
298
|
-
if flow_run_ctx and flow_run_ctx.flow_run
|
299
|
-
else None
|
300
|
-
),
|
301
|
-
dynamic_key=str(dynamic_key),
|
302
|
-
state=Pending(),
|
303
|
-
task_inputs=task_inputs, # type: ignore
|
304
|
-
)
|
305
|
-
return task_run
|
306
|
-
|
307
|
-
@asynccontextmanager
|
308
|
-
async def enter_run_context(self, client: Optional[PrefectClient] = None):
|
309
|
-
if client is None:
|
310
|
-
client = self.client
|
311
|
-
|
312
|
-
if not self.task_run:
|
313
|
-
raise ValueError("Task run is not set")
|
314
|
-
|
315
|
-
self.task_run = await client.read_task_run(self.task_run.id)
|
316
|
-
|
317
|
-
with TaskRunContext(
|
318
|
-
task=self.task,
|
319
|
-
log_prints=self.task.log_prints or False,
|
320
|
-
task_run=self.task_run,
|
321
|
-
parameters=self.parameters,
|
322
|
-
result_factory=await ResultFactory.from_autonomous_task(self.task), # type: ignore
|
323
|
-
client=client,
|
324
|
-
):
|
325
|
-
self.logger = task_run_logger(task_run=self.task_run, task=self.task) # type: ignore
|
326
|
-
yield
|
250
|
+
self.set_state(state, force=True)
|
327
251
|
|
328
252
|
@contextmanager
|
329
|
-
def
|
253
|
+
def enter_run_context(self, client: Optional[SyncPrefectClient] = None):
|
330
254
|
if client is None:
|
331
255
|
client = self.client
|
332
256
|
if not self.task_run:
|
333
257
|
raise ValueError("Task run is not set")
|
334
258
|
|
335
|
-
self.task_run =
|
259
|
+
self.task_run = client.read_task_run(self.task_run.id)
|
336
260
|
|
337
261
|
with TaskRunContext(
|
338
262
|
task=self.task,
|
@@ -342,58 +266,45 @@ class TaskRunEngine(Generic[P, R]):
|
|
342
266
|
result_factory=run_sync(ResultFactory.from_autonomous_task(self.task)), # type: ignore
|
343
267
|
client=client,
|
344
268
|
):
|
345
|
-
|
346
|
-
|
269
|
+
# set the logger to the task run logger
|
270
|
+
current_logger = self.logger
|
271
|
+
try:
|
272
|
+
self.logger = task_run_logger(task_run=self.task_run, task=self.task) # type: ignore
|
273
|
+
yield
|
274
|
+
finally:
|
275
|
+
self.logger = current_logger
|
347
276
|
|
348
|
-
@
|
349
|
-
|
277
|
+
@contextmanager
|
278
|
+
def start(self) -> Generator["TaskRunEngine", Any, Any]:
|
350
279
|
"""
|
351
280
|
Enters a client context and creates a task run if needed.
|
352
281
|
"""
|
353
|
-
|
282
|
+
with get_client(sync_client=True) as client:
|
354
283
|
self._client = client
|
355
284
|
self._is_started = True
|
356
285
|
try:
|
357
286
|
if not self.task_run:
|
358
|
-
self.task_run =
|
287
|
+
self.task_run = run_sync(
|
288
|
+
self.task.create_run(
|
289
|
+
client=client,
|
290
|
+
parameters=self.parameters,
|
291
|
+
flow_run_context=FlowRunContext.get(),
|
292
|
+
parent_task_run_context=TaskRunContext.get(),
|
293
|
+
)
|
294
|
+
)
|
295
|
+
|
359
296
|
yield self
|
360
297
|
except Exception:
|
361
298
|
# regular exceptions are caught and re-raised to the user
|
362
299
|
raise
|
363
300
|
except BaseException as exc:
|
364
301
|
# BaseExceptions are caught and handled as crashes
|
365
|
-
|
302
|
+
self.handle_crash(exc)
|
366
303
|
raise
|
367
304
|
finally:
|
368
305
|
self._is_started = False
|
369
306
|
self._client = None
|
370
307
|
|
371
|
-
@contextmanager
|
372
|
-
def start_sync(self):
|
373
|
-
"""
|
374
|
-
Enters a client context and creates a task run if needed.
|
375
|
-
"""
|
376
|
-
client = get_client()
|
377
|
-
run_sync(client.__aenter__())
|
378
|
-
self._client = client
|
379
|
-
self._is_started = True
|
380
|
-
try:
|
381
|
-
if not self.task_run:
|
382
|
-
self.task_run = run_sync(self.create_task_run(client))
|
383
|
-
yield self
|
384
|
-
except Exception:
|
385
|
-
# regular exceptions are caught and re-raised to the user
|
386
|
-
raise
|
387
|
-
except BaseException as exc:
|
388
|
-
# BaseExceptions are caught and handled as crashes
|
389
|
-
run_sync(self.handle_crash(exc))
|
390
|
-
raise
|
391
|
-
finally:
|
392
|
-
# quickly close client
|
393
|
-
run_sync(client.__aexit__(None, None, None))
|
394
|
-
self._is_started = False
|
395
|
-
self._client = None
|
396
|
-
|
397
308
|
async def get_client(self):
|
398
309
|
if not self._is_started:
|
399
310
|
raise RuntimeError("Engine has not started.")
|
@@ -411,78 +322,102 @@ class TaskRunEngine(Generic[P, R]):
|
|
411
322
|
return getattr(self, "task_run").state.is_pending()
|
412
323
|
|
413
324
|
|
414
|
-
|
415
|
-
task: Task[P,
|
325
|
+
def run_task_sync(
|
326
|
+
task: Task[P, R],
|
416
327
|
task_run: Optional[TaskRun] = None,
|
417
328
|
parameters: Optional[Dict[str, Any]] = None,
|
418
329
|
wait_for: Optional[Iterable[PrefectFuture[A, Async]]] = None,
|
419
330
|
return_type: Literal["state", "result"] = "result",
|
420
331
|
) -> Union[R, State, None]:
|
421
|
-
"""
|
422
|
-
Runs a task against the API.
|
423
|
-
|
424
|
-
We will most likely want to use this logic as a wrapper and return a coroutine for type inference.
|
425
|
-
"""
|
426
332
|
engine = TaskRunEngine[P, R](task=task, parameters=parameters, task_run=task_run)
|
427
333
|
|
428
334
|
# This is a context manager that keeps track of the run of the task run.
|
429
|
-
|
430
|
-
|
335
|
+
with engine.start() as run:
|
336
|
+
run.begin_run()
|
431
337
|
|
432
338
|
while run.is_running():
|
433
|
-
|
339
|
+
with run.enter_run_context():
|
434
340
|
try:
|
435
341
|
# This is where the task is actually run.
|
436
|
-
|
342
|
+
with timeout(seconds=run.task.timeout_seconds):
|
437
343
|
call_args, call_kwargs = parameters_to_args_kwargs(
|
438
344
|
task.fn, run.parameters or {}
|
439
345
|
)
|
440
|
-
result = cast(R,
|
346
|
+
result = cast(R, task.fn(*call_args, **call_kwargs)) # type: ignore
|
441
347
|
|
442
348
|
# If the task run is successful, finalize it.
|
443
|
-
|
444
|
-
if return_type == "result":
|
445
|
-
return result
|
349
|
+
run.handle_success(result)
|
446
350
|
|
447
351
|
except Exception as exc:
|
448
|
-
|
352
|
+
run.handle_exception(exc)
|
353
|
+
|
354
|
+
if run.state.is_final():
|
355
|
+
for hook in run.get_hooks(run.state):
|
356
|
+
hook()
|
449
357
|
|
450
358
|
if return_type == "state":
|
451
359
|
return run.state
|
452
|
-
return
|
360
|
+
return run.result()
|
453
361
|
|
454
362
|
|
455
|
-
def
|
456
|
-
task: Task[P, R],
|
363
|
+
async def run_task_async(
|
364
|
+
task: Task[P, Coroutine[Any, Any, R]],
|
457
365
|
task_run: Optional[TaskRun] = None,
|
458
366
|
parameters: Optional[Dict[str, Any]] = None,
|
459
367
|
wait_for: Optional[Iterable[PrefectFuture[A, Async]]] = None,
|
460
368
|
return_type: Literal["state", "result"] = "result",
|
461
369
|
) -> Union[R, State, None]:
|
370
|
+
"""
|
371
|
+
Runs a task against the API.
|
372
|
+
|
373
|
+
We will most likely want to use this logic as a wrapper and return a coroutine for type inference.
|
374
|
+
"""
|
462
375
|
engine = TaskRunEngine[P, R](task=task, parameters=parameters, task_run=task_run)
|
463
376
|
|
464
377
|
# This is a context manager that keeps track of the run of the task run.
|
465
|
-
with engine.
|
466
|
-
|
378
|
+
with engine.start() as run:
|
379
|
+
run.begin_run()
|
467
380
|
|
468
381
|
while run.is_running():
|
469
|
-
with run.
|
382
|
+
with run.enter_run_context():
|
470
383
|
try:
|
471
384
|
# This is where the task is actually run.
|
472
|
-
with
|
385
|
+
with timeout_async(seconds=run.task.timeout_seconds):
|
473
386
|
call_args, call_kwargs = parameters_to_args_kwargs(
|
474
387
|
task.fn, run.parameters or {}
|
475
388
|
)
|
476
|
-
result = cast(R, task.fn(*call_args, **call_kwargs)) # type: ignore
|
389
|
+
result = cast(R, await task.fn(*call_args, **call_kwargs)) # type: ignore
|
477
390
|
|
478
391
|
# If the task run is successful, finalize it.
|
479
|
-
|
480
|
-
if return_type == "result":
|
481
|
-
return result
|
392
|
+
run.handle_success(result)
|
482
393
|
|
483
394
|
except Exception as exc:
|
484
|
-
|
395
|
+
run.handle_exception(exc)
|
396
|
+
|
397
|
+
if run.state.is_final():
|
398
|
+
for hook in run.get_hooks(run.state, as_async=True):
|
399
|
+
await hook()
|
485
400
|
|
486
401
|
if return_type == "state":
|
487
402
|
return run.state
|
488
|
-
return
|
403
|
+
return run.result()
|
404
|
+
|
405
|
+
|
406
|
+
def run_task(
|
407
|
+
task: Task[P, R],
|
408
|
+
task_run: Optional[TaskRun] = None,
|
409
|
+
parameters: Optional[Dict[str, Any]] = None,
|
410
|
+
wait_for: Optional[Iterable[PrefectFuture[A, Async]]] = None,
|
411
|
+
return_type: Literal["state", "result"] = "result",
|
412
|
+
) -> Union[R, State, None]:
|
413
|
+
kwargs = dict(
|
414
|
+
task=task,
|
415
|
+
task_run=task_run,
|
416
|
+
parameters=parameters,
|
417
|
+
wait_for=wait_for,
|
418
|
+
return_type=return_type,
|
419
|
+
)
|
420
|
+
if task.isasync:
|
421
|
+
return run_task_async(**kwargs)
|
422
|
+
else:
|
423
|
+
return run_task_sync(**kwargs)
|
prefect/runner/__init__.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
from .runner import Runner
|
1
|
+
from .runner import Runner
|
2
2
|
from .submit import submit_to_runner, wait_for_submitted_runs
|