prefect-client 2.18.0__py3-none-any.whl → 2.18.2__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/_internal/schemas/fields.py +31 -12
- prefect/automations.py +162 -0
- prefect/blocks/core.py +1 -1
- prefect/blocks/notifications.py +2 -2
- prefect/blocks/system.py +2 -3
- prefect/client/orchestration.py +309 -30
- prefect/client/schemas/objects.py +11 -8
- prefect/client/schemas/sorting.py +9 -0
- prefect/client/utilities.py +25 -3
- prefect/concurrency/asyncio.py +11 -5
- prefect/concurrency/events.py +3 -3
- prefect/concurrency/services.py +1 -1
- prefect/concurrency/sync.py +9 -5
- prefect/deployments/deployments.py +27 -18
- prefect/deployments/runner.py +34 -26
- prefect/engine.py +3 -1
- prefect/events/actions.py +2 -1
- prefect/events/cli/automations.py +207 -46
- prefect/events/clients.py +53 -20
- prefect/events/filters.py +31 -4
- prefect/events/instrument.py +40 -40
- prefect/events/related.py +2 -1
- prefect/events/schemas/automations.py +52 -7
- prefect/events/schemas/deployment_triggers.py +16 -228
- prefect/events/schemas/events.py +18 -11
- prefect/events/schemas/labelling.py +1 -1
- prefect/events/utilities.py +1 -1
- prefect/events/worker.py +10 -7
- prefect/flows.py +42 -24
- prefect/input/actions.py +9 -9
- prefect/input/run_input.py +51 -37
- prefect/new_flow_engine.py +444 -0
- prefect/new_task_engine.py +488 -0
- prefect/results.py +3 -2
- prefect/runner/runner.py +3 -2
- prefect/server/api/collections_data/views/aggregate-worker-metadata.json +45 -4
- prefect/settings.py +47 -0
- prefect/states.py +25 -19
- prefect/tasks.py +146 -19
- prefect/utilities/asyncutils.py +41 -0
- prefect/utilities/engine.py +6 -4
- prefect/utilities/schema_tools/validation.py +1 -1
- prefect/workers/process.py +2 -1
- {prefect_client-2.18.0.dist-info → prefect_client-2.18.2.dist-info}/METADATA +1 -1
- {prefect_client-2.18.0.dist-info → prefect_client-2.18.2.dist-info}/RECORD +48 -46
- prefect/concurrency/common.py +0 -0
- {prefect_client-2.18.0.dist-info → prefect_client-2.18.2.dist-info}/LICENSE +0 -0
- {prefect_client-2.18.0.dist-info → prefect_client-2.18.2.dist-info}/WHEEL +0 -0
- {prefect_client-2.18.0.dist-info → prefect_client-2.18.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,488 @@
|
|
1
|
+
import asyncio
|
2
|
+
import inspect
|
3
|
+
import logging
|
4
|
+
from contextlib import asynccontextmanager, contextmanager
|
5
|
+
from dataclasses import dataclass, field
|
6
|
+
from typing import (
|
7
|
+
Any,
|
8
|
+
Callable,
|
9
|
+
Coroutine,
|
10
|
+
Dict,
|
11
|
+
Generic,
|
12
|
+
Iterable,
|
13
|
+
Literal,
|
14
|
+
Optional,
|
15
|
+
TypeVar,
|
16
|
+
Union,
|
17
|
+
cast,
|
18
|
+
)
|
19
|
+
from uuid import uuid4
|
20
|
+
|
21
|
+
import pendulum
|
22
|
+
from typing_extensions import ParamSpec
|
23
|
+
|
24
|
+
from prefect import Task, get_client
|
25
|
+
from prefect._internal.concurrency.cancellation import (
|
26
|
+
AlarmCancelScope,
|
27
|
+
AsyncCancelScope,
|
28
|
+
CancelledError,
|
29
|
+
)
|
30
|
+
from prefect.client.orchestration import PrefectClient
|
31
|
+
from prefect.client.schemas import TaskRun
|
32
|
+
from prefect.client.schemas.objects import TaskRunResult
|
33
|
+
from prefect.context import FlowRunContext, TaskRunContext
|
34
|
+
from prefect.futures import PrefectFuture, resolve_futures_to_states
|
35
|
+
from prefect.logging.loggers import get_logger, task_run_logger
|
36
|
+
from prefect.results import ResultFactory
|
37
|
+
from prefect.server.schemas.states import State
|
38
|
+
from prefect.settings import PREFECT_TASKS_REFRESH_CACHE
|
39
|
+
from prefect.states import (
|
40
|
+
Pending,
|
41
|
+
Retrying,
|
42
|
+
Running,
|
43
|
+
StateDetails,
|
44
|
+
exception_to_crashed_state,
|
45
|
+
exception_to_failed_state,
|
46
|
+
return_value_to_state,
|
47
|
+
)
|
48
|
+
from prefect.utilities.asyncutils import A, Async, is_async_fn, run_sync
|
49
|
+
from prefect.utilities.callables import parameters_to_args_kwargs
|
50
|
+
from prefect.utilities.engine import (
|
51
|
+
_dynamic_key_for_task_run,
|
52
|
+
_get_hook_name,
|
53
|
+
_resolve_custom_task_run_name,
|
54
|
+
collect_task_run_inputs,
|
55
|
+
propose_state,
|
56
|
+
)
|
57
|
+
|
58
|
+
P = ParamSpec("P")
|
59
|
+
R = TypeVar("R")
|
60
|
+
|
61
|
+
|
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
|
+
@dataclass
|
81
|
+
class TaskRunEngine(Generic[P, R]):
|
82
|
+
task: Union[Task[P, R], Task[P, Coroutine[Any, Any, R]]]
|
83
|
+
logger: logging.Logger = field(default_factory=lambda: get_logger("engine"))
|
84
|
+
parameters: Optional[Dict[str, Any]] = None
|
85
|
+
task_run: Optional[TaskRun] = None
|
86
|
+
retries: int = 0
|
87
|
+
_is_started: bool = False
|
88
|
+
_client: Optional[PrefectClient] = None
|
89
|
+
|
90
|
+
def __post_init__(self):
|
91
|
+
if self.parameters is None:
|
92
|
+
self.parameters = {}
|
93
|
+
|
94
|
+
@property
|
95
|
+
def client(self) -> PrefectClient:
|
96
|
+
if not self._is_started or self._client is None:
|
97
|
+
raise RuntimeError("Engine has not started.")
|
98
|
+
return self._client
|
99
|
+
|
100
|
+
@property
|
101
|
+
def state(self) -> State:
|
102
|
+
if not self.task_run:
|
103
|
+
raise ValueError("Task run is not set")
|
104
|
+
return self.task_run.state
|
105
|
+
|
106
|
+
@property
|
107
|
+
def can_retry(self) -> bool:
|
108
|
+
retry_condition: Optional[
|
109
|
+
Callable[[Task[P, Coroutine[Any, Any, R]], TaskRun, State], bool]
|
110
|
+
] = self.task.retry_condition_fn
|
111
|
+
if not self.task_run:
|
112
|
+
raise ValueError("Task run is not set")
|
113
|
+
return not retry_condition or retry_condition(
|
114
|
+
self.task, self.task_run, self.state
|
115
|
+
)
|
116
|
+
|
117
|
+
async def _run_hooks(self, state: State) -> None:
|
118
|
+
"""Run the on_failure and on_completion hooks for a task, making sure to
|
119
|
+
catch and log any errors that occur.
|
120
|
+
"""
|
121
|
+
task = self.task
|
122
|
+
task_run = self.task_run
|
123
|
+
|
124
|
+
if not task_run:
|
125
|
+
raise ValueError("Task run is not set")
|
126
|
+
|
127
|
+
hooks = None
|
128
|
+
if state.is_failed() and task.on_failure:
|
129
|
+
hooks = task.on_failure
|
130
|
+
elif state.is_completed() and task.on_completion:
|
131
|
+
hooks = task.on_completion
|
132
|
+
|
133
|
+
if hooks:
|
134
|
+
for hook in hooks:
|
135
|
+
hook_name = _get_hook_name(hook)
|
136
|
+
try:
|
137
|
+
self.logger.info(
|
138
|
+
f"Running hook {hook_name!r} in response to entering state"
|
139
|
+
f" {state.name!r}"
|
140
|
+
)
|
141
|
+
if is_async_fn(hook):
|
142
|
+
await hook(task, task_run, state)
|
143
|
+
else:
|
144
|
+
hook(task, task_run, state)
|
145
|
+
except Exception:
|
146
|
+
self.logger.error(
|
147
|
+
f"An error was encountered while running hook {hook_name!r}",
|
148
|
+
exc_info=True,
|
149
|
+
)
|
150
|
+
else:
|
151
|
+
self.logger.info(
|
152
|
+
f"Hook {hook_name!r} finished running successfully"
|
153
|
+
)
|
154
|
+
|
155
|
+
def _compute_state_details(
|
156
|
+
self, include_cache_expiration: bool = False
|
157
|
+
) -> StateDetails:
|
158
|
+
## setup cache metadata
|
159
|
+
task_run_context = TaskRunContext.get()
|
160
|
+
cache_key = (
|
161
|
+
self.task.cache_key_fn(
|
162
|
+
task_run_context,
|
163
|
+
self.parameters or {},
|
164
|
+
)
|
165
|
+
if self.task.cache_key_fn
|
166
|
+
else None
|
167
|
+
)
|
168
|
+
# Ignore the cached results for a cache key, default = false
|
169
|
+
# Setting on task level overrules the Prefect setting (env var)
|
170
|
+
refresh_cache = (
|
171
|
+
self.task.refresh_cache
|
172
|
+
if self.task.refresh_cache is not None
|
173
|
+
else PREFECT_TASKS_REFRESH_CACHE.value()
|
174
|
+
)
|
175
|
+
|
176
|
+
if include_cache_expiration:
|
177
|
+
cache_expiration = (
|
178
|
+
(pendulum.now("utc") + self.task.cache_expiration)
|
179
|
+
if self.task.cache_expiration
|
180
|
+
else None
|
181
|
+
)
|
182
|
+
else:
|
183
|
+
cache_expiration = None
|
184
|
+
return StateDetails(
|
185
|
+
cache_key=cache_key,
|
186
|
+
refresh_cache=refresh_cache,
|
187
|
+
cache_expiration=cache_expiration,
|
188
|
+
)
|
189
|
+
|
190
|
+
async def begin_run(self):
|
191
|
+
state_details = self._compute_state_details()
|
192
|
+
new_state = Running(state_details=state_details)
|
193
|
+
state = await self.set_state(new_state)
|
194
|
+
while state.is_pending():
|
195
|
+
await asyncio.sleep(1)
|
196
|
+
state = await self.set_state(new_state)
|
197
|
+
|
198
|
+
async def set_state(self, state: State, force: bool = False) -> State:
|
199
|
+
if not self.task_run:
|
200
|
+
raise ValueError("Task run is not set")
|
201
|
+
new_state = await propose_state(
|
202
|
+
self.client, state, task_run_id=self.task_run.id, force=force
|
203
|
+
) # type: ignore
|
204
|
+
|
205
|
+
# currently this is a hack to keep a reference to the state object
|
206
|
+
# that has an in-memory result attached to it; using the API state
|
207
|
+
# could result in losing that reference
|
208
|
+
self.task_run.state = new_state
|
209
|
+
if new_state.is_final():
|
210
|
+
await self._run_hooks(new_state)
|
211
|
+
return new_state
|
212
|
+
|
213
|
+
async def result(self, raise_on_failure: bool = True) -> "Union[R, State, None]":
|
214
|
+
_result = self.state.result(raise_on_failure=raise_on_failure, fetch=True)
|
215
|
+
# state.result is a `sync_compatible` function that may or may not return an awaitable
|
216
|
+
# depending on whether the parent frame is sync or not
|
217
|
+
if inspect.isawaitable(_result):
|
218
|
+
_result = await _result
|
219
|
+
return _result
|
220
|
+
|
221
|
+
async def handle_success(self, result: R) -> R:
|
222
|
+
result_factory = getattr(TaskRunContext.get(), "result_factory", None)
|
223
|
+
if result_factory is None:
|
224
|
+
raise ValueError("Result factory is not set")
|
225
|
+
terminal_state = await return_value_to_state(
|
226
|
+
await resolve_futures_to_states(result),
|
227
|
+
result_factory=result_factory,
|
228
|
+
)
|
229
|
+
terminal_state.state_details = self._compute_state_details(
|
230
|
+
include_cache_expiration=True
|
231
|
+
)
|
232
|
+
await self.set_state(terminal_state)
|
233
|
+
return result
|
234
|
+
|
235
|
+
async def handle_retry(self, exc: Exception) -> bool:
|
236
|
+
"""
|
237
|
+
If the task has retries left, and the retry condition is met, set the task to retrying.
|
238
|
+
- If the task has no retries left, or the retry condition is not met, return False.
|
239
|
+
- If the task has retries left, and the retry condition is met, return True.
|
240
|
+
"""
|
241
|
+
if self.retries < self.task.retries and self.can_retry:
|
242
|
+
await self.set_state(Retrying(), force=True)
|
243
|
+
self.retries = self.retries + 1
|
244
|
+
return True
|
245
|
+
return False
|
246
|
+
|
247
|
+
async def handle_exception(self, exc: Exception) -> None:
|
248
|
+
# If the task fails, and we have retries left, set the task to retrying.
|
249
|
+
if not await self.handle_retry(exc):
|
250
|
+
# If the task has no retries left, or the retry condition is not met, set the task to failed.
|
251
|
+
context = TaskRunContext.get()
|
252
|
+
state = await exception_to_failed_state(
|
253
|
+
exc,
|
254
|
+
message="Task run encountered an exception",
|
255
|
+
result_factory=getattr(context, "result_factory", None),
|
256
|
+
)
|
257
|
+
await self.set_state(state)
|
258
|
+
|
259
|
+
async def handle_crash(self, exc: BaseException) -> None:
|
260
|
+
state = await exception_to_crashed_state(exc)
|
261
|
+
self.logger.error(f"Crash detected! {state.message}")
|
262
|
+
self.logger.debug("Crash details:", exc_info=exc)
|
263
|
+
await self.set_state(state, force=True)
|
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
|
327
|
+
|
328
|
+
@contextmanager
|
329
|
+
def enter_run_context_sync(self, client: Optional[PrefectClient] = None):
|
330
|
+
if client is None:
|
331
|
+
client = self.client
|
332
|
+
if not self.task_run:
|
333
|
+
raise ValueError("Task run is not set")
|
334
|
+
|
335
|
+
self.task_run = run_sync(client.read_task_run(self.task_run.id))
|
336
|
+
|
337
|
+
with TaskRunContext(
|
338
|
+
task=self.task,
|
339
|
+
log_prints=self.task.log_prints or False,
|
340
|
+
task_run=self.task_run,
|
341
|
+
parameters=self.parameters,
|
342
|
+
result_factory=run_sync(ResultFactory.from_autonomous_task(self.task)), # type: ignore
|
343
|
+
client=client,
|
344
|
+
):
|
345
|
+
self.logger = task_run_logger(task_run=self.task_run, task=self.task) # type: ignore
|
346
|
+
yield
|
347
|
+
|
348
|
+
@asynccontextmanager
|
349
|
+
async def start(self):
|
350
|
+
"""
|
351
|
+
Enters a client context and creates a task run if needed.
|
352
|
+
"""
|
353
|
+
async with get_client() as client:
|
354
|
+
self._client = client
|
355
|
+
self._is_started = True
|
356
|
+
try:
|
357
|
+
if not self.task_run:
|
358
|
+
self.task_run = await self.create_task_run(client)
|
359
|
+
yield self
|
360
|
+
except Exception:
|
361
|
+
# regular exceptions are caught and re-raised to the user
|
362
|
+
raise
|
363
|
+
except BaseException as exc:
|
364
|
+
# BaseExceptions are caught and handled as crashes
|
365
|
+
await self.handle_crash(exc)
|
366
|
+
raise
|
367
|
+
finally:
|
368
|
+
self._is_started = False
|
369
|
+
self._client = None
|
370
|
+
|
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
|
+
async def get_client(self):
|
398
|
+
if not self._is_started:
|
399
|
+
raise RuntimeError("Engine has not started.")
|
400
|
+
else:
|
401
|
+
return self._client
|
402
|
+
|
403
|
+
def is_running(self) -> bool:
|
404
|
+
if getattr(self, "task_run", None) is None:
|
405
|
+
return False
|
406
|
+
return getattr(self, "task_run").state.is_running()
|
407
|
+
|
408
|
+
def is_pending(self) -> bool:
|
409
|
+
if getattr(self, "task_run", None) is None:
|
410
|
+
return False # TODO: handle this differently?
|
411
|
+
return getattr(self, "task_run").state.is_pending()
|
412
|
+
|
413
|
+
|
414
|
+
async def run_task(
|
415
|
+
task: Task[P, Coroutine[Any, Any, R]],
|
416
|
+
task_run: Optional[TaskRun] = None,
|
417
|
+
parameters: Optional[Dict[str, Any]] = None,
|
418
|
+
wait_for: Optional[Iterable[PrefectFuture[A, Async]]] = None,
|
419
|
+
return_type: Literal["state", "result"] = "result",
|
420
|
+
) -> 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
|
+
engine = TaskRunEngine[P, R](task=task, parameters=parameters, task_run=task_run)
|
427
|
+
|
428
|
+
# This is a context manager that keeps track of the run of the task run.
|
429
|
+
async with engine.start() as run:
|
430
|
+
await run.begin_run()
|
431
|
+
|
432
|
+
while run.is_running():
|
433
|
+
async with run.enter_run_context():
|
434
|
+
try:
|
435
|
+
# This is where the task is actually run.
|
436
|
+
async with timeout(seconds=run.task.timeout_seconds):
|
437
|
+
call_args, call_kwargs = parameters_to_args_kwargs(
|
438
|
+
task.fn, run.parameters or {}
|
439
|
+
)
|
440
|
+
result = cast(R, await task.fn(*call_args, **call_kwargs)) # type: ignore
|
441
|
+
|
442
|
+
# If the task run is successful, finalize it.
|
443
|
+
await run.handle_success(result)
|
444
|
+
if return_type == "result":
|
445
|
+
return result
|
446
|
+
|
447
|
+
except Exception as exc:
|
448
|
+
await run.handle_exception(exc)
|
449
|
+
|
450
|
+
if return_type == "state":
|
451
|
+
return run.state
|
452
|
+
return await run.result()
|
453
|
+
|
454
|
+
|
455
|
+
def run_task_sync(
|
456
|
+
task: Task[P, R],
|
457
|
+
task_run: Optional[TaskRun] = None,
|
458
|
+
parameters: Optional[Dict[str, Any]] = None,
|
459
|
+
wait_for: Optional[Iterable[PrefectFuture[A, Async]]] = None,
|
460
|
+
return_type: Literal["state", "result"] = "result",
|
461
|
+
) -> Union[R, State, None]:
|
462
|
+
engine = TaskRunEngine[P, R](task=task, parameters=parameters, task_run=task_run)
|
463
|
+
|
464
|
+
# This is a context manager that keeps track of the run of the task run.
|
465
|
+
with engine.start_sync() as run:
|
466
|
+
run_sync(run.begin_run())
|
467
|
+
|
468
|
+
while run.is_running():
|
469
|
+
with run.enter_run_context_sync():
|
470
|
+
try:
|
471
|
+
# This is where the task is actually run.
|
472
|
+
with timeout_sync(seconds=run.task.timeout_seconds):
|
473
|
+
call_args, call_kwargs = parameters_to_args_kwargs(
|
474
|
+
task.fn, run.parameters or {}
|
475
|
+
)
|
476
|
+
result = cast(R, task.fn(*call_args, **call_kwargs)) # type: ignore
|
477
|
+
|
478
|
+
# If the task run is successful, finalize it.
|
479
|
+
run_sync(run.handle_success(result))
|
480
|
+
if return_type == "result":
|
481
|
+
return result
|
482
|
+
|
483
|
+
except Exception as exc:
|
484
|
+
run_sync(run.handle_exception(exc))
|
485
|
+
|
486
|
+
if return_type == "state":
|
487
|
+
return run.state
|
488
|
+
return run_sync(run.result())
|
prefect/results.py
CHANGED
@@ -16,7 +16,7 @@ from typing import (
|
|
16
16
|
)
|
17
17
|
from uuid import UUID
|
18
18
|
|
19
|
-
from typing_extensions import Self
|
19
|
+
from typing_extensions import ParamSpec, Self
|
20
20
|
|
21
21
|
import prefect
|
22
22
|
from prefect._internal.pydantic import HAS_PYDANTIC_V2
|
@@ -63,6 +63,7 @@ def DEFAULT_STORAGE_KEY_FN():
|
|
63
63
|
|
64
64
|
|
65
65
|
logger = get_logger("results")
|
66
|
+
P = ParamSpec("P")
|
66
67
|
R = TypeVar("R")
|
67
68
|
|
68
69
|
|
@@ -286,7 +287,7 @@ class ResultFactory(pydantic.BaseModel):
|
|
286
287
|
@classmethod
|
287
288
|
@inject_client
|
288
289
|
async def from_autonomous_task(
|
289
|
-
cls: Type[Self], task: "Task", client: "PrefectClient" = None
|
290
|
+
cls: Type[Self], task: "Task[P, R]", client: "PrefectClient" = None
|
290
291
|
) -> Self:
|
291
292
|
"""
|
292
293
|
Create a new result factory for an autonomous task.
|
prefect/runner/runner.py
CHANGED
@@ -29,6 +29,7 @@ Example:
|
|
29
29
|
```
|
30
30
|
|
31
31
|
"""
|
32
|
+
|
32
33
|
import asyncio
|
33
34
|
import datetime
|
34
35
|
import inspect
|
@@ -80,7 +81,7 @@ from prefect.deployments.runner import (
|
|
80
81
|
)
|
81
82
|
from prefect.deployments.schedules import FlexibleScheduleList
|
82
83
|
from prefect.engine import propose_state
|
83
|
-
from prefect.events import DeploymentTriggerTypes
|
84
|
+
from prefect.events import DeploymentTriggerTypes, TriggerTypes
|
84
85
|
from prefect.exceptions import (
|
85
86
|
Abort,
|
86
87
|
)
|
@@ -232,7 +233,7 @@ class Runner:
|
|
232
233
|
schedule: Optional[SCHEDULE_TYPES] = None,
|
233
234
|
is_schedule_active: Optional[bool] = None,
|
234
235
|
parameters: Optional[dict] = None,
|
235
|
-
triggers: Optional[List[DeploymentTriggerTypes]] = None,
|
236
|
+
triggers: Optional[List[Union[DeploymentTriggerTypes, TriggerTypes]]] = None,
|
236
237
|
description: Optional[str] = None,
|
237
238
|
tags: Optional[List[str]] = None,
|
238
239
|
version: Optional[str] = None,
|
@@ -64,7 +64,7 @@
|
|
64
64
|
}
|
65
65
|
},
|
66
66
|
"description": "Execute flow runs as subprocesses on a worker. Works well for local execution when first getting started.",
|
67
|
-
"display_name": "
|
67
|
+
"display_name": "Process",
|
68
68
|
"documentation_url": "https://docs.prefect.io/latest/api-ref/prefect/workers/process/",
|
69
69
|
"install_command": "pip install prefect",
|
70
70
|
"is_beta": false,
|
@@ -111,10 +111,12 @@
|
|
111
111
|
"taskRoleArn": "{{ task_role_arn }}"
|
112
112
|
},
|
113
113
|
"tags": "{{ labels }}",
|
114
|
-
"taskDefinition": "{{ task_definition_arn }}"
|
114
|
+
"taskDefinition": "{{ task_definition_arn }}",
|
115
|
+
"capacityProviderStrategy": "{{ capacity_provider_strategy }}"
|
115
116
|
},
|
116
117
|
"configure_cloudwatch_logs": "{{ configure_cloudwatch_logs }}",
|
117
118
|
"cloudwatch_logs_options": "{{ cloudwatch_logs_options }}",
|
119
|
+
"cloudwatch_logs_prefix": "{{ cloudwatch_logs_prefix }}",
|
118
120
|
"network_configuration": "{{ network_configuration }}",
|
119
121
|
"stream_output": "{{ stream_output }}",
|
120
122
|
"task_start_timeout_seconds": "{{ task_start_timeout_seconds }}",
|
@@ -191,6 +193,14 @@
|
|
191
193
|
],
|
192
194
|
"type": "string"
|
193
195
|
},
|
196
|
+
"capacity_provider_strategy": {
|
197
|
+
"title": "Capacity Provider Strategy",
|
198
|
+
"description": "The capacity provider strategy to use when running the task. If a capacity provider strategy is specified, the selected launch type will be ignored.",
|
199
|
+
"type": "array",
|
200
|
+
"items": {
|
201
|
+
"$ref": "#/definitions/CapacityProvider"
|
202
|
+
}
|
203
|
+
},
|
194
204
|
"image": {
|
195
205
|
"title": "Image",
|
196
206
|
"description": "The image to use for the Prefect container in the task. If this value is not null, it will override the value in the task definition. This value defaults to a Prefect base image matching your local versions.",
|
@@ -239,6 +249,11 @@
|
|
239
249
|
"type": "string"
|
240
250
|
}
|
241
251
|
},
|
252
|
+
"cloudwatch_logs_prefix": {
|
253
|
+
"title": "Cloudwatch Logs Prefix",
|
254
|
+
"description": "When `configure_cloudwatch_logs` is enabled, this setting may be used to set a prefix for the log group. If not provided, the default prefix will be `prefect-logs_<work_pool_name>_<deployment_id>`. If `awslogs-stream-prefix` is present in `Cloudwatch logs options` this setting will be ignored.",
|
255
|
+
"type": "string"
|
256
|
+
},
|
242
257
|
"network_configuration": {
|
243
258
|
"title": "Network Configuration",
|
244
259
|
"description": "When `network_configuration` is supplied it will override ECS Worker'sawsvpcConfiguration that defined in the ECS task executing your workload. See the [AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-service-awsvpcconfiguration.html) for available options.",
|
@@ -370,6 +385,30 @@
|
|
370
385
|
"aws_secret_access_key"
|
371
386
|
],
|
372
387
|
"block_schema_references": {}
|
388
|
+
},
|
389
|
+
"CapacityProvider": {
|
390
|
+
"title": "CapacityProvider",
|
391
|
+
"description": "The capacity provider strategy to use when running the task.",
|
392
|
+
"type": "object",
|
393
|
+
"properties": {
|
394
|
+
"capacityProvider": {
|
395
|
+
"title": "Capacityprovider",
|
396
|
+
"type": "string"
|
397
|
+
},
|
398
|
+
"weight": {
|
399
|
+
"title": "Weight",
|
400
|
+
"type": "integer"
|
401
|
+
},
|
402
|
+
"base": {
|
403
|
+
"title": "Base",
|
404
|
+
"type": "integer"
|
405
|
+
}
|
406
|
+
},
|
407
|
+
"required": [
|
408
|
+
"capacityProvider",
|
409
|
+
"weight",
|
410
|
+
"base"
|
411
|
+
]
|
373
412
|
}
|
374
413
|
}
|
375
414
|
}
|
@@ -1100,7 +1139,9 @@
|
|
1100
1139
|
"serviceAccount": "{{ service_account_name }}",
|
1101
1140
|
"maxRetries": "{{ max_retries }}",
|
1102
1141
|
"timeout": "{{ timeout }}",
|
1103
|
-
"vpcAccess":
|
1142
|
+
"vpcAccess": {
|
1143
|
+
"connector": "{{ vpc_connector_name }}"
|
1144
|
+
},
|
1104
1145
|
"containers": [
|
1105
1146
|
{
|
1106
1147
|
"env": [],
|
@@ -1647,4 +1688,4 @@
|
|
1647
1688
|
"type": "kubernetes"
|
1648
1689
|
}
|
1649
1690
|
}
|
1650
|
-
}
|
1691
|
+
}
|