prefect-client 2.20.2__py3-none-any.whl → 3.0.0__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 (288) hide show
  1. prefect/__init__.py +74 -110
  2. prefect/_internal/compatibility/deprecated.py +6 -115
  3. prefect/_internal/compatibility/experimental.py +4 -79
  4. prefect/_internal/compatibility/migration.py +166 -0
  5. prefect/_internal/concurrency/__init__.py +2 -2
  6. prefect/_internal/concurrency/api.py +1 -35
  7. prefect/_internal/concurrency/calls.py +0 -6
  8. prefect/_internal/concurrency/cancellation.py +0 -3
  9. prefect/_internal/concurrency/event_loop.py +0 -20
  10. prefect/_internal/concurrency/inspection.py +3 -3
  11. prefect/_internal/concurrency/primitives.py +1 -0
  12. prefect/_internal/concurrency/services.py +23 -0
  13. prefect/_internal/concurrency/threads.py +35 -0
  14. prefect/_internal/concurrency/waiters.py +0 -28
  15. prefect/_internal/integrations.py +7 -0
  16. prefect/_internal/pydantic/__init__.py +0 -45
  17. prefect/_internal/pydantic/annotations/pendulum.py +2 -2
  18. prefect/_internal/pydantic/v1_schema.py +21 -22
  19. prefect/_internal/pydantic/v2_schema.py +0 -2
  20. prefect/_internal/pydantic/v2_validated_func.py +18 -23
  21. prefect/_internal/pytz.py +1 -1
  22. prefect/_internal/retries.py +61 -0
  23. prefect/_internal/schemas/bases.py +45 -177
  24. prefect/_internal/schemas/fields.py +1 -43
  25. prefect/_internal/schemas/validators.py +47 -233
  26. prefect/agent.py +3 -695
  27. prefect/artifacts.py +173 -14
  28. prefect/automations.py +39 -4
  29. prefect/blocks/abstract.py +1 -1
  30. prefect/blocks/core.py +423 -164
  31. prefect/blocks/fields.py +2 -57
  32. prefect/blocks/notifications.py +43 -28
  33. prefect/blocks/redis.py +168 -0
  34. prefect/blocks/system.py +67 -20
  35. prefect/blocks/webhook.py +2 -9
  36. prefect/cache_policies.py +239 -0
  37. prefect/client/__init__.py +4 -0
  38. prefect/client/base.py +33 -27
  39. prefect/client/cloud.py +65 -20
  40. prefect/client/collections.py +1 -1
  41. prefect/client/orchestration.py +667 -440
  42. prefect/client/schemas/actions.py +115 -100
  43. prefect/client/schemas/filters.py +46 -52
  44. prefect/client/schemas/objects.py +228 -178
  45. prefect/client/schemas/responses.py +18 -36
  46. prefect/client/schemas/schedules.py +55 -36
  47. prefect/client/schemas/sorting.py +2 -0
  48. prefect/client/subscriptions.py +8 -7
  49. prefect/client/types/flexible_schedule_list.py +11 -0
  50. prefect/client/utilities.py +9 -6
  51. prefect/concurrency/asyncio.py +60 -11
  52. prefect/concurrency/context.py +24 -0
  53. prefect/concurrency/events.py +2 -2
  54. prefect/concurrency/services.py +46 -16
  55. prefect/concurrency/sync.py +51 -7
  56. prefect/concurrency/v1/asyncio.py +143 -0
  57. prefect/concurrency/v1/context.py +27 -0
  58. prefect/concurrency/v1/events.py +61 -0
  59. prefect/concurrency/v1/services.py +116 -0
  60. prefect/concurrency/v1/sync.py +92 -0
  61. prefect/context.py +246 -149
  62. prefect/deployments/__init__.py +33 -18
  63. prefect/deployments/base.py +10 -15
  64. prefect/deployments/deployments.py +2 -1048
  65. prefect/deployments/flow_runs.py +178 -0
  66. prefect/deployments/runner.py +72 -173
  67. prefect/deployments/schedules.py +31 -25
  68. prefect/deployments/steps/__init__.py +0 -1
  69. prefect/deployments/steps/core.py +7 -0
  70. prefect/deployments/steps/pull.py +15 -21
  71. prefect/deployments/steps/utility.py +2 -1
  72. prefect/docker/__init__.py +20 -0
  73. prefect/docker/docker_image.py +82 -0
  74. prefect/engine.py +15 -2466
  75. prefect/events/actions.py +17 -23
  76. prefect/events/cli/automations.py +20 -7
  77. prefect/events/clients.py +142 -80
  78. prefect/events/filters.py +14 -18
  79. prefect/events/related.py +74 -75
  80. prefect/events/schemas/__init__.py +0 -5
  81. prefect/events/schemas/automations.py +55 -46
  82. prefect/events/schemas/deployment_triggers.py +7 -197
  83. prefect/events/schemas/events.py +46 -65
  84. prefect/events/schemas/labelling.py +10 -14
  85. prefect/events/utilities.py +4 -5
  86. prefect/events/worker.py +23 -8
  87. prefect/exceptions.py +15 -0
  88. prefect/filesystems.py +30 -529
  89. prefect/flow_engine.py +827 -0
  90. prefect/flow_runs.py +379 -7
  91. prefect/flows.py +470 -360
  92. prefect/futures.py +382 -331
  93. prefect/infrastructure/__init__.py +5 -26
  94. prefect/infrastructure/base.py +3 -320
  95. prefect/infrastructure/provisioners/__init__.py +5 -3
  96. prefect/infrastructure/provisioners/cloud_run.py +13 -8
  97. prefect/infrastructure/provisioners/container_instance.py +14 -9
  98. prefect/infrastructure/provisioners/ecs.py +10 -8
  99. prefect/infrastructure/provisioners/modal.py +8 -5
  100. prefect/input/__init__.py +4 -0
  101. prefect/input/actions.py +2 -4
  102. prefect/input/run_input.py +9 -9
  103. prefect/logging/formatters.py +2 -4
  104. prefect/logging/handlers.py +9 -14
  105. prefect/logging/loggers.py +5 -5
  106. prefect/main.py +72 -0
  107. prefect/plugins.py +2 -64
  108. prefect/profiles.toml +16 -2
  109. prefect/records/__init__.py +1 -0
  110. prefect/records/base.py +223 -0
  111. prefect/records/filesystem.py +207 -0
  112. prefect/records/memory.py +178 -0
  113. prefect/records/result_store.py +64 -0
  114. prefect/results.py +577 -504
  115. prefect/runner/runner.py +124 -51
  116. prefect/runner/server.py +32 -34
  117. prefect/runner/storage.py +3 -12
  118. prefect/runner/submit.py +2 -10
  119. prefect/runner/utils.py +2 -2
  120. prefect/runtime/__init__.py +1 -0
  121. prefect/runtime/deployment.py +1 -0
  122. prefect/runtime/flow_run.py +40 -5
  123. prefect/runtime/task_run.py +1 -0
  124. prefect/serializers.py +28 -39
  125. prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
  126. prefect/settings.py +209 -332
  127. prefect/states.py +160 -63
  128. prefect/task_engine.py +1478 -57
  129. prefect/task_runners.py +383 -287
  130. prefect/task_runs.py +240 -0
  131. prefect/task_worker.py +463 -0
  132. prefect/tasks.py +684 -374
  133. prefect/transactions.py +410 -0
  134. prefect/types/__init__.py +72 -86
  135. prefect/types/entrypoint.py +13 -0
  136. prefect/utilities/annotations.py +4 -3
  137. prefect/utilities/asyncutils.py +227 -148
  138. prefect/utilities/callables.py +138 -48
  139. prefect/utilities/collections.py +134 -86
  140. prefect/utilities/dispatch.py +27 -14
  141. prefect/utilities/dockerutils.py +11 -4
  142. prefect/utilities/engine.py +186 -32
  143. prefect/utilities/filesystem.py +4 -5
  144. prefect/utilities/importtools.py +26 -27
  145. prefect/utilities/pydantic.py +128 -38
  146. prefect/utilities/schema_tools/hydration.py +18 -1
  147. prefect/utilities/schema_tools/validation.py +30 -0
  148. prefect/utilities/services.py +35 -9
  149. prefect/utilities/templating.py +12 -2
  150. prefect/utilities/timeout.py +20 -5
  151. prefect/utilities/urls.py +195 -0
  152. prefect/utilities/visualization.py +1 -0
  153. prefect/variables.py +78 -59
  154. prefect/workers/__init__.py +0 -1
  155. prefect/workers/base.py +237 -244
  156. prefect/workers/block.py +5 -226
  157. prefect/workers/cloud.py +6 -0
  158. prefect/workers/process.py +265 -12
  159. prefect/workers/server.py +29 -11
  160. {prefect_client-2.20.2.dist-info → prefect_client-3.0.0.dist-info}/METADATA +30 -26
  161. prefect_client-3.0.0.dist-info/RECORD +201 -0
  162. {prefect_client-2.20.2.dist-info → prefect_client-3.0.0.dist-info}/WHEEL +1 -1
  163. prefect/_internal/pydantic/_base_model.py +0 -51
  164. prefect/_internal/pydantic/_compat.py +0 -82
  165. prefect/_internal/pydantic/_flags.py +0 -20
  166. prefect/_internal/pydantic/_types.py +0 -8
  167. prefect/_internal/pydantic/utilities/config_dict.py +0 -72
  168. prefect/_internal/pydantic/utilities/field_validator.py +0 -150
  169. prefect/_internal/pydantic/utilities/model_construct.py +0 -56
  170. prefect/_internal/pydantic/utilities/model_copy.py +0 -55
  171. prefect/_internal/pydantic/utilities/model_dump.py +0 -136
  172. prefect/_internal/pydantic/utilities/model_dump_json.py +0 -112
  173. prefect/_internal/pydantic/utilities/model_fields.py +0 -50
  174. prefect/_internal/pydantic/utilities/model_fields_set.py +0 -29
  175. prefect/_internal/pydantic/utilities/model_json_schema.py +0 -82
  176. prefect/_internal/pydantic/utilities/model_rebuild.py +0 -80
  177. prefect/_internal/pydantic/utilities/model_validate.py +0 -75
  178. prefect/_internal/pydantic/utilities/model_validate_json.py +0 -68
  179. prefect/_internal/pydantic/utilities/model_validator.py +0 -87
  180. prefect/_internal/pydantic/utilities/type_adapter.py +0 -71
  181. prefect/_vendor/fastapi/__init__.py +0 -25
  182. prefect/_vendor/fastapi/applications.py +0 -946
  183. prefect/_vendor/fastapi/background.py +0 -3
  184. prefect/_vendor/fastapi/concurrency.py +0 -44
  185. prefect/_vendor/fastapi/datastructures.py +0 -58
  186. prefect/_vendor/fastapi/dependencies/__init__.py +0 -0
  187. prefect/_vendor/fastapi/dependencies/models.py +0 -64
  188. prefect/_vendor/fastapi/dependencies/utils.py +0 -877
  189. prefect/_vendor/fastapi/encoders.py +0 -177
  190. prefect/_vendor/fastapi/exception_handlers.py +0 -40
  191. prefect/_vendor/fastapi/exceptions.py +0 -46
  192. prefect/_vendor/fastapi/logger.py +0 -3
  193. prefect/_vendor/fastapi/middleware/__init__.py +0 -1
  194. prefect/_vendor/fastapi/middleware/asyncexitstack.py +0 -25
  195. prefect/_vendor/fastapi/middleware/cors.py +0 -3
  196. prefect/_vendor/fastapi/middleware/gzip.py +0 -3
  197. prefect/_vendor/fastapi/middleware/httpsredirect.py +0 -3
  198. prefect/_vendor/fastapi/middleware/trustedhost.py +0 -3
  199. prefect/_vendor/fastapi/middleware/wsgi.py +0 -3
  200. prefect/_vendor/fastapi/openapi/__init__.py +0 -0
  201. prefect/_vendor/fastapi/openapi/constants.py +0 -2
  202. prefect/_vendor/fastapi/openapi/docs.py +0 -203
  203. prefect/_vendor/fastapi/openapi/models.py +0 -480
  204. prefect/_vendor/fastapi/openapi/utils.py +0 -485
  205. prefect/_vendor/fastapi/param_functions.py +0 -340
  206. prefect/_vendor/fastapi/params.py +0 -453
  207. prefect/_vendor/fastapi/py.typed +0 -0
  208. prefect/_vendor/fastapi/requests.py +0 -4
  209. prefect/_vendor/fastapi/responses.py +0 -40
  210. prefect/_vendor/fastapi/routing.py +0 -1331
  211. prefect/_vendor/fastapi/security/__init__.py +0 -15
  212. prefect/_vendor/fastapi/security/api_key.py +0 -98
  213. prefect/_vendor/fastapi/security/base.py +0 -6
  214. prefect/_vendor/fastapi/security/http.py +0 -172
  215. prefect/_vendor/fastapi/security/oauth2.py +0 -227
  216. prefect/_vendor/fastapi/security/open_id_connect_url.py +0 -34
  217. prefect/_vendor/fastapi/security/utils.py +0 -10
  218. prefect/_vendor/fastapi/staticfiles.py +0 -1
  219. prefect/_vendor/fastapi/templating.py +0 -3
  220. prefect/_vendor/fastapi/testclient.py +0 -1
  221. prefect/_vendor/fastapi/types.py +0 -3
  222. prefect/_vendor/fastapi/utils.py +0 -235
  223. prefect/_vendor/fastapi/websockets.py +0 -7
  224. prefect/_vendor/starlette/__init__.py +0 -1
  225. prefect/_vendor/starlette/_compat.py +0 -28
  226. prefect/_vendor/starlette/_exception_handler.py +0 -80
  227. prefect/_vendor/starlette/_utils.py +0 -88
  228. prefect/_vendor/starlette/applications.py +0 -261
  229. prefect/_vendor/starlette/authentication.py +0 -159
  230. prefect/_vendor/starlette/background.py +0 -43
  231. prefect/_vendor/starlette/concurrency.py +0 -59
  232. prefect/_vendor/starlette/config.py +0 -151
  233. prefect/_vendor/starlette/convertors.py +0 -87
  234. prefect/_vendor/starlette/datastructures.py +0 -707
  235. prefect/_vendor/starlette/endpoints.py +0 -130
  236. prefect/_vendor/starlette/exceptions.py +0 -60
  237. prefect/_vendor/starlette/formparsers.py +0 -276
  238. prefect/_vendor/starlette/middleware/__init__.py +0 -17
  239. prefect/_vendor/starlette/middleware/authentication.py +0 -52
  240. prefect/_vendor/starlette/middleware/base.py +0 -220
  241. prefect/_vendor/starlette/middleware/cors.py +0 -176
  242. prefect/_vendor/starlette/middleware/errors.py +0 -265
  243. prefect/_vendor/starlette/middleware/exceptions.py +0 -74
  244. prefect/_vendor/starlette/middleware/gzip.py +0 -113
  245. prefect/_vendor/starlette/middleware/httpsredirect.py +0 -19
  246. prefect/_vendor/starlette/middleware/sessions.py +0 -82
  247. prefect/_vendor/starlette/middleware/trustedhost.py +0 -64
  248. prefect/_vendor/starlette/middleware/wsgi.py +0 -147
  249. prefect/_vendor/starlette/py.typed +0 -0
  250. prefect/_vendor/starlette/requests.py +0 -328
  251. prefect/_vendor/starlette/responses.py +0 -347
  252. prefect/_vendor/starlette/routing.py +0 -933
  253. prefect/_vendor/starlette/schemas.py +0 -154
  254. prefect/_vendor/starlette/staticfiles.py +0 -248
  255. prefect/_vendor/starlette/status.py +0 -199
  256. prefect/_vendor/starlette/templating.py +0 -231
  257. prefect/_vendor/starlette/testclient.py +0 -804
  258. prefect/_vendor/starlette/types.py +0 -30
  259. prefect/_vendor/starlette/websockets.py +0 -193
  260. prefect/blocks/kubernetes.py +0 -119
  261. prefect/deprecated/__init__.py +0 -0
  262. prefect/deprecated/data_documents.py +0 -350
  263. prefect/deprecated/packaging/__init__.py +0 -12
  264. prefect/deprecated/packaging/base.py +0 -96
  265. prefect/deprecated/packaging/docker.py +0 -146
  266. prefect/deprecated/packaging/file.py +0 -92
  267. prefect/deprecated/packaging/orion.py +0 -80
  268. prefect/deprecated/packaging/serializers.py +0 -171
  269. prefect/events/instrument.py +0 -135
  270. prefect/infrastructure/container.py +0 -824
  271. prefect/infrastructure/kubernetes.py +0 -920
  272. prefect/infrastructure/process.py +0 -289
  273. prefect/manifests.py +0 -20
  274. prefect/new_flow_engine.py +0 -449
  275. prefect/new_task_engine.py +0 -423
  276. prefect/pydantic/__init__.py +0 -76
  277. prefect/pydantic/main.py +0 -39
  278. prefect/software/__init__.py +0 -2
  279. prefect/software/base.py +0 -50
  280. prefect/software/conda.py +0 -199
  281. prefect/software/pip.py +0 -122
  282. prefect/software/python.py +0 -52
  283. prefect/task_server.py +0 -322
  284. prefect_client-2.20.2.dist-info/RECORD +0 -294
  285. /prefect/{_internal/pydantic/utilities → client/types}/__init__.py +0 -0
  286. /prefect/{_vendor → concurrency/v1}/__init__.py +0 -0
  287. {prefect_client-2.20.2.dist-info → prefect_client-3.0.0.dist-info}/LICENSE +0 -0
  288. {prefect_client-2.20.2.dist-info → prefect_client-3.0.0.dist-info}/top_level.txt +0 -0
prefect/tasks.py CHANGED
@@ -21,61 +21,72 @@ from typing import (
21
21
  NoReturn,
22
22
  Optional,
23
23
  Set,
24
+ Tuple,
25
+ Type,
24
26
  TypeVar,
25
27
  Union,
26
28
  cast,
27
29
  overload,
28
30
  )
29
- from uuid import uuid4
31
+ from uuid import UUID, uuid4
30
32
 
31
33
  from typing_extensions import Literal, ParamSpec
32
34
 
33
- from prefect._internal.concurrency.api import create_call, from_async, from_sync
34
- from prefect.client.orchestration import PrefectClient, SyncPrefectClient
35
+ import prefect.states
36
+ from prefect.cache_policies import DEFAULT, NONE, CachePolicy
37
+ from prefect.client.orchestration import get_client
35
38
  from prefect.client.schemas import TaskRun
36
- from prefect.client.schemas.objects import TaskRunInput, TaskRunResult
39
+ from prefect.client.schemas.objects import (
40
+ StateDetails,
41
+ TaskRunInput,
42
+ TaskRunPolicy,
43
+ TaskRunResult,
44
+ )
37
45
  from prefect.context import (
38
46
  FlowRunContext,
39
- PrefectObjectRegistry,
40
47
  TagsContext,
41
48
  TaskRunContext,
49
+ serialize_context,
50
+ )
51
+ from prefect.futures import PrefectDistributedFuture, PrefectFuture, PrefectFutureList
52
+ from prefect.logging.loggers import get_logger
53
+ from prefect.results import (
54
+ ResultSerializer,
55
+ ResultStorage,
56
+ ResultStore,
57
+ get_or_create_default_task_scheduling_storage,
42
58
  )
43
- from prefect.futures import PrefectFuture
44
- from prefect.logging.loggers import get_logger, get_run_logger
45
- from prefect.results import ResultSerializer, ResultStorage
46
59
  from prefect.settings import (
47
- PREFECT_EXPERIMENTAL_ENABLE_NEW_ENGINE,
48
- PREFECT_EXPERIMENTAL_ENABLE_TASK_SCHEDULING,
49
60
  PREFECT_TASK_DEFAULT_RETRIES,
50
61
  PREFECT_TASK_DEFAULT_RETRY_DELAY_SECONDS,
51
62
  )
52
- from prefect.states import Pending, State
53
- from prefect.task_runners import BaseTaskRunner
63
+ from prefect.states import Pending, Scheduled, State
54
64
  from prefect.utilities.annotations import NotSet
55
- from prefect.utilities.asyncutils import Async, Sync
65
+ from prefect.utilities.asyncutils import (
66
+ run_coro_as_sync,
67
+ sync_compatible,
68
+ )
56
69
  from prefect.utilities.callables import (
70
+ expand_mapping_parameters,
57
71
  get_call_parameters,
58
72
  raise_for_reserved_arguments,
59
73
  )
60
74
  from prefect.utilities.hashing import hash_objects
61
75
  from prefect.utilities.importtools import to_qualified_name
62
- from prefect.utilities.visualization import (
63
- VisualizationUnsupportedError,
64
- get_task_viz_tracker,
65
- track_viz_task,
66
- )
76
+ from prefect.utilities.urls import url_for
67
77
 
68
78
  if TYPE_CHECKING:
79
+ from prefect.client.orchestration import PrefectClient
69
80
  from prefect.context import TaskRunContext
70
-
81
+ from prefect.transactions import Transaction
71
82
 
72
83
  T = TypeVar("T") # Generic type var for capturing the inner return type of async funcs
73
84
  R = TypeVar("R") # The return type of the user's function
74
85
  P = ParamSpec("P") # The parameters of the task
75
86
 
76
- logger = get_logger("tasks")
87
+ NUM_CHARS_DYNAMIC_KEY = 8
77
88
 
78
- NUM_CHARS_DYNAMIC_KEY: int = 8
89
+ logger = get_logger("tasks")
79
90
 
80
91
 
81
92
  def task_input_hash(
@@ -126,13 +137,67 @@ def exponential_backoff(backoff_factor: float) -> Callable[[int], List[float]]:
126
137
  return retry_backoff_callable
127
138
 
128
139
 
140
+ def _infer_parent_task_runs(
141
+ flow_run_context: Optional[FlowRunContext],
142
+ task_run_context: Optional[TaskRunContext],
143
+ parameters: Dict[str, Any],
144
+ ):
145
+ """
146
+ Attempt to infer the parent task runs for this task run based on the
147
+ provided flow run and task run contexts, as well as any parameters. It is
148
+ assumed that the task run is running within those contexts.
149
+ If any parameter comes from a running task run, that task run is considered
150
+ a parent. This is expected to happen when task inputs are yielded from
151
+ generator tasks.
152
+ """
153
+ parents = []
154
+
155
+ # check if this task has a parent task run based on running in another
156
+ # task run's existing context. A task run is only considered a parent if
157
+ # it is in the same flow run (because otherwise presumably the child is
158
+ # in a subflow, so the subflow serves as the parent) or if there is no
159
+ # flow run
160
+ if task_run_context:
161
+ # there is no flow run
162
+ if not flow_run_context:
163
+ parents.append(TaskRunResult(id=task_run_context.task_run.id))
164
+ # there is a flow run and the task run is in the same flow run
165
+ elif flow_run_context and task_run_context.task_run.flow_run_id == getattr(
166
+ flow_run_context.flow_run, "id", None
167
+ ):
168
+ parents.append(TaskRunResult(id=task_run_context.task_run.id))
169
+
170
+ # parent dependency tracking: for every provided parameter value, try to
171
+ # load the corresponding task run state. If the task run state is still
172
+ # running, we consider it a parent task run. Note this is only done if
173
+ # there is an active flow run context because dependencies are only
174
+ # tracked within the same flow run.
175
+ if flow_run_context:
176
+ for v in parameters.values():
177
+ if isinstance(v, State):
178
+ upstream_state = v
179
+ elif isinstance(v, PrefectFuture):
180
+ upstream_state = v.state
181
+ else:
182
+ upstream_state = flow_run_context.task_run_results.get(id(v))
183
+ if upstream_state and upstream_state.is_running():
184
+ parents.append(
185
+ TaskRunResult(id=upstream_state.state_details.task_run_id)
186
+ )
187
+
188
+ return parents
189
+
190
+
129
191
  def _generate_task_key(fn: Callable[..., Any]) -> str:
130
192
  """Generate a task key based on the function name and source code.
193
+
131
194
  We may eventually want some sort of top-level namespace here to
132
195
  disambiguate tasks with the same function name in different modules,
133
196
  in a more human-readable way, while avoiding relative import problems (see #12337).
197
+
134
198
  As long as the task implementations are unique (even if named the same), we should
135
199
  not have any collisions.
200
+
136
201
  Args:
137
202
  fn: The function to generate a task key for.
138
203
  """
@@ -148,7 +213,6 @@ def _generate_task_key(fn: Callable[..., Any]) -> str:
148
213
  return f"{qualname}-{code_hash}"
149
214
 
150
215
 
151
- @PrefectObjectRegistry.register_instances
152
216
  class Task(Generic[P, R]):
153
217
  """
154
218
  A Prefect task definition.
@@ -171,6 +235,7 @@ class Task(Generic[P, R]):
171
235
  tags are combined with any tags defined by a `prefect.tags` context at
172
236
  task runtime.
173
237
  version: An optional string specifying the version of this task definition
238
+ cache_policy: A cache policy that determines the level of caching for this task
174
239
  cache_key_fn: An optional callable that, given the task run context and call
175
240
  parameters, generates a string key; if the key matches a previous completed
176
241
  state, that state result will be restored instead of running the task again.
@@ -191,10 +256,10 @@ class Task(Generic[P, R]):
191
256
  cannot exceed 50.
192
257
  retry_jitter_factor: An optional factor that defines the factor to which a retry
193
258
  can be jittered in order to avoid a "thundering herd".
194
- persist_result: An optional toggle indicating whether the result of this task
195
- should be persisted to result storage. Defaults to `None`, which indicates
196
- that Prefect should choose whether the result should be persisted depending on
197
- the features being used.
259
+ persist_result: A toggle indicating whether the result of this task
260
+ should be persisted to result storage. Defaults to `None`, which
261
+ indicates that the global default should be used (which is `True` by
262
+ default).
198
263
  result_storage: An optional block to use to persist the result of this task.
199
264
  Defaults to the value set in the flow the task is called in.
200
265
  result_storage_key: An optional key to store the result in storage at when persisted.
@@ -212,6 +277,8 @@ class Task(Generic[P, R]):
212
277
  execution with matching cache key is used.
213
278
  on_failure: An optional list of callables to run when the task enters a failed state.
214
279
  on_completion: An optional list of callables to run when the task enters a completed state.
280
+ on_commit: An optional list of callables to run when the task's idempotency record is committed.
281
+ on_rollback: An optional list of callables to run when the task rolls back.
215
282
  retry_condition_fn: An optional callable run when a task run returns a Failed state. Should
216
283
  return `True` if the task should continue to its retry policy (e.g. `retries=3`), and `False` if the task
217
284
  should end as failed. Defaults to `None`, indicating the task should always continue
@@ -228,6 +295,7 @@ class Task(Generic[P, R]):
228
295
  description: Optional[str] = None,
229
296
  tags: Optional[Iterable[str]] = None,
230
297
  version: Optional[str] = None,
298
+ cache_policy: Union[CachePolicy, Type[NotSet]] = NotSet,
231
299
  cache_key_fn: Optional[
232
300
  Callable[["TaskRunContext", Dict[str, Any]], Optional[str]]
233
301
  ] = None,
@@ -253,6 +321,8 @@ class Task(Generic[P, R]):
253
321
  refresh_cache: Optional[bool] = None,
254
322
  on_completion: Optional[List[Callable[["Task", TaskRun, State], None]]] = None,
255
323
  on_failure: Optional[List[Callable[["Task", TaskRun, State], None]]] = None,
324
+ on_rollback: Optional[List[Callable[["Transaction"], None]]] = None,
325
+ on_commit: Optional[List[Callable[["Transaction"], None]]] = None,
256
326
  retry_condition_fn: Optional[Callable[["Task", TaskRun, State], bool]] = None,
257
327
  viz_return_value: Optional[Any] = None,
258
328
  ):
@@ -261,8 +331,6 @@ class Task(Generic[P, R]):
261
331
  hook_names = ["on_completion", "on_failure"]
262
332
  for hooks, hook_name in zip(hook_categories, hook_names):
263
333
  if hooks is not None:
264
- if not hooks:
265
- raise ValueError(f"Empty list passed for '{hook_name}'")
266
334
  try:
267
335
  hooks = list(hooks)
268
336
  except TypeError:
@@ -270,8 +338,8 @@ class Task(Generic[P, R]):
270
338
  f"Expected iterable for '{hook_name}'; got"
271
339
  f" {type(hooks).__name__} instead. Please provide a list of"
272
340
  f" hooks to '{hook_name}':\n\n"
273
- f"@flow({hook_name}=[hook1, hook2])\ndef"
274
- " my_flow():\n\tpass"
341
+ f"@task({hook_name}=[hook1, hook2])\ndef"
342
+ " my_task():\n\tpass"
275
343
  )
276
344
 
277
345
  for hook in hooks:
@@ -280,8 +348,8 @@ class Task(Generic[P, R]):
280
348
  f"Expected callables in '{hook_name}'; got"
281
349
  f" {type(hook).__name__} instead. Please provide a list of"
282
350
  f" hooks to '{hook_name}':\n\n"
283
- f"@flow({hook_name}=[hook1, hook2])\ndef"
284
- " my_flow():\n\tpass"
351
+ f"@task({hook_name}=[hook1, hook2])\ndef"
352
+ " my_task():\n\tpass"
285
353
  )
286
354
 
287
355
  if not callable(fn):
@@ -290,7 +358,18 @@ class Task(Generic[P, R]):
290
358
  self.description = description or inspect.getdoc(fn)
291
359
  update_wrapper(self, fn)
292
360
  self.fn = fn
293
- self.isasync = inspect.iscoroutinefunction(self.fn)
361
+
362
+ # the task is considered async if its function is async or an async
363
+ # generator
364
+ self.isasync = inspect.iscoroutinefunction(
365
+ self.fn
366
+ ) or inspect.isasyncgenfunction(self.fn)
367
+
368
+ # the task is considered a generator if its function is a generator or
369
+ # an async generator
370
+ self.isgenerator = inspect.isgeneratorfunction(
371
+ self.fn
372
+ ) or inspect.isasyncgenfunction(self.fn)
294
373
 
295
374
  if not name:
296
375
  if not hasattr(self.fn, "__name__"):
@@ -317,10 +396,46 @@ class Task(Generic[P, R]):
317
396
 
318
397
  self.task_key = _generate_task_key(self.fn)
319
398
 
399
+ if cache_policy is not NotSet and cache_key_fn is not None:
400
+ logger.warning(
401
+ f"Both `cache_policy` and `cache_key_fn` are set on task {self}. `cache_key_fn` will be used."
402
+ )
403
+
404
+ if cache_key_fn:
405
+ cache_policy = CachePolicy.from_cache_key_fn(cache_key_fn)
406
+
407
+ # TODO: manage expiration and cache refresh
320
408
  self.cache_key_fn = cache_key_fn
321
409
  self.cache_expiration = cache_expiration
322
410
  self.refresh_cache = refresh_cache
323
411
 
412
+ # result persistence settings
413
+ if persist_result is None:
414
+ if any(
415
+ [
416
+ cache_policy and cache_policy != NONE and cache_policy != NotSet,
417
+ cache_key_fn is not None,
418
+ result_storage_key is not None,
419
+ result_storage is not None,
420
+ result_serializer is not None,
421
+ ]
422
+ ):
423
+ persist_result = True
424
+
425
+ if persist_result is False:
426
+ self.cache_policy = None if cache_policy is None else NONE
427
+ if cache_policy and cache_policy is not NotSet and cache_policy != NONE:
428
+ logger.warning(
429
+ "Ignoring `cache_policy` because `persist_result` is False"
430
+ )
431
+ elif cache_policy is NotSet and result_storage_key is None:
432
+ self.cache_policy = DEFAULT
433
+ elif result_storage_key:
434
+ # TODO: handle this situation with double storage
435
+ self.cache_policy = None
436
+ else:
437
+ self.cache_policy = cache_policy
438
+
324
439
  # TaskRunPolicy settings
325
440
  # TODO: We can instantiate a `TaskRunPolicy` and add Pydantic bound checks to
326
441
  # validate that the user passes positive numbers here
@@ -346,13 +461,23 @@ class Task(Generic[P, R]):
346
461
 
347
462
  self.retry_jitter_factor = retry_jitter_factor
348
463
  self.persist_result = persist_result
464
+
465
+ if result_storage and not isinstance(result_storage, str):
466
+ if getattr(result_storage, "_block_document_id", None) is None:
467
+ raise TypeError(
468
+ "Result storage configuration must be persisted server-side."
469
+ " Please call `.save()` on your block before passing it in."
470
+ )
471
+
349
472
  self.result_storage = result_storage
350
473
  self.result_serializer = result_serializer
351
474
  self.result_storage_key = result_storage_key
352
475
  self.cache_result_in_memory = cache_result_in_memory
353
476
  self.timeout_seconds = float(timeout_seconds) if timeout_seconds else None
354
- self.on_completion = on_completion
355
- self.on_failure = on_failure
477
+ self.on_rollback_hooks = on_rollback or []
478
+ self.on_commit_hooks = on_commit or []
479
+ self.on_completion_hooks = on_completion or []
480
+ self.on_failure_hooks = on_failure or []
356
481
 
357
482
  # retry_condition_fn must be a callable or None. If it is neither, raise a TypeError
358
483
  if retry_condition_fn is not None and not (callable(retry_condition_fn)):
@@ -364,33 +489,57 @@ class Task(Generic[P, R]):
364
489
  self.retry_condition_fn = retry_condition_fn
365
490
  self.viz_return_value = viz_return_value
366
491
 
492
+ @property
493
+ def ismethod(self) -> bool:
494
+ return hasattr(self.fn, "__prefect_self__")
495
+
496
+ def __get__(self, instance, owner):
497
+ """
498
+ Implement the descriptor protocol so that the task can be used as an instance method.
499
+ When an instance method is loaded, this method is called with the "self" instance as
500
+ an argument. We return a copy of the task with that instance bound to the task's function.
501
+ """
502
+
503
+ # if no instance is provided, it's being accessed on the class
504
+ if instance is None:
505
+ return self
506
+
507
+ # if the task is being accessed on an instance, bind the instance to the __prefect_self__ attribute
508
+ # of the task's function. This will allow it to be automatically added to the task's parameters
509
+ else:
510
+ bound_task = copy(self)
511
+ bound_task.fn.__prefect_self__ = instance
512
+ return bound_task
513
+
367
514
  def with_options(
368
515
  self,
369
516
  *,
370
- name: str = None,
371
- description: str = None,
372
- tags: Iterable[str] = None,
373
- cache_key_fn: Callable[
374
- ["TaskRunContext", Dict[str, Any]], Optional[str]
517
+ name: Optional[str] = None,
518
+ description: Optional[str] = None,
519
+ tags: Optional[Iterable[str]] = None,
520
+ cache_policy: Union[CachePolicy, Type[NotSet]] = NotSet,
521
+ cache_key_fn: Optional[
522
+ Callable[["TaskRunContext", Dict[str, Any]], Optional[str]]
375
523
  ] = None,
376
- task_run_name: Optional[Union[Callable[[], str], str]] = None,
377
- cache_expiration: datetime.timedelta = None,
378
- retries: Optional[int] = NotSet,
524
+ task_run_name: Optional[Union[Callable[[], str], str, Type[NotSet]]] = NotSet,
525
+ cache_expiration: Optional[datetime.timedelta] = None,
526
+ retries: Union[int, Type[NotSet]] = NotSet,
379
527
  retry_delay_seconds: Union[
380
528
  float,
381
529
  int,
382
530
  List[float],
383
531
  Callable[[int], List[float]],
532
+ Type[NotSet],
384
533
  ] = NotSet,
385
- retry_jitter_factor: Optional[float] = NotSet,
386
- persist_result: Optional[bool] = NotSet,
387
- result_storage: Optional[ResultStorage] = NotSet,
388
- result_serializer: Optional[ResultSerializer] = NotSet,
389
- result_storage_key: Optional[str] = NotSet,
534
+ retry_jitter_factor: Union[float, Type[NotSet]] = NotSet,
535
+ persist_result: Union[bool, Type[NotSet]] = NotSet,
536
+ result_storage: Union[ResultStorage, Type[NotSet]] = NotSet,
537
+ result_serializer: Union[ResultSerializer, Type[NotSet]] = NotSet,
538
+ result_storage_key: Union[str, Type[NotSet]] = NotSet,
390
539
  cache_result_in_memory: Optional[bool] = None,
391
- timeout_seconds: Union[int, float] = None,
392
- log_prints: Optional[bool] = NotSet,
393
- refresh_cache: Optional[bool] = NotSet,
540
+ timeout_seconds: Union[int, float, None] = None,
541
+ log_prints: Union[bool, Type[NotSet]] = NotSet,
542
+ refresh_cache: Union[bool, Type[NotSet]] = NotSet,
394
543
  on_completion: Optional[
395
544
  List[Callable[["Task", TaskRun, State], Union[Awaitable[None], None]]]
396
545
  ] = None,
@@ -481,9 +630,14 @@ class Task(Generic[P, R]):
481
630
  name=name or self.name,
482
631
  description=description or self.description,
483
632
  tags=tags or copy(self.tags),
633
+ cache_policy=cache_policy
634
+ if cache_policy is not NotSet
635
+ else self.cache_policy,
484
636
  cache_key_fn=cache_key_fn or self.cache_key_fn,
485
637
  cache_expiration=cache_expiration or self.cache_expiration,
486
- task_run_name=task_run_name,
638
+ task_run_name=task_run_name
639
+ if task_run_name is not NotSet
640
+ else self.task_run_name,
487
641
  retries=retries if retries is not NotSet else self.retries,
488
642
  retry_delay_seconds=(
489
643
  retry_delay_seconds
@@ -523,25 +677,50 @@ class Task(Generic[P, R]):
523
677
  refresh_cache=(
524
678
  refresh_cache if refresh_cache is not NotSet else self.refresh_cache
525
679
  ),
526
- on_completion=on_completion or self.on_completion,
527
- on_failure=on_failure or self.on_failure,
680
+ on_completion=on_completion or self.on_completion_hooks,
681
+ on_failure=on_failure or self.on_failure_hooks,
528
682
  retry_condition_fn=retry_condition_fn or self.retry_condition_fn,
529
683
  viz_return_value=viz_return_value or self.viz_return_value,
530
684
  )
531
685
 
686
+ def on_completion(
687
+ self, fn: Callable[["Task", TaskRun, State], None]
688
+ ) -> Callable[["Task", TaskRun, State], None]:
689
+ self.on_completion_hooks.append(fn)
690
+ return fn
691
+
692
+ def on_failure(
693
+ self, fn: Callable[["Task", TaskRun, State], None]
694
+ ) -> Callable[["Task", TaskRun, State], None]:
695
+ self.on_failure_hooks.append(fn)
696
+ return fn
697
+
698
+ def on_commit(
699
+ self, fn: Callable[["Transaction"], None]
700
+ ) -> Callable[["Transaction"], None]:
701
+ self.on_commit_hooks.append(fn)
702
+ return fn
703
+
704
+ def on_rollback(
705
+ self, fn: Callable[["Transaction"], None]
706
+ ) -> Callable[["Transaction"], None]:
707
+ self.on_rollback_hooks.append(fn)
708
+ return fn
709
+
532
710
  async def create_run(
533
711
  self,
534
- client: Optional[Union[PrefectClient, SyncPrefectClient]],
535
- parameters: Dict[str, Any] = None,
712
+ client: Optional["PrefectClient"] = None,
713
+ id: Optional[UUID] = None,
714
+ parameters: Optional[Dict[str, Any]] = None,
536
715
  flow_run_context: Optional[FlowRunContext] = None,
537
716
  parent_task_run_context: Optional[TaskRunContext] = None,
538
717
  wait_for: Optional[Iterable[PrefectFuture]] = None,
539
718
  extra_task_inputs: Optional[Dict[str, Set[TaskRunInput]]] = None,
719
+ deferred: bool = False,
540
720
  ) -> TaskRun:
541
721
  from prefect.utilities.engine import (
542
722
  _dynamic_key_for_task_run,
543
- _resolve_custom_task_run_name,
544
- collect_task_run_inputs,
723
+ collect_task_run_inputs_sync,
545
724
  )
546
725
 
547
726
  if flow_run_context is None:
@@ -550,76 +729,205 @@ class Task(Generic[P, R]):
550
729
  parent_task_run_context = TaskRunContext.get()
551
730
  if parameters is None:
552
731
  parameters = {}
732
+ if client is None:
733
+ client = get_client()
553
734
 
554
- try:
555
- task_run_name = _resolve_custom_task_run_name(self, parameters)
556
- except TypeError:
557
- task_run_name = None
735
+ async with client:
736
+ if not flow_run_context:
737
+ dynamic_key = f"{self.task_key}-{str(uuid4().hex)}"
738
+ task_run_name = self.name
739
+ else:
740
+ dynamic_key = _dynamic_key_for_task_run(
741
+ context=flow_run_context, task=self
742
+ )
743
+ task_run_name = f"{self.name}-{dynamic_key}"
558
744
 
559
- if flow_run_context:
560
- dynamic_key = _dynamic_key_for_task_run(context=flow_run_context, task=self)
561
- else:
562
- dynamic_key = uuid4().hex
563
-
564
- # collect task inputs
565
- task_inputs = {
566
- k: await collect_task_run_inputs(v) for k, v in parameters.items()
567
- }
568
-
569
- # check if this task has a parent task run based on running in another
570
- # task run's existing context. A task run is only considered a parent if
571
- # it is in the same flow run (because otherwise presumably the child is
572
- # in a subflow, so the subflow serves as the parent) or if there is no
573
- # flow run
574
- if parent_task_run_context:
575
- # there is no flow run
745
+ if deferred:
746
+ state = Scheduled()
747
+ state.state_details.deferred = True
748
+ else:
749
+ state = Pending()
750
+
751
+ # store parameters for background tasks so that task worker
752
+ # can retrieve them at runtime
753
+ if deferred and (parameters or wait_for):
754
+ parameters_id = uuid4()
755
+ state.state_details.task_parameters_id = parameters_id
756
+
757
+ # TODO: Improve use of result storage for parameter storage / reference
758
+ self.persist_result = True
759
+
760
+ store = await ResultStore(
761
+ result_storage=await get_or_create_default_task_scheduling_storage()
762
+ ).update_for_task(self)
763
+ context = serialize_context()
764
+ data: Dict[str, Any] = {"context": context}
765
+ if parameters:
766
+ data["parameters"] = parameters
767
+ if wait_for:
768
+ data["wait_for"] = wait_for
769
+ await store.store_parameters(parameters_id, data)
770
+
771
+ # collect task inputs
772
+ task_inputs = {
773
+ k: collect_task_run_inputs_sync(v) for k, v in parameters.items()
774
+ }
775
+
776
+ # collect all parent dependencies
777
+ if task_parents := _infer_parent_task_runs(
778
+ flow_run_context=flow_run_context,
779
+ task_run_context=parent_task_run_context,
780
+ parameters=parameters,
781
+ ):
782
+ task_inputs["__parents__"] = task_parents
783
+
784
+ # check wait for dependencies
785
+ if wait_for:
786
+ task_inputs["wait_for"] = collect_task_run_inputs_sync(wait_for)
787
+
788
+ # Join extra task inputs
789
+ for k, extras in (extra_task_inputs or {}).items():
790
+ task_inputs[k] = task_inputs[k].union(extras)
791
+
792
+ # create the task run
793
+ task_run = client.create_task_run(
794
+ task=self,
795
+ name=task_run_name,
796
+ flow_run_id=(
797
+ getattr(flow_run_context.flow_run, "id", None)
798
+ if flow_run_context and flow_run_context.flow_run
799
+ else None
800
+ ),
801
+ dynamic_key=str(dynamic_key),
802
+ id=id,
803
+ state=state,
804
+ task_inputs=task_inputs,
805
+ extra_tags=TagsContext.get().current_tags,
806
+ )
807
+ # the new engine uses sync clients but old engines use async clients
808
+ if inspect.isawaitable(task_run):
809
+ task_run = await task_run
810
+
811
+ return task_run
812
+
813
+ async def create_local_run(
814
+ self,
815
+ client: Optional["PrefectClient"] = None,
816
+ id: Optional[UUID] = None,
817
+ parameters: Optional[Dict[str, Any]] = None,
818
+ flow_run_context: Optional[FlowRunContext] = None,
819
+ parent_task_run_context: Optional[TaskRunContext] = None,
820
+ wait_for: Optional[Iterable[PrefectFuture]] = None,
821
+ extra_task_inputs: Optional[Dict[str, Set[TaskRunInput]]] = None,
822
+ deferred: bool = False,
823
+ ) -> TaskRun:
824
+ from prefect.utilities.engine import (
825
+ _dynamic_key_for_task_run,
826
+ collect_task_run_inputs_sync,
827
+ )
828
+
829
+ if flow_run_context is None:
830
+ flow_run_context = FlowRunContext.get()
831
+ if parent_task_run_context is None:
832
+ parent_task_run_context = TaskRunContext.get()
833
+ if parameters is None:
834
+ parameters = {}
835
+ if client is None:
836
+ client = get_client()
837
+
838
+ async with client:
576
839
  if not flow_run_context:
577
- task_inputs["__parents__"] = [
578
- TaskRunResult(id=parent_task_run_context.task_run.id)
579
- ]
580
- # there is a flow run and the task run is in the same flow run
581
- elif (
582
- flow_run_context
583
- and parent_task_run_context.task_run.flow_run_id
584
- == flow_run_context.flow_run.id
840
+ dynamic_key = f"{self.task_key}-{str(uuid4().hex)}"
841
+ task_run_name = self.name
842
+ else:
843
+ dynamic_key = _dynamic_key_for_task_run(
844
+ context=flow_run_context, task=self, stable=False
845
+ )
846
+ task_run_name = f"{self.name}-{dynamic_key[:3]}"
847
+
848
+ if deferred:
849
+ state = Scheduled()
850
+ state.state_details.deferred = True
851
+ else:
852
+ state = Pending()
853
+
854
+ # store parameters for background tasks so that task worker
855
+ # can retrieve them at runtime
856
+ if deferred and (parameters or wait_for):
857
+ parameters_id = uuid4()
858
+ state.state_details.task_parameters_id = parameters_id
859
+
860
+ # TODO: Improve use of result storage for parameter storage / reference
861
+ self.persist_result = True
862
+
863
+ store = await ResultStore(
864
+ result_storage=await get_or_create_default_task_scheduling_storage()
865
+ ).update_for_task(task)
866
+ context = serialize_context()
867
+ data: Dict[str, Any] = {"context": context}
868
+ if parameters:
869
+ data["parameters"] = parameters
870
+ if wait_for:
871
+ data["wait_for"] = wait_for
872
+ await store.store_parameters(parameters_id, data)
873
+
874
+ # collect task inputs
875
+ task_inputs = {
876
+ k: collect_task_run_inputs_sync(v) for k, v in parameters.items()
877
+ }
878
+
879
+ # collect all parent dependencies
880
+ if task_parents := _infer_parent_task_runs(
881
+ flow_run_context=flow_run_context,
882
+ task_run_context=parent_task_run_context,
883
+ parameters=parameters,
585
884
  ):
586
- task_inputs["__parents__"] = [
587
- TaskRunResult(id=parent_task_run_context.task_run.id)
588
- ]
885
+ task_inputs["__parents__"] = task_parents
589
886
 
590
- if wait_for:
591
- task_inputs["wait_for"] = await collect_task_run_inputs(wait_for)
887
+ # check wait for dependencies
888
+ if wait_for:
889
+ task_inputs["wait_for"] = collect_task_run_inputs_sync(wait_for)
592
890
 
593
- # Join extra task inputs
594
- for k, extras in (extra_task_inputs or {}).items():
595
- task_inputs[k] = task_inputs[k].union(extras)
891
+ # Join extra task inputs
892
+ for k, extras in (extra_task_inputs or {}).items():
893
+ task_inputs[k] = task_inputs[k].union(extras)
596
894
 
597
- # create the task run
598
- task_run = client.create_task_run(
599
- task=self,
600
- name=task_run_name,
601
- flow_run_id=(
895
+ flow_run_id = (
602
896
  getattr(flow_run_context.flow_run, "id", None)
603
897
  if flow_run_context and flow_run_context.flow_run
604
898
  else None
605
- ),
606
- dynamic_key=str(dynamic_key),
607
- state=Pending(),
608
- task_inputs=task_inputs,
609
- extra_tags=TagsContext.get().current_tags,
610
- )
611
- # the new engine uses sync clients but old engines use async clients
612
- if inspect.isawaitable(task_run):
613
- task_run = await task_run
614
-
615
- if flow_run_context and flow_run_context.flow_run:
616
- get_run_logger(flow_run_context).debug(
617
- f"Created task run {task_run.name!r} for task {self.name!r}"
618
899
  )
619
- else:
620
- logger.debug(f"Created task run {task_run.name!r} for task {self.name!r}")
900
+ task_run_id = id or uuid4()
901
+ state = prefect.states.Pending(
902
+ state_details=StateDetails(
903
+ task_run_id=task_run_id,
904
+ flow_run_id=flow_run_id,
905
+ )
906
+ )
907
+ task_run = TaskRun(
908
+ id=task_run_id,
909
+ name=task_run_name,
910
+ flow_run_id=flow_run_id,
911
+ task_key=self.task_key,
912
+ dynamic_key=str(dynamic_key),
913
+ task_version=self.version,
914
+ empirical_policy=TaskRunPolicy(
915
+ retries=self.retries,
916
+ retry_delay=self.retry_delay_seconds,
917
+ retry_jitter_factor=self.retry_jitter_factor,
918
+ ),
919
+ tags=list(set(self.tags).union(TagsContext.get().current_tags or [])),
920
+ task_inputs=task_inputs or {},
921
+ expected_start_time=state.timestamp,
922
+ state_id=state.id,
923
+ state_type=state.type,
924
+ state_name=state.name,
925
+ state=state,
926
+ created=state.timestamp,
927
+ updated=state.timestamp,
928
+ )
621
929
 
622
- return task_run
930
+ return task_run
623
931
 
624
932
  @overload
625
933
  def __call__(
@@ -659,9 +967,10 @@ class Task(Generic[P, R]):
659
967
  Run the task and return the result. If `return_state` is True returns
660
968
  the result is wrapped in a Prefect State which provides error handling.
661
969
  """
662
- from prefect.engine import enter_task_run_engine
663
- from prefect.task_engine import submit_autonomous_task_run_to_engine
664
- from prefect.task_runners import SequentialTaskRunner
970
+ from prefect.utilities.visualization import (
971
+ get_task_viz_tracker,
972
+ track_viz_task,
973
+ )
665
974
 
666
975
  # Convert the call args/kwargs to a parameter dict
667
976
  parameters = get_call_parameters(self.fn, args, kwargs)
@@ -674,90 +983,13 @@ class Task(Generic[P, R]):
674
983
  self.isasync, self.name, parameters, self.viz_return_value
675
984
  )
676
985
 
677
- if PREFECT_EXPERIMENTAL_ENABLE_NEW_ENGINE.value():
678
- from prefect.new_task_engine import run_task
679
-
680
- return run_task(
681
- task=self,
682
- parameters=parameters,
683
- wait_for=wait_for,
684
- return_type=return_type,
685
- )
686
-
687
- if (
688
- PREFECT_EXPERIMENTAL_ENABLE_TASK_SCHEDULING.value()
689
- and not FlowRunContext.get()
690
- ):
691
- from prefect import get_client
692
-
693
- return submit_autonomous_task_run_to_engine(
694
- task=self,
695
- task_run=None,
696
- task_runner=SequentialTaskRunner(),
697
- parameters=parameters,
698
- return_type=return_type,
699
- client=get_client(),
700
- )
701
- entering_from_task_run = bool(TaskRunContext.get())
986
+ from prefect.task_engine import run_task
702
987
 
703
- return enter_task_run_engine(
704
- self,
988
+ return run_task(
989
+ task=self,
705
990
  parameters=parameters,
706
991
  wait_for=wait_for,
707
- task_runner=SequentialTaskRunner(),
708
992
  return_type=return_type,
709
- mapped=False,
710
- entering_from_task_run=entering_from_task_run,
711
- )
712
-
713
- @overload
714
- def _run(
715
- self: "Task[P, NoReturn]",
716
- *args: P.args,
717
- **kwargs: P.kwargs,
718
- ) -> PrefectFuture[None, Sync]:
719
- # `NoReturn` matches if a type can't be inferred for the function which stops a
720
- # sync function from matching the `Coroutine` overload
721
- ...
722
-
723
- @overload
724
- def _run(
725
- self: "Task[P, Coroutine[Any, Any, T]]",
726
- *args: P.args,
727
- **kwargs: P.kwargs,
728
- ) -> Awaitable[State[T]]:
729
- ...
730
-
731
- @overload
732
- def _run(
733
- self: "Task[P, T]",
734
- *args: P.args,
735
- **kwargs: P.kwargs,
736
- ) -> State[T]:
737
- ...
738
-
739
- def _run(
740
- self,
741
- *args: P.args,
742
- wait_for: Optional[Iterable[PrefectFuture]] = None,
743
- **kwargs: P.kwargs,
744
- ) -> Union[State, Awaitable[State]]:
745
- """
746
- Run the task and return the final state.
747
- """
748
- from prefect.engine import enter_task_run_engine
749
- from prefect.task_runners import SequentialTaskRunner
750
-
751
- # Convert the call args/kwargs to a parameter dict
752
- parameters = get_call_parameters(self.fn, args, kwargs)
753
-
754
- return enter_task_run_engine(
755
- self,
756
- parameters=parameters,
757
- wait_for=wait_for,
758
- return_type="state",
759
- task_runner=SequentialTaskRunner(),
760
- mapped=False,
761
993
  )
762
994
 
763
995
  @overload
@@ -765,7 +997,7 @@ class Task(Generic[P, R]):
765
997
  self: "Task[P, NoReturn]",
766
998
  *args: P.args,
767
999
  **kwargs: P.kwargs,
768
- ) -> PrefectFuture[None, Sync]:
1000
+ ) -> PrefectFuture[NoReturn]:
769
1001
  # `NoReturn` matches if a type can't be inferred for the function which stops a
770
1002
  # sync function from matching the `Coroutine` overload
771
1003
  ...
@@ -775,7 +1007,7 @@ class Task(Generic[P, R]):
775
1007
  self: "Task[P, Coroutine[Any, Any, T]]",
776
1008
  *args: P.args,
777
1009
  **kwargs: P.kwargs,
778
- ) -> Awaitable[PrefectFuture[T, Async]]:
1010
+ ) -> PrefectFuture[T]:
779
1011
  ...
780
1012
 
781
1013
  @overload
@@ -783,12 +1015,12 @@ class Task(Generic[P, R]):
783
1015
  self: "Task[P, T]",
784
1016
  *args: P.args,
785
1017
  **kwargs: P.kwargs,
786
- ) -> PrefectFuture[T, Sync]:
1018
+ ) -> PrefectFuture[T]:
787
1019
  ...
788
1020
 
789
1021
  @overload
790
1022
  def submit(
791
- self: "Task[P, T]",
1023
+ self: "Task[P, Coroutine[Any, Any, T]]",
792
1024
  *args: P.args,
793
1025
  return_state: Literal[True],
794
1026
  **kwargs: P.kwargs,
@@ -799,16 +1031,9 @@ class Task(Generic[P, R]):
799
1031
  def submit(
800
1032
  self: "Task[P, T]",
801
1033
  *args: P.args,
1034
+ return_state: Literal[True],
802
1035
  **kwargs: P.kwargs,
803
- ) -> TaskRun:
804
- ...
805
-
806
- @overload
807
- def submit(
808
- self: "Task[P, Coroutine[Any, Any, T]]",
809
- *args: P.args,
810
- **kwargs: P.kwargs,
811
- ) -> Awaitable[TaskRun]:
1036
+ ) -> State[T]:
812
1037
  ...
813
1038
 
814
1039
  def submit(
@@ -817,19 +1042,13 @@ class Task(Generic[P, R]):
817
1042
  return_state: bool = False,
818
1043
  wait_for: Optional[Iterable[PrefectFuture]] = None,
819
1044
  **kwargs: Any,
820
- ) -> Union[PrefectFuture, Awaitable[PrefectFuture], TaskRun, Awaitable[TaskRun]]:
1045
+ ):
821
1046
  """
822
1047
  Submit a run of the task to the engine.
823
1048
 
824
- If writing an async task, this call must be awaited.
825
-
826
- If called from within a flow function,
827
-
828
1049
  Will create a new task run in the backing API and submit the task to the flow's
829
1050
  task runner. This call only blocks execution while the task is being submitted,
830
- once it is submitted, the flow function will continue executing. However, note
831
- that the `SequentialTaskRunner` does not implement parallel execution for sync tasks
832
- and they are fully resolved on submission.
1051
+ once it is submitted, the flow function will continue executing.
833
1052
 
834
1053
  Args:
835
1054
  *args: Arguments to run the task with
@@ -909,97 +1128,33 @@ class Task(Generic[P, R]):
909
1128
 
910
1129
  """
911
1130
 
912
- from prefect.engine import create_autonomous_task_run, enter_task_run_engine
1131
+ from prefect.utilities.visualization import (
1132
+ VisualizationUnsupportedError,
1133
+ get_task_viz_tracker,
1134
+ )
913
1135
 
914
1136
  # Convert the call args/kwargs to a parameter dict
915
1137
  parameters = get_call_parameters(self.fn, args, kwargs)
916
- return_type = "state" if return_state else "future"
917
1138
  flow_run_context = FlowRunContext.get()
918
1139
 
1140
+ if not flow_run_context:
1141
+ raise RuntimeError(
1142
+ "Unable to determine task runner to use for submission. If you are"
1143
+ " submitting a task outside of a flow, please use `.delay`"
1144
+ " to submit the task run for deferred execution."
1145
+ )
1146
+
919
1147
  task_viz_tracker = get_task_viz_tracker()
920
1148
  if task_viz_tracker:
921
1149
  raise VisualizationUnsupportedError(
922
1150
  "`task.submit()` is not currently supported by `flow.visualize()`"
923
1151
  )
924
1152
 
925
- if PREFECT_EXPERIMENTAL_ENABLE_TASK_SCHEDULING and not flow_run_context:
926
- create_autonomous_task_run_call = create_call(
927
- create_autonomous_task_run, task=self, parameters=parameters
928
- )
929
- if self.isasync:
930
- return from_async.wait_for_call_in_loop_thread(
931
- create_autonomous_task_run_call
932
- )
933
- else:
934
- return from_sync.wait_for_call_in_loop_thread(
935
- create_autonomous_task_run_call
936
- )
937
- if PREFECT_EXPERIMENTAL_ENABLE_NEW_ENGINE and flow_run_context:
938
- if self.isasync:
939
- return self._submit_async(
940
- parameters=parameters,
941
- flow_run_context=flow_run_context,
942
- wait_for=wait_for,
943
- return_state=return_state,
944
- )
945
- else:
946
- raise NotImplementedError(
947
- "Submitting sync tasks with the new engine has not be implemented yet."
948
- )
949
-
950
- else:
951
- return enter_task_run_engine(
952
- self,
953
- parameters=parameters,
954
- wait_for=wait_for,
955
- return_type=return_type,
956
- task_runner=None, # Use the flow's task runner
957
- mapped=False,
958
- )
959
-
960
- async def _submit_async(
961
- self,
962
- parameters: Dict[str, Any],
963
- flow_run_context: FlowRunContext,
964
- wait_for: Optional[Iterable[PrefectFuture]],
965
- return_state: bool,
966
- ):
967
- from prefect.new_task_engine import run_task_async
968
-
969
1153
  task_runner = flow_run_context.task_runner
970
-
971
- task_run = await self.create_run(
972
- client=flow_run_context.client,
973
- flow_run_context=flow_run_context,
974
- parameters=parameters,
975
- wait_for=wait_for,
976
- )
977
-
978
- future = PrefectFuture(
979
- name=task_run.name,
980
- key=uuid4(),
981
- task_runner=task_runner,
982
- asynchronous=(self.isasync and flow_run_context.flow.isasync),
983
- )
984
- future.task_run = task_run
985
- flow_run_context.task_run_futures.append(future)
986
- await task_runner.submit(
987
- key=future.key,
988
- call=partial(
989
- run_task_async,
990
- task=self,
991
- task_run=task_run,
992
- parameters=parameters,
993
- wait_for=wait_for,
994
- return_type="state",
995
- ),
996
- )
997
- # TODO: I don't like this. Can we move responsibility for creating the future
998
- # and setting this anyio.Event to the task runner?
999
- future._submitted.set()
1000
-
1154
+ future = task_runner.submit(self, parameters, wait_for)
1001
1155
  if return_state:
1002
- return await future.wait()
1156
+ future.wait()
1157
+ return future.state
1003
1158
  else:
1004
1159
  return future
1005
1160
 
@@ -1008,9 +1163,7 @@ class Task(Generic[P, R]):
1008
1163
  self: "Task[P, NoReturn]",
1009
1164
  *args: P.args,
1010
1165
  **kwargs: P.kwargs,
1011
- ) -> List[PrefectFuture[None, Sync]]:
1012
- # `NoReturn` matches if a type can't be inferred for the function which stops a
1013
- # sync function from matching the `Coroutine` overload
1166
+ ) -> PrefectFutureList[PrefectFuture[NoReturn]]:
1014
1167
  ...
1015
1168
 
1016
1169
  @overload
@@ -1018,7 +1171,7 @@ class Task(Generic[P, R]):
1018
1171
  self: "Task[P, Coroutine[Any, Any, T]]",
1019
1172
  *args: P.args,
1020
1173
  **kwargs: P.kwargs,
1021
- ) -> Awaitable[List[PrefectFuture[T, Async]]]:
1174
+ ) -> PrefectFutureList[PrefectFuture[T]]:
1022
1175
  ...
1023
1176
 
1024
1177
  @overload
@@ -1026,7 +1179,16 @@ class Task(Generic[P, R]):
1026
1179
  self: "Task[P, T]",
1027
1180
  *args: P.args,
1028
1181
  **kwargs: P.kwargs,
1029
- ) -> List[PrefectFuture[T, Sync]]:
1182
+ ) -> PrefectFutureList[PrefectFuture[T]]:
1183
+ ...
1184
+
1185
+ @overload
1186
+ def map(
1187
+ self: "Task[P, Coroutine[Any, Any, T]]",
1188
+ *args: P.args,
1189
+ return_state: Literal[True],
1190
+ **kwargs: P.kwargs,
1191
+ ) -> PrefectFutureList[State[T]]:
1030
1192
  ...
1031
1193
 
1032
1194
  @overload
@@ -1035,7 +1197,7 @@ class Task(Generic[P, R]):
1035
1197
  *args: P.args,
1036
1198
  return_state: Literal[True],
1037
1199
  **kwargs: P.kwargs,
1038
- ) -> List[State[T]]:
1200
+ ) -> PrefectFutureList[State[T]]:
1039
1201
  ...
1040
1202
 
1041
1203
  def map(
@@ -1043,13 +1205,15 @@ class Task(Generic[P, R]):
1043
1205
  *args: Any,
1044
1206
  return_state: bool = False,
1045
1207
  wait_for: Optional[Iterable[PrefectFuture]] = None,
1208
+ deferred: bool = False,
1046
1209
  **kwargs: Any,
1047
- ) -> Any:
1210
+ ):
1048
1211
  """
1049
1212
  Submit a mapped run of the task to a worker.
1050
1213
 
1051
- Must be called within a flow function. If writing an async task, this
1052
- call must be awaited.
1214
+ Must be called within a flow run context. Will return a list of futures
1215
+ that should be waited on before exiting the flow context to ensure all
1216
+ mapped tasks have completed.
1053
1217
 
1054
1218
  Must be called with at least one iterable and all iterables must be
1055
1219
  the same length. Any arguments that are not iterable will be treated as
@@ -1059,9 +1223,7 @@ class Task(Generic[P, R]):
1059
1223
  backing API and submit the task runs to the flow's task runner. This
1060
1224
  call blocks if given a future as input while the future is resolved. It
1061
1225
  also blocks while the tasks are being submitted, once they are
1062
- submitted, the flow function will continue executing. However, note
1063
- that the `SequentialTaskRunner` does not implement parallel execution
1064
- for sync tasks and they are fully resolved on submission.
1226
+ submitted, the flow function will continue executing.
1065
1227
 
1066
1228
  Args:
1067
1229
  *args: Iterable and static arguments to run the tasks with
@@ -1089,15 +1251,14 @@ class Task(Generic[P, R]):
1089
1251
  >>> from prefect import flow
1090
1252
  >>> @flow
1091
1253
  >>> def my_flow():
1092
- >>> my_task.map([1, 2, 3])
1254
+ >>> return my_task.map([1, 2, 3])
1093
1255
 
1094
1256
  Wait for all mapped tasks to finish
1095
1257
 
1096
1258
  >>> @flow
1097
1259
  >>> def my_flow():
1098
1260
  >>> futures = my_task.map([1, 2, 3])
1099
- >>> for future in futures:
1100
- >>> future.wait()
1261
+ >>> futures.wait():
1101
1262
  >>> # Now all of the mapped tasks have finished
1102
1263
  >>> my_task(10)
1103
1264
 
@@ -1106,8 +1267,8 @@ class Task(Generic[P, R]):
1106
1267
  >>> @flow
1107
1268
  >>> def my_flow():
1108
1269
  >>> futures = my_task.map([1, 2, 3])
1109
- >>> for future in futures:
1110
- >>> print(future.result())
1270
+ >>> for x in futures.result():
1271
+ >>> print(x)
1111
1272
  >>> my_flow()
1112
1273
  2
1113
1274
  3
@@ -1128,6 +1289,7 @@ class Task(Generic[P, R]):
1128
1289
  >>>
1129
1290
  >>> # task 2 will wait for task_1 to complete
1130
1291
  >>> y = task_2.map([1, 2, 3], wait_for=[x])
1292
+ >>> return y
1131
1293
 
1132
1294
  Use a non-iterable input as a constant across mapped tasks
1133
1295
  >>> @task
@@ -1136,7 +1298,7 @@ class Task(Generic[P, R]):
1136
1298
  >>>
1137
1299
  >>> @flow
1138
1300
  >>> def my_flow():
1139
- >>> display.map("Check it out: ", [1, 2, 3])
1301
+ >>> return display.map("Check it out: ", [1, 2, 3])
1140
1302
  >>>
1141
1303
  >>> my_flow()
1142
1304
  Check it out: 1
@@ -1158,12 +1320,16 @@ class Task(Generic[P, R]):
1158
1320
  [[11, 21], [12, 22], [13, 23]]
1159
1321
  """
1160
1322
 
1161
- from prefect.engine import begin_task_map, enter_task_run_engine
1323
+ from prefect.task_runners import TaskRunner
1324
+ from prefect.utilities.visualization import (
1325
+ VisualizationUnsupportedError,
1326
+ get_task_viz_tracker,
1327
+ )
1162
1328
 
1163
1329
  # Convert the call args/kwargs to a parameter dict; do not apply defaults
1164
1330
  # since they should not be mapped over
1165
1331
  parameters = get_call_parameters(self.fn, args, kwargs, apply_defaults=False)
1166
- return_type = "state" if return_state else "future"
1332
+ flow_run_context = FlowRunContext.get()
1167
1333
 
1168
1334
  task_viz_tracker = get_task_viz_tracker()
1169
1335
  if task_viz_tracker:
@@ -1171,42 +1337,185 @@ class Task(Generic[P, R]):
1171
1337
  "`task.map()` is not currently supported by `flow.visualize()`"
1172
1338
  )
1173
1339
 
1174
- if (
1175
- PREFECT_EXPERIMENTAL_ENABLE_TASK_SCHEDULING.value()
1176
- and not FlowRunContext.get()
1177
- ):
1178
- map_call = create_call(
1179
- begin_task_map,
1180
- task=self,
1340
+ if deferred:
1341
+ parameters_list = expand_mapping_parameters(self.fn, parameters)
1342
+ futures = [
1343
+ self.apply_async(kwargs=parameters, wait_for=wait_for)
1344
+ for parameters in parameters_list
1345
+ ]
1346
+ elif task_runner := getattr(flow_run_context, "task_runner", None):
1347
+ assert isinstance(task_runner, TaskRunner)
1348
+ futures = task_runner.map(self, parameters, wait_for)
1349
+ else:
1350
+ raise RuntimeError(
1351
+ "Unable to determine task runner to use for mapped task runs. If"
1352
+ " you are mapping a task outside of a flow, please provide"
1353
+ " `deferred=True` to submit the mapped task runs for deferred"
1354
+ " execution."
1355
+ )
1356
+ if return_state:
1357
+ states = []
1358
+ for future in futures:
1359
+ future.wait()
1360
+ states.append(future.state)
1361
+ return states
1362
+ else:
1363
+ return futures
1364
+
1365
+ def apply_async(
1366
+ self,
1367
+ args: Optional[Tuple[Any, ...]] = None,
1368
+ kwargs: Optional[Dict[str, Any]] = None,
1369
+ wait_for: Optional[Iterable[PrefectFuture]] = None,
1370
+ dependencies: Optional[Dict[str, Set[TaskRunInput]]] = None,
1371
+ ) -> PrefectDistributedFuture:
1372
+ """
1373
+ Create a pending task run for a task worker to execute.
1374
+
1375
+ Args:
1376
+ args: Arguments to run the task with
1377
+ kwargs: Keyword arguments to run the task with
1378
+
1379
+ Returns:
1380
+ A PrefectDistributedFuture object representing the pending task run
1381
+
1382
+ Examples:
1383
+
1384
+ Define a task
1385
+
1386
+ >>> from prefect import task
1387
+ >>> @task
1388
+ >>> def my_task(name: str = "world"):
1389
+ >>> return f"hello {name}"
1390
+
1391
+ Create a pending task run for the task
1392
+
1393
+ >>> from prefect import flow
1394
+ >>> @flow
1395
+ >>> def my_flow():
1396
+ >>> my_task.apply_async(("marvin",))
1397
+
1398
+ Wait for a task to finish
1399
+
1400
+ >>> @flow
1401
+ >>> def my_flow():
1402
+ >>> my_task.apply_async(("marvin",)).wait()
1403
+
1404
+
1405
+ >>> @flow
1406
+ >>> def my_flow():
1407
+ >>> print(my_task.apply_async(("marvin",)).result())
1408
+ >>>
1409
+ >>> my_flow()
1410
+ hello marvin
1411
+
1412
+ TODO: Enforce ordering between tasks that do not exchange data
1413
+ >>> @task
1414
+ >>> def task_1():
1415
+ >>> pass
1416
+ >>>
1417
+ >>> @task
1418
+ >>> def task_2():
1419
+ >>> pass
1420
+ >>>
1421
+ >>> @flow
1422
+ >>> def my_flow():
1423
+ >>> x = task_1.apply_async()
1424
+ >>>
1425
+ >>> # task 2 will wait for task_1 to complete
1426
+ >>> y = task_2.apply_async(wait_for=[x])
1427
+
1428
+ """
1429
+ from prefect.utilities.visualization import (
1430
+ VisualizationUnsupportedError,
1431
+ get_task_viz_tracker,
1432
+ )
1433
+
1434
+ task_viz_tracker = get_task_viz_tracker()
1435
+ if task_viz_tracker:
1436
+ raise VisualizationUnsupportedError(
1437
+ "`task.apply_async()` is not currently supported by `flow.visualize()`"
1438
+ )
1439
+ args = args or ()
1440
+ kwargs = kwargs or {}
1441
+
1442
+ # Convert the call args/kwargs to a parameter dict
1443
+ parameters = get_call_parameters(self.fn, args, kwargs)
1444
+
1445
+ task_run: TaskRun = run_coro_as_sync(
1446
+ self.create_run(
1181
1447
  parameters=parameters,
1182
- flow_run_context=None,
1448
+ deferred=True,
1183
1449
  wait_for=wait_for,
1184
- return_type=return_type,
1185
- task_runner=None,
1186
- autonomous=True,
1450
+ extra_task_inputs=dependencies,
1187
1451
  )
1188
- if self.isasync:
1189
- return from_async.wait_for_call_in_loop_thread(map_call)
1190
- else:
1191
- return from_sync.wait_for_call_in_loop_thread(map_call)
1452
+ ) # type: ignore
1192
1453
 
1193
- return enter_task_run_engine(
1194
- self,
1195
- parameters=parameters,
1196
- wait_for=wait_for,
1197
- return_type=return_type,
1198
- task_runner=None,
1199
- mapped=True,
1454
+ from prefect.utilities.engine import emit_task_run_state_change_event
1455
+
1456
+ # emit a `SCHEDULED` event for the task run
1457
+ emit_task_run_state_change_event(
1458
+ task_run=task_run,
1459
+ initial_state=None,
1460
+ validated_state=task_run.state,
1200
1461
  )
1201
1462
 
1202
- def serve(self, task_runner: Optional[BaseTaskRunner] = None) -> "Task":
1463
+ if task_run_url := url_for(task_run):
1464
+ logger.info(
1465
+ f"Created task run {task_run.name!r}. View it in the UI at {task_run_url!r}"
1466
+ )
1467
+
1468
+ return PrefectDistributedFuture(task_run_id=task_run.id)
1469
+
1470
+ def delay(self, *args: P.args, **kwargs: P.kwargs) -> PrefectDistributedFuture:
1471
+ """
1472
+ An alias for `apply_async` with simpler calling semantics.
1473
+
1474
+ Avoids having to use explicit "args" and "kwargs" arguments. Arguments
1475
+ will pass through as-is to the task.
1476
+
1477
+ Examples:
1478
+
1479
+ Define a task
1480
+
1481
+ >>> from prefect import task
1482
+ >>> @task
1483
+ >>> def my_task(name: str = "world"):
1484
+ >>> return f"hello {name}"
1485
+
1486
+ Create a pending task run for the task
1487
+
1488
+ >>> from prefect import flow
1489
+ >>> @flow
1490
+ >>> def my_flow():
1491
+ >>> my_task.delay("marvin")
1492
+
1493
+ Wait for a task to finish
1494
+
1495
+ >>> @flow
1496
+ >>> def my_flow():
1497
+ >>> my_task.delay("marvin").wait()
1498
+
1499
+ Use the result from a task in a flow
1500
+
1501
+ >>> @flow
1502
+ >>> def my_flow():
1503
+ >>> print(my_task.delay("marvin").result())
1504
+ >>>
1505
+ >>> my_flow()
1506
+ hello marvin
1507
+ """
1508
+ return self.apply_async(args=args, kwargs=kwargs)
1509
+
1510
+ @sync_compatible
1511
+ async def serve(self) -> NoReturn:
1203
1512
  """Serve the task using the provided task runner. This method is used to
1204
1513
  establish a websocket connection with the Prefect server and listen for
1205
1514
  submitted task runs to execute.
1206
1515
 
1207
1516
  Args:
1208
1517
  task_runner: The task runner to use for serving the task. If not provided,
1209
- the default ConcurrentTaskRunner will be used.
1518
+ the default task runner will be used.
1210
1519
 
1211
1520
  Examples:
1212
1521
  Serve a task using the default task runner
@@ -1216,16 +1525,9 @@ class Task(Generic[P, R]):
1216
1525
 
1217
1526
  >>> my_task.serve()
1218
1527
  """
1528
+ from prefect.task_worker import serve
1219
1529
 
1220
- if not PREFECT_EXPERIMENTAL_ENABLE_TASK_SCHEDULING:
1221
- raise ValueError(
1222
- "Task's `serve` method is an experimental feature and must be enabled with "
1223
- "`prefect config set PREFECT_EXPERIMENTAL_ENABLE_TASK_SCHEDULING=True`"
1224
- )
1225
-
1226
- from prefect.task_server import serve
1227
-
1228
- serve(self, task_runner=task_runner)
1530
+ await serve(self)
1229
1531
 
1230
1532
 
1231
1533
  @overload
@@ -1236,12 +1538,15 @@ def task(__fn: Callable[P, R]) -> Task[P, R]:
1236
1538
  @overload
1237
1539
  def task(
1238
1540
  *,
1239
- name: str = None,
1240
- description: str = None,
1241
- tags: Iterable[str] = None,
1242
- version: str = None,
1243
- cache_key_fn: Callable[["TaskRunContext", Dict[str, Any]], Optional[str]] = None,
1244
- cache_expiration: datetime.timedelta = None,
1541
+ name: Optional[str] = None,
1542
+ description: Optional[str] = None,
1543
+ tags: Optional[Iterable[str]] = None,
1544
+ version: Optional[str] = None,
1545
+ cache_policy: Union[CachePolicy, Type[NotSet]] = NotSet,
1546
+ cache_key_fn: Optional[
1547
+ Callable[["TaskRunContext", Dict[str, Any]], Optional[str]]
1548
+ ] = None,
1549
+ cache_expiration: Optional[datetime.timedelta] = None,
1245
1550
  task_run_name: Optional[Union[Callable[[], str], str]] = None,
1246
1551
  retries: int = 0,
1247
1552
  retry_delay_seconds: Union[
@@ -1256,7 +1561,7 @@ def task(
1256
1561
  result_storage_key: Optional[str] = None,
1257
1562
  result_serializer: Optional[ResultSerializer] = None,
1258
1563
  cache_result_in_memory: bool = True,
1259
- timeout_seconds: Union[int, float] = None,
1564
+ timeout_seconds: Union[int, float, None] = None,
1260
1565
  log_prints: Optional[bool] = None,
1261
1566
  refresh_cache: Optional[bool] = None,
1262
1567
  on_completion: Optional[List[Callable[["Task", TaskRun, State], None]]] = None,
@@ -1270,19 +1575,19 @@ def task(
1270
1575
  def task(
1271
1576
  __fn=None,
1272
1577
  *,
1273
- name: str = None,
1274
- description: str = None,
1275
- tags: Iterable[str] = None,
1276
- version: str = None,
1277
- cache_key_fn: Callable[["TaskRunContext", Dict[str, Any]], Optional[str]] = None,
1278
- cache_expiration: datetime.timedelta = None,
1578
+ name: Optional[str] = None,
1579
+ description: Optional[str] = None,
1580
+ tags: Optional[Iterable[str]] = None,
1581
+ version: Optional[str] = None,
1582
+ cache_policy: Union[CachePolicy, Type[NotSet]] = NotSet,
1583
+ cache_key_fn: Union[
1584
+ Callable[["TaskRunContext", Dict[str, Any]], Optional[str]], None
1585
+ ] = None,
1586
+ cache_expiration: Optional[datetime.timedelta] = None,
1279
1587
  task_run_name: Optional[Union[Callable[[], str], str]] = None,
1280
- retries: int = None,
1588
+ retries: Optional[int] = None,
1281
1589
  retry_delay_seconds: Union[
1282
- float,
1283
- int,
1284
- List[float],
1285
- Callable[[int], List[float]],
1590
+ float, int, List[float], Callable[[int], List[float]], None
1286
1591
  ] = None,
1287
1592
  retry_jitter_factor: Optional[float] = None,
1288
1593
  persist_result: Optional[bool] = None,
@@ -1290,7 +1595,7 @@ def task(
1290
1595
  result_storage_key: Optional[str] = None,
1291
1596
  result_serializer: Optional[ResultSerializer] = None,
1292
1597
  cache_result_in_memory: bool = True,
1293
- timeout_seconds: Union[int, float] = None,
1598
+ timeout_seconds: Union[int, float, None] = None,
1294
1599
  log_prints: Optional[bool] = None,
1295
1600
  refresh_cache: Optional[bool] = None,
1296
1601
  on_completion: Optional[List[Callable[["Task", TaskRun, State], None]]] = None,
@@ -1331,10 +1636,10 @@ def task(
1331
1636
  cannot exceed 50.
1332
1637
  retry_jitter_factor: An optional factor that defines the factor to which a retry
1333
1638
  can be jittered in order to avoid a "thundering herd".
1334
- persist_result: An optional toggle indicating whether the result of this task
1335
- should be persisted to result storage. Defaults to `None`, which indicates
1336
- that Prefect should choose whether the result should be persisted depending on
1337
- the features being used.
1639
+ persist_result: A toggle indicating whether the result of this task
1640
+ should be persisted to result storage. Defaults to `None`, which
1641
+ indicates that the global default should be used (which is `True` by
1642
+ default).
1338
1643
  result_storage: An optional block to use to persist the result of this task.
1339
1644
  Defaults to the value set in the flow the task is called in.
1340
1645
  result_storage_key: An optional key to store the result in storage at when persisted.
@@ -1408,6 +1713,9 @@ def task(
1408
1713
  """
1409
1714
 
1410
1715
  if __fn:
1716
+ if isinstance(__fn, (classmethod, staticmethod)):
1717
+ method_decorator = type(__fn).__name__
1718
+ raise TypeError(f"@{method_decorator} should be applied on top of @task")
1411
1719
  return cast(
1412
1720
  Task[P, R],
1413
1721
  Task(
@@ -1416,6 +1724,7 @@ def task(
1416
1724
  description=description,
1417
1725
  tags=tags,
1418
1726
  version=version,
1727
+ cache_policy=cache_policy,
1419
1728
  cache_key_fn=cache_key_fn,
1420
1729
  cache_expiration=cache_expiration,
1421
1730
  task_run_name=task_run_name,
@@ -1445,6 +1754,7 @@ def task(
1445
1754
  description=description,
1446
1755
  tags=tags,
1447
1756
  version=version,
1757
+ cache_policy=cache_policy,
1448
1758
  cache_key_fn=cache_key_fn,
1449
1759
  cache_expiration=cache_expiration,
1450
1760
  task_run_name=task_run_name,