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.
Files changed (49) hide show
  1. prefect/__init__.py +0 -3
  2. prefect/_internal/compatibility/migration.py +1 -1
  3. prefect/artifacts.py +1 -1
  4. prefect/blocks/core.py +8 -5
  5. prefect/blocks/notifications.py +10 -10
  6. prefect/blocks/system.py +52 -16
  7. prefect/blocks/webhook.py +3 -1
  8. prefect/client/cloud.py +57 -7
  9. prefect/client/collections.py +1 -1
  10. prefect/client/orchestration.py +68 -7
  11. prefect/client/schemas/objects.py +40 -2
  12. prefect/concurrency/asyncio.py +8 -2
  13. prefect/concurrency/services.py +16 -6
  14. prefect/concurrency/sync.py +4 -1
  15. prefect/context.py +7 -9
  16. prefect/deployments/runner.py +3 -3
  17. prefect/exceptions.py +12 -0
  18. prefect/filesystems.py +5 -3
  19. prefect/flow_engine.py +16 -10
  20. prefect/flows.py +2 -4
  21. prefect/futures.py +2 -1
  22. prefect/locking/__init__.py +0 -0
  23. prefect/locking/memory.py +213 -0
  24. prefect/locking/protocol.py +122 -0
  25. prefect/logging/handlers.py +4 -1
  26. prefect/main.py +8 -6
  27. prefect/records/filesystem.py +4 -2
  28. prefect/records/result_store.py +12 -6
  29. prefect/results.py +768 -363
  30. prefect/settings.py +24 -10
  31. prefect/states.py +82 -27
  32. prefect/task_engine.py +51 -26
  33. prefect/task_worker.py +6 -4
  34. prefect/tasks.py +24 -6
  35. prefect/transactions.py +57 -36
  36. prefect/utilities/annotations.py +4 -3
  37. prefect/utilities/asyncutils.py +1 -1
  38. prefect/utilities/callables.py +1 -3
  39. prefect/utilities/dispatch.py +16 -11
  40. prefect/utilities/schema_tools/hydration.py +13 -0
  41. prefect/variables.py +34 -24
  42. prefect/workers/base.py +78 -18
  43. prefect/workers/process.py +1 -3
  44. {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.1.dist-info}/METADATA +2 -2
  45. {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.1.dist-info}/RECORD +48 -46
  46. prefect/manifests.py +0 -21
  47. {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.1.dist-info}/LICENSE +0 -0
  48. {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.1.dist-info}/WHEEL +0 -0
  49. {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
- host = PREFECT_API_DATABASE_HOST.value_from(settings)
429
- port = PREFECT_API_DATABASE_PORT.value_from(settings) or 5432
430
- user = PREFECT_API_DATABASE_USER.value_from(settings)
431
- name = PREFECT_API_DATABASE_NAME.value_from(settings)
432
- password = PREFECT_API_DATABASE_PASSWORD.value_from(settings)
433
-
434
- return f"{driver}://{user}:{password}@{host}:{port}/{name}"
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 all profiles from the default and current profile paths.
2196
+ Load profiles from the current profile path. Optionally include profiles from the
2197
+ default profile path.
2190
2198
  """
2191
- profiles = _read_profiles_from(DEFAULT_PROFILES_PATH)
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, ResultFactory
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
- result_factory: Optional[ResultFactory] = None,
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 result_factory:
209
- data = await result_factory.create_result(exc)
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
- result_factory: Optional[ResultFactory] = None,
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 result_factory:
236
- data = await result_factory.create_result(exc)
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
- result_factory: ResultFactory,
273
+ result_store: ResultStore,
259
274
  key: Optional[str] = None,
260
275
  expiration: Optional[datetime.datetime] = None,
261
- defer_persistence: bool = False,
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 factory
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
- state.data = await result_factory.create_result(
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=await result_factory.create_result(
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
- return Completed(
358
- data=await result_factory.create_result(
359
- data,
360
- key=key,
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 ResultFactoryStore
58
- from prefect.results import BaseResult, ResultFactory, _format_user_supplied_storage_key
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
- result_factory = getattr(TaskRunContext.get(), "result_factory", None)
453
- if result_factory is None:
454
- raise ValueError("Result factory is not set")
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
- result_factory=result_factory,
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=[self.handle_rollback] + self.task.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
- result_factory=getattr(context, "result_factory", None),
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
- result_factory=run_coro_as_sync(ResultFactory.from_task(self.task)), # type: ignore
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=ResultFactoryStore(result_factory=result_factory),
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
- result_factory = getattr(TaskRunContext.get(), "result_factory", None)
954
- if result_factory is None:
955
- raise ValueError("Result factory is not set")
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
- result_factory=result_factory,
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=[self.handle_rollback] + self.task.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
- result_factory=getattr(context, "result_factory", None),
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
- result_factory=await ResultFactory.from_task(self.task), # type: ignore
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=ResultFactoryStore(result_factory=result_factory),
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 ResultFactory
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 factory."""
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
- factory = await ResultFactory.from_autonomous_task(task)
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 factory.read_parameters(parameters_id)
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 ResultFactory, ResultSerializer, ResultStorage
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(fn.__code__)) else "unknown"
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
- factory = await ResultFactory.from_autonomous_task(self, client=client)
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 factory.store_parameters(parameters_id, data)
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
- factory = await ResultFactory.from_autonomous_task(self, client=client)
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 factory.store_parameters(parameters_id, data)
881
+ await store.store_parameters(parameters_id, data)
864
882
 
865
883
  # collect task inputs
866
884
  task_inputs = {