prefect-client 3.0.0rc19__py3-none-any.whl → 3.0.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 +0 -3
- prefect/_internal/compatibility/migration.py +1 -1
- prefect/artifacts.py +1 -1
- prefect/blocks/core.py +8 -5
- prefect/blocks/notifications.py +10 -10
- prefect/blocks/system.py +52 -16
- prefect/blocks/webhook.py +3 -1
- prefect/client/cloud.py +57 -7
- prefect/client/collections.py +1 -1
- prefect/client/orchestration.py +68 -7
- prefect/client/schemas/objects.py +40 -2
- prefect/concurrency/asyncio.py +8 -2
- prefect/concurrency/services.py +16 -6
- prefect/concurrency/sync.py +4 -1
- prefect/context.py +7 -9
- prefect/deployments/runner.py +3 -3
- prefect/exceptions.py +12 -0
- prefect/filesystems.py +5 -3
- prefect/flow_engine.py +16 -10
- prefect/flows.py +2 -4
- prefect/futures.py +2 -1
- prefect/locking/__init__.py +0 -0
- prefect/locking/memory.py +213 -0
- prefect/locking/protocol.py +122 -0
- prefect/logging/handlers.py +4 -1
- prefect/main.py +8 -6
- prefect/records/filesystem.py +4 -2
- prefect/records/result_store.py +12 -6
- prefect/results.py +768 -363
- prefect/settings.py +24 -10
- prefect/states.py +82 -27
- prefect/task_engine.py +51 -26
- prefect/task_worker.py +6 -4
- prefect/tasks.py +24 -6
- prefect/transactions.py +57 -36
- prefect/utilities/annotations.py +4 -3
- prefect/utilities/asyncutils.py +1 -1
- prefect/utilities/callables.py +1 -3
- prefect/utilities/dispatch.py +16 -11
- prefect/utilities/schema_tools/hydration.py +13 -0
- prefect/variables.py +34 -24
- prefect/workers/base.py +78 -18
- prefect/workers/process.py +1 -3
- {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.1.dist-info}/METADATA +2 -2
- {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.1.dist-info}/RECORD +48 -46
- prefect/manifests.py +0 -21
- {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.1.dist-info}/LICENSE +0 -0
- {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.1.dist-info}/WHEEL +0 -0
- {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.1.dist-info}/top_level.txt +0 -0
prefect/settings.py
CHANGED
@@ -425,13 +425,20 @@ def default_database_connection_url(settings: "Settings", value: Optional[str]):
|
|
425
425
|
f"Missing required database connection settings: {', '.join(missing)}"
|
426
426
|
)
|
427
427
|
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
428
|
+
# We only need SQLAlchemy here if we're parsing a remote database connection
|
429
|
+
# string. Import it here so that we don't require the prefect-client package
|
430
|
+
# to have SQLAlchemy installed.
|
431
|
+
from sqlalchemy import URL
|
432
|
+
|
433
|
+
return URL(
|
434
|
+
drivername=driver,
|
435
|
+
host=PREFECT_API_DATABASE_HOST.value_from(settings),
|
436
|
+
port=PREFECT_API_DATABASE_PORT.value_from(settings) or 5432,
|
437
|
+
username=PREFECT_API_DATABASE_USER.value_from(settings),
|
438
|
+
password=PREFECT_API_DATABASE_PASSWORD.value_from(settings),
|
439
|
+
database=PREFECT_API_DATABASE_NAME.value_from(settings),
|
440
|
+
query=[],
|
441
|
+
).render_as_string(hide_password=False)
|
435
442
|
|
436
443
|
elif driver == "sqlite+aiosqlite":
|
437
444
|
path = PREFECT_API_DATABASE_NAME.value_from(settings)
|
@@ -2184,13 +2191,20 @@ def _write_profiles_to(path: Path, profiles: ProfilesCollection) -> None:
|
|
2184
2191
|
return path.write_text(toml.dumps(profiles.to_dict()))
|
2185
2192
|
|
2186
2193
|
|
2187
|
-
def load_profiles() -> ProfilesCollection:
|
2194
|
+
def load_profiles(include_defaults: bool = True) -> ProfilesCollection:
|
2188
2195
|
"""
|
2189
|
-
Load
|
2196
|
+
Load profiles from the current profile path. Optionally include profiles from the
|
2197
|
+
default profile path.
|
2190
2198
|
"""
|
2191
|
-
|
2199
|
+
default_profiles = _read_profiles_from(DEFAULT_PROFILES_PATH)
|
2200
|
+
|
2201
|
+
if not include_defaults:
|
2202
|
+
if not PREFECT_PROFILES_PATH.value().exists():
|
2203
|
+
return ProfilesCollection([])
|
2204
|
+
return _read_profiles_from(PREFECT_PROFILES_PATH.value())
|
2192
2205
|
|
2193
2206
|
user_profiles_path = PREFECT_PROFILES_PATH.value()
|
2207
|
+
profiles = default_profiles
|
2194
2208
|
if user_profiles_path.exists():
|
2195
2209
|
user_profiles = _read_profiles_from(user_profiles_path)
|
2196
2210
|
|
prefect/states.py
CHANGED
@@ -18,13 +18,14 @@ from prefect.exceptions import (
|
|
18
18
|
CancelledRun,
|
19
19
|
CrashedRun,
|
20
20
|
FailedRun,
|
21
|
+
MissingContextError,
|
21
22
|
MissingResult,
|
22
23
|
PausedRun,
|
23
24
|
TerminationSignal,
|
24
25
|
UnfinishedRun,
|
25
26
|
)
|
26
|
-
from prefect.logging.loggers import get_logger
|
27
|
-
from prefect.results import BaseResult, R,
|
27
|
+
from prefect.logging.loggers import get_logger, get_run_logger
|
28
|
+
from prefect.results import BaseResult, R, ResultStore
|
28
29
|
from prefect.settings import PREFECT_ASYNC_FETCH_STATE_RESULT
|
29
30
|
from prefect.utilities.annotations import BaseAnnotation
|
30
31
|
from prefect.utilities.asyncutils import in_async_main_thread, sync_compatible
|
@@ -166,7 +167,7 @@ def format_exception(exc: BaseException, tb: TracebackType = None) -> str:
|
|
166
167
|
|
167
168
|
async def exception_to_crashed_state(
|
168
169
|
exc: BaseException,
|
169
|
-
|
170
|
+
result_store: Optional[ResultStore] = None,
|
170
171
|
) -> State:
|
171
172
|
"""
|
172
173
|
Takes an exception that occurs _outside_ of user code and converts it to a
|
@@ -205,8 +206,8 @@ async def exception_to_crashed_state(
|
|
205
206
|
f" {format_exception(exc)}"
|
206
207
|
)
|
207
208
|
|
208
|
-
if
|
209
|
-
data = await
|
209
|
+
if result_store:
|
210
|
+
data = await result_store.create_result(exc)
|
210
211
|
else:
|
211
212
|
# Attach the exception for local usage, will not be available when retrieved
|
212
213
|
# from the API
|
@@ -217,12 +218,18 @@ async def exception_to_crashed_state(
|
|
217
218
|
|
218
219
|
async def exception_to_failed_state(
|
219
220
|
exc: Optional[BaseException] = None,
|
220
|
-
|
221
|
+
result_store: Optional[ResultStore] = None,
|
222
|
+
write_result: bool = False,
|
221
223
|
**kwargs,
|
222
224
|
) -> State:
|
223
225
|
"""
|
224
226
|
Convenience function for creating `Failed` states from exceptions
|
225
227
|
"""
|
228
|
+
try:
|
229
|
+
local_logger = get_run_logger()
|
230
|
+
except MissingContextError:
|
231
|
+
local_logger = logger
|
232
|
+
|
226
233
|
if not exc:
|
227
234
|
_, exc, _ = sys.exc_info()
|
228
235
|
if exc is None:
|
@@ -232,8 +239,16 @@ async def exception_to_failed_state(
|
|
232
239
|
else:
|
233
240
|
pass
|
234
241
|
|
235
|
-
if
|
236
|
-
data = await
|
242
|
+
if result_store:
|
243
|
+
data = await result_store.create_result(exc)
|
244
|
+
if write_result:
|
245
|
+
try:
|
246
|
+
await data.write()
|
247
|
+
except Exception as exc:
|
248
|
+
local_logger.warning(
|
249
|
+
"Failed to write result: %s Execution will continue, but the result has not been written",
|
250
|
+
exc,
|
251
|
+
)
|
237
252
|
else:
|
238
253
|
# Attach the exception for local usage, will not be available when retrieved
|
239
254
|
# from the API
|
@@ -255,10 +270,10 @@ async def exception_to_failed_state(
|
|
255
270
|
|
256
271
|
async def return_value_to_state(
|
257
272
|
retval: R,
|
258
|
-
|
273
|
+
result_store: ResultStore,
|
259
274
|
key: Optional[str] = None,
|
260
275
|
expiration: Optional[datetime.datetime] = None,
|
261
|
-
|
276
|
+
write_result: bool = False,
|
262
277
|
) -> State[R]:
|
263
278
|
"""
|
264
279
|
Given a return value from a user's function, create a `State` the run should
|
@@ -280,6 +295,10 @@ async def return_value_to_state(
|
|
280
295
|
Callers should resolve all futures into states before passing return values to this
|
281
296
|
function.
|
282
297
|
"""
|
298
|
+
try:
|
299
|
+
local_logger = get_run_logger()
|
300
|
+
except MissingContextError:
|
301
|
+
local_logger = logger
|
283
302
|
|
284
303
|
if (
|
285
304
|
isinstance(retval, State)
|
@@ -288,16 +307,23 @@ async def return_value_to_state(
|
|
288
307
|
and not retval.state_details.task_run_id
|
289
308
|
):
|
290
309
|
state = retval
|
291
|
-
# Unless the user has already constructed a result explicitly, use the
|
310
|
+
# Unless the user has already constructed a result explicitly, use the store
|
292
311
|
# to update the data to the correct type
|
293
312
|
if not isinstance(state.data, BaseResult):
|
294
|
-
|
313
|
+
result = await result_store.create_result(
|
295
314
|
state.data,
|
296
315
|
key=key,
|
297
316
|
expiration=expiration,
|
298
|
-
defer_persistence=defer_persistence,
|
299
317
|
)
|
300
|
-
|
318
|
+
if write_result:
|
319
|
+
try:
|
320
|
+
await result.write()
|
321
|
+
except Exception as exc:
|
322
|
+
local_logger.warning(
|
323
|
+
"Encountered an error while persisting result: %s Execution will continue, but the result has not been persisted",
|
324
|
+
exc,
|
325
|
+
)
|
326
|
+
state.data = result
|
301
327
|
return state
|
302
328
|
|
303
329
|
# Determine a new state from the aggregate of contained states
|
@@ -333,15 +359,23 @@ async def return_value_to_state(
|
|
333
359
|
# TODO: We may actually want to set the data to a `StateGroup` object and just
|
334
360
|
# allow it to be unpacked into a tuple and such so users can interact with
|
335
361
|
# it
|
362
|
+
result = await result_store.create_result(
|
363
|
+
retval,
|
364
|
+
key=key,
|
365
|
+
expiration=expiration,
|
366
|
+
)
|
367
|
+
if write_result:
|
368
|
+
try:
|
369
|
+
await result.write()
|
370
|
+
except Exception as exc:
|
371
|
+
local_logger.warning(
|
372
|
+
"Encountered an error while persisting result: %s Execution will continue, but the result has not been persisted",
|
373
|
+
exc,
|
374
|
+
)
|
336
375
|
return State(
|
337
376
|
type=new_state_type,
|
338
377
|
message=message,
|
339
|
-
data=
|
340
|
-
retval,
|
341
|
-
key=key,
|
342
|
-
expiration=expiration,
|
343
|
-
defer_persistence=defer_persistence,
|
344
|
-
),
|
378
|
+
data=result,
|
345
379
|
)
|
346
380
|
|
347
381
|
# Generators aren't portable, implicitly convert them to a list.
|
@@ -354,14 +388,20 @@ async def return_value_to_state(
|
|
354
388
|
if isinstance(data, BaseResult):
|
355
389
|
return Completed(data=data)
|
356
390
|
else:
|
357
|
-
|
358
|
-
data
|
359
|
-
|
360
|
-
|
361
|
-
expiration=expiration,
|
362
|
-
defer_persistence=defer_persistence,
|
363
|
-
)
|
391
|
+
result = await result_store.create_result(
|
392
|
+
data,
|
393
|
+
key=key,
|
394
|
+
expiration=expiration,
|
364
395
|
)
|
396
|
+
if write_result:
|
397
|
+
try:
|
398
|
+
await result.write()
|
399
|
+
except Exception as exc:
|
400
|
+
local_logger.warning(
|
401
|
+
"Encountered an error while persisting result: %s Execution will continue, but the result has not been persisted",
|
402
|
+
exc,
|
403
|
+
)
|
404
|
+
return Completed(data=result)
|
365
405
|
|
366
406
|
|
367
407
|
@sync_compatible
|
@@ -684,6 +724,21 @@ def AwaitingRetry(
|
|
684
724
|
)
|
685
725
|
|
686
726
|
|
727
|
+
def AwaitingConcurrencySlot(
|
728
|
+
cls: Type[State[R]] = State,
|
729
|
+
scheduled_time: Optional[datetime.datetime] = None,
|
730
|
+
**kwargs: Any,
|
731
|
+
) -> State[R]:
|
732
|
+
"""Convenience function for creating `AwaitingConcurrencySlot` states.
|
733
|
+
|
734
|
+
Returns:
|
735
|
+
State: a AwaitingConcurrencySlot state
|
736
|
+
"""
|
737
|
+
return Scheduled(
|
738
|
+
cls=cls, scheduled_time=scheduled_time, name="AwaitingConcurrencySlot", **kwargs
|
739
|
+
)
|
740
|
+
|
741
|
+
|
687
742
|
def Retrying(cls: Type[State[R]] = State, **kwargs: Any) -> State[R]:
|
688
743
|
"""Convenience function for creating `Retrying` states.
|
689
744
|
|
prefect/task_engine.py
CHANGED
@@ -5,6 +5,7 @@ import time
|
|
5
5
|
from asyncio import CancelledError
|
6
6
|
from contextlib import ExitStack, asynccontextmanager, contextmanager
|
7
7
|
from dataclasses import dataclass, field
|
8
|
+
from functools import partial
|
8
9
|
from textwrap import dedent
|
9
10
|
from typing import (
|
10
11
|
Any,
|
@@ -54,8 +55,12 @@ from prefect.exceptions import (
|
|
54
55
|
)
|
55
56
|
from prefect.futures import PrefectFuture
|
56
57
|
from prefect.logging.loggers import get_logger, patch_print, task_run_logger
|
57
|
-
from prefect.records.result_store import
|
58
|
-
from prefect.results import
|
58
|
+
from prefect.records.result_store import ResultRecordStore
|
59
|
+
from prefect.results import (
|
60
|
+
BaseResult,
|
61
|
+
_format_user_supplied_storage_key,
|
62
|
+
get_current_result_store,
|
63
|
+
)
|
59
64
|
from prefect.settings import (
|
60
65
|
PREFECT_DEBUG_MODE,
|
61
66
|
PREFECT_TASKS_REFRESH_CACHE,
|
@@ -449,9 +454,9 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
|
|
449
454
|
return self._raised
|
450
455
|
|
451
456
|
def handle_success(self, result: R, transaction: Transaction) -> R:
|
452
|
-
|
453
|
-
if
|
454
|
-
raise ValueError("Result
|
457
|
+
result_store = getattr(TaskRunContext.get(), "result_store", None)
|
458
|
+
if result_store is None:
|
459
|
+
raise ValueError("Result store is not set")
|
455
460
|
|
456
461
|
if self.task.cache_expiration is not None:
|
457
462
|
expiration = pendulum.now("utc") + self.task.cache_expiration
|
@@ -461,16 +466,19 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
|
|
461
466
|
terminal_state = run_coro_as_sync(
|
462
467
|
return_value_to_state(
|
463
468
|
result,
|
464
|
-
|
469
|
+
result_store=result_store,
|
465
470
|
key=transaction.key,
|
466
471
|
expiration=expiration,
|
467
|
-
# defer persistence to transaction commit
|
468
|
-
defer_persistence=True,
|
469
472
|
)
|
470
473
|
)
|
474
|
+
|
475
|
+
# Avoid logging when running this rollback hook since it is not user-defined
|
476
|
+
handle_rollback = partial(self.handle_rollback)
|
477
|
+
handle_rollback.log_on_run = False
|
478
|
+
|
471
479
|
transaction.stage(
|
472
480
|
terminal_state.data,
|
473
|
-
on_rollback_hooks=[
|
481
|
+
on_rollback_hooks=[handle_rollback] + self.task.on_rollback_hooks,
|
474
482
|
on_commit_hooks=self.task.on_commit_hooks,
|
475
483
|
)
|
476
484
|
if transaction.is_committed():
|
@@ -535,7 +543,8 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
|
|
535
543
|
exception_to_failed_state(
|
536
544
|
exc,
|
537
545
|
message="Task run encountered an exception",
|
538
|
-
|
546
|
+
result_store=getattr(context, "result_store", None),
|
547
|
+
write_result=True,
|
539
548
|
)
|
540
549
|
)
|
541
550
|
self.record_terminal_state_timing(state)
|
@@ -586,7 +595,9 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
|
|
586
595
|
log_prints=log_prints,
|
587
596
|
task_run=self.task_run,
|
588
597
|
parameters=self.parameters,
|
589
|
-
|
598
|
+
result_store=get_current_result_store().update_for_task(
|
599
|
+
self.task, _sync=True
|
600
|
+
),
|
590
601
|
client=client,
|
591
602
|
)
|
592
603
|
)
|
@@ -705,17 +716,22 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
|
|
705
716
|
|
706
717
|
@contextmanager
|
707
718
|
def transaction_context(self) -> Generator[Transaction, None, None]:
|
708
|
-
result_factory = getattr(TaskRunContext.get(), "result_factory", None)
|
709
|
-
|
710
719
|
# refresh cache setting is now repurposes as overwrite transaction record
|
711
720
|
overwrite = (
|
712
721
|
self.task.refresh_cache
|
713
722
|
if self.task.refresh_cache is not None
|
714
723
|
else PREFECT_TASKS_REFRESH_CACHE.value()
|
715
724
|
)
|
725
|
+
|
726
|
+
result_store = getattr(TaskRunContext.get(), "result_store", None)
|
727
|
+
if result_store and result_store.persist_result:
|
728
|
+
store = ResultRecordStore(result_store=result_store)
|
729
|
+
else:
|
730
|
+
store = None
|
731
|
+
|
716
732
|
with transaction(
|
717
733
|
key=self.compute_transaction_key(),
|
718
|
-
store=
|
734
|
+
store=store,
|
719
735
|
overwrite=overwrite,
|
720
736
|
logger=self.logger,
|
721
737
|
) as txn:
|
@@ -950,9 +966,9 @@ class AsyncTaskRunEngine(BaseTaskRunEngine[P, R]):
|
|
950
966
|
return self._raised
|
951
967
|
|
952
968
|
async def handle_success(self, result: R, transaction: Transaction) -> R:
|
953
|
-
|
954
|
-
if
|
955
|
-
raise ValueError("Result
|
969
|
+
result_store = getattr(TaskRunContext.get(), "result_store", None)
|
970
|
+
if result_store is None:
|
971
|
+
raise ValueError("Result store is not set")
|
956
972
|
|
957
973
|
if self.task.cache_expiration is not None:
|
958
974
|
expiration = pendulum.now("utc") + self.task.cache_expiration
|
@@ -961,15 +977,18 @@ class AsyncTaskRunEngine(BaseTaskRunEngine[P, R]):
|
|
961
977
|
|
962
978
|
terminal_state = await return_value_to_state(
|
963
979
|
result,
|
964
|
-
|
980
|
+
result_store=result_store,
|
965
981
|
key=transaction.key,
|
966
982
|
expiration=expiration,
|
967
|
-
# defer persistence to transaction commit
|
968
|
-
defer_persistence=True,
|
969
983
|
)
|
984
|
+
|
985
|
+
# Avoid logging when running this rollback hook since it is not user-defined
|
986
|
+
handle_rollback = partial(self.handle_rollback)
|
987
|
+
handle_rollback.log_on_run = False
|
988
|
+
|
970
989
|
transaction.stage(
|
971
990
|
terminal_state.data,
|
972
|
-
on_rollback_hooks=[
|
991
|
+
on_rollback_hooks=[handle_rollback] + self.task.on_rollback_hooks,
|
973
992
|
on_commit_hooks=self.task.on_commit_hooks,
|
974
993
|
)
|
975
994
|
if transaction.is_committed():
|
@@ -1033,7 +1052,7 @@ class AsyncTaskRunEngine(BaseTaskRunEngine[P, R]):
|
|
1033
1052
|
state = await exception_to_failed_state(
|
1034
1053
|
exc,
|
1035
1054
|
message="Task run encountered an exception",
|
1036
|
-
|
1055
|
+
result_store=getattr(context, "result_store", None),
|
1037
1056
|
)
|
1038
1057
|
self.record_terminal_state_timing(state)
|
1039
1058
|
await self.set_state(state)
|
@@ -1083,7 +1102,9 @@ class AsyncTaskRunEngine(BaseTaskRunEngine[P, R]):
|
|
1083
1102
|
log_prints=log_prints,
|
1084
1103
|
task_run=self.task_run,
|
1085
1104
|
parameters=self.parameters,
|
1086
|
-
|
1105
|
+
result_store=await get_current_result_store().update_for_task(
|
1106
|
+
self.task, _sync=False
|
1107
|
+
),
|
1087
1108
|
client=client,
|
1088
1109
|
)
|
1089
1110
|
)
|
@@ -1199,17 +1220,21 @@ class AsyncTaskRunEngine(BaseTaskRunEngine[P, R]):
|
|
1199
1220
|
|
1200
1221
|
@asynccontextmanager
|
1201
1222
|
async def transaction_context(self) -> AsyncGenerator[Transaction, None]:
|
1202
|
-
result_factory = getattr(TaskRunContext.get(), "result_factory", None)
|
1203
|
-
|
1204
1223
|
# refresh cache setting is now repurposes as overwrite transaction record
|
1205
1224
|
overwrite = (
|
1206
1225
|
self.task.refresh_cache
|
1207
1226
|
if self.task.refresh_cache is not None
|
1208
1227
|
else PREFECT_TASKS_REFRESH_CACHE.value()
|
1209
1228
|
)
|
1229
|
+
result_store = getattr(TaskRunContext.get(), "result_store", None)
|
1230
|
+
if result_store and result_store.persist_result:
|
1231
|
+
store = ResultRecordStore(result_store=result_store)
|
1232
|
+
else:
|
1233
|
+
store = None
|
1234
|
+
|
1210
1235
|
with transaction(
|
1211
1236
|
key=self.compute_transaction_key(),
|
1212
|
-
store=
|
1237
|
+
store=store,
|
1213
1238
|
overwrite=overwrite,
|
1214
1239
|
logger=self.logger,
|
1215
1240
|
) as txn:
|
prefect/task_worker.py
CHANGED
@@ -25,7 +25,7 @@ from prefect.client.orchestration import get_client
|
|
25
25
|
from prefect.client.schemas.objects import TaskRun
|
26
26
|
from prefect.client.subscriptions import Subscription
|
27
27
|
from prefect.logging.loggers import get_logger
|
28
|
-
from prefect.results import
|
28
|
+
from prefect.results import ResultStore, get_or_create_default_task_scheduling_storage
|
29
29
|
from prefect.settings import (
|
30
30
|
PREFECT_API_URL,
|
31
31
|
PREFECT_TASK_SCHEDULING_DELETE_FAILED_SUBMISSIONS,
|
@@ -49,7 +49,7 @@ class StopTaskWorker(Exception):
|
|
49
49
|
|
50
50
|
|
51
51
|
def should_try_to_read_parameters(task: Task, task_run: TaskRun) -> bool:
|
52
|
-
"""Determines whether a task run should read parameters from the result
|
52
|
+
"""Determines whether a task run should read parameters from the result store."""
|
53
53
|
new_enough_state_details = hasattr(
|
54
54
|
task_run.state.state_details, "task_parameters_id"
|
55
55
|
)
|
@@ -273,9 +273,11 @@ class TaskWorker:
|
|
273
273
|
if should_try_to_read_parameters(task, task_run):
|
274
274
|
parameters_id = task_run.state.state_details.task_parameters_id
|
275
275
|
task.persist_result = True
|
276
|
-
|
276
|
+
store = await ResultStore(
|
277
|
+
result_storage=await get_or_create_default_task_scheduling_storage()
|
278
|
+
).update_for_task(task)
|
277
279
|
try:
|
278
|
-
run_data = await
|
280
|
+
run_data = await store.read_parameters(parameters_id)
|
279
281
|
parameters = run_data.get("parameters", {})
|
280
282
|
wait_for = run_data.get("wait_for", [])
|
281
283
|
run_context = run_data.get("context", None)
|
prefect/tasks.py
CHANGED
@@ -50,7 +50,12 @@ from prefect.context import (
|
|
50
50
|
)
|
51
51
|
from prefect.futures import PrefectDistributedFuture, PrefectFuture, PrefectFutureList
|
52
52
|
from prefect.logging.loggers import get_logger
|
53
|
-
from prefect.results import
|
53
|
+
from prefect.results import (
|
54
|
+
ResultSerializer,
|
55
|
+
ResultStorage,
|
56
|
+
ResultStore,
|
57
|
+
get_or_create_default_task_scheduling_storage,
|
58
|
+
)
|
54
59
|
from prefect.settings import (
|
55
60
|
PREFECT_TASK_DEFAULT_RETRIES,
|
56
61
|
PREFECT_TASK_DEFAULT_RETRY_DELAY_SECONDS,
|
@@ -201,8 +206,17 @@ def _generate_task_key(fn: Callable[..., Any]) -> str:
|
|
201
206
|
|
202
207
|
qualname = fn.__qualname__.split(".")[-1]
|
203
208
|
|
209
|
+
try:
|
210
|
+
code_obj = getattr(fn, "__code__", None)
|
211
|
+
if code_obj is None:
|
212
|
+
code_obj = fn.__call__.__code__
|
213
|
+
except AttributeError:
|
214
|
+
raise AttributeError(
|
215
|
+
f"{fn} is not a standard Python function object and could not be converted to a task."
|
216
|
+
) from None
|
217
|
+
|
204
218
|
code_hash = (
|
205
|
-
h[:NUM_CHARS_DYNAMIC_KEY] if (h := hash_objects(
|
219
|
+
h[:NUM_CHARS_DYNAMIC_KEY] if (h := hash_objects(code_obj)) else "unknown"
|
206
220
|
)
|
207
221
|
|
208
222
|
return f"{qualname}-{code_hash}"
|
@@ -752,14 +766,16 @@ class Task(Generic[P, R]):
|
|
752
766
|
# TODO: Improve use of result storage for parameter storage / reference
|
753
767
|
self.persist_result = True
|
754
768
|
|
755
|
-
|
769
|
+
store = await ResultStore(
|
770
|
+
result_storage=await get_or_create_default_task_scheduling_storage()
|
771
|
+
).update_for_task(self)
|
756
772
|
context = serialize_context()
|
757
773
|
data: Dict[str, Any] = {"context": context}
|
758
774
|
if parameters:
|
759
775
|
data["parameters"] = parameters
|
760
776
|
if wait_for:
|
761
777
|
data["wait_for"] = wait_for
|
762
|
-
await
|
778
|
+
await store.store_parameters(parameters_id, data)
|
763
779
|
|
764
780
|
# collect task inputs
|
765
781
|
task_inputs = {
|
@@ -853,14 +869,16 @@ class Task(Generic[P, R]):
|
|
853
869
|
# TODO: Improve use of result storage for parameter storage / reference
|
854
870
|
self.persist_result = True
|
855
871
|
|
856
|
-
|
872
|
+
store = await ResultStore(
|
873
|
+
result_storage=await get_or_create_default_task_scheduling_storage()
|
874
|
+
).update_for_task(task)
|
857
875
|
context = serialize_context()
|
858
876
|
data: Dict[str, Any] = {"context": context}
|
859
877
|
if parameters:
|
860
878
|
data["parameters"] = parameters
|
861
879
|
if wait_for:
|
862
880
|
data["wait_for"] = wait_for
|
863
|
-
await
|
881
|
+
await store.store_parameters(parameters_id, data)
|
864
882
|
|
865
883
|
# collect task inputs
|
866
884
|
task_inputs = {
|