prefect-client 2.20.4__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.
- prefect/__init__.py +74 -110
- prefect/_internal/compatibility/deprecated.py +6 -115
- prefect/_internal/compatibility/experimental.py +4 -79
- prefect/_internal/compatibility/migration.py +166 -0
- prefect/_internal/concurrency/__init__.py +2 -2
- prefect/_internal/concurrency/api.py +1 -35
- prefect/_internal/concurrency/calls.py +0 -6
- prefect/_internal/concurrency/cancellation.py +0 -3
- prefect/_internal/concurrency/event_loop.py +0 -20
- prefect/_internal/concurrency/inspection.py +3 -3
- prefect/_internal/concurrency/primitives.py +1 -0
- prefect/_internal/concurrency/services.py +23 -0
- prefect/_internal/concurrency/threads.py +35 -0
- prefect/_internal/concurrency/waiters.py +0 -28
- prefect/_internal/integrations.py +7 -0
- prefect/_internal/pydantic/__init__.py +0 -45
- prefect/_internal/pydantic/annotations/pendulum.py +2 -2
- prefect/_internal/pydantic/v1_schema.py +21 -22
- prefect/_internal/pydantic/v2_schema.py +0 -2
- prefect/_internal/pydantic/v2_validated_func.py +18 -23
- prefect/_internal/pytz.py +1 -1
- prefect/_internal/retries.py +61 -0
- prefect/_internal/schemas/bases.py +45 -177
- prefect/_internal/schemas/fields.py +1 -43
- prefect/_internal/schemas/validators.py +47 -233
- prefect/agent.py +3 -695
- prefect/artifacts.py +173 -14
- prefect/automations.py +39 -4
- prefect/blocks/abstract.py +1 -1
- prefect/blocks/core.py +405 -153
- prefect/blocks/fields.py +2 -57
- prefect/blocks/notifications.py +43 -28
- prefect/blocks/redis.py +168 -0
- prefect/blocks/system.py +67 -20
- prefect/blocks/webhook.py +2 -9
- prefect/cache_policies.py +239 -0
- prefect/client/__init__.py +4 -0
- prefect/client/base.py +33 -27
- prefect/client/cloud.py +65 -20
- prefect/client/collections.py +1 -1
- prefect/client/orchestration.py +650 -442
- prefect/client/schemas/actions.py +115 -100
- prefect/client/schemas/filters.py +46 -52
- prefect/client/schemas/objects.py +228 -178
- prefect/client/schemas/responses.py +18 -36
- prefect/client/schemas/schedules.py +55 -36
- prefect/client/schemas/sorting.py +2 -0
- prefect/client/subscriptions.py +8 -7
- prefect/client/types/flexible_schedule_list.py +11 -0
- prefect/client/utilities.py +9 -6
- prefect/concurrency/asyncio.py +60 -11
- prefect/concurrency/context.py +24 -0
- prefect/concurrency/events.py +2 -2
- prefect/concurrency/services.py +46 -16
- prefect/concurrency/sync.py +51 -7
- prefect/concurrency/v1/asyncio.py +143 -0
- prefect/concurrency/v1/context.py +27 -0
- prefect/concurrency/v1/events.py +61 -0
- prefect/concurrency/v1/services.py +116 -0
- prefect/concurrency/v1/sync.py +92 -0
- prefect/context.py +246 -149
- prefect/deployments/__init__.py +33 -18
- prefect/deployments/base.py +10 -15
- prefect/deployments/deployments.py +2 -1048
- prefect/deployments/flow_runs.py +178 -0
- prefect/deployments/runner.py +72 -173
- prefect/deployments/schedules.py +31 -25
- prefect/deployments/steps/__init__.py +0 -1
- prefect/deployments/steps/core.py +7 -0
- prefect/deployments/steps/pull.py +15 -21
- prefect/deployments/steps/utility.py +2 -1
- prefect/docker/__init__.py +20 -0
- prefect/docker/docker_image.py +82 -0
- prefect/engine.py +15 -2475
- prefect/events/actions.py +17 -23
- prefect/events/cli/automations.py +20 -7
- prefect/events/clients.py +142 -80
- prefect/events/filters.py +14 -18
- prefect/events/related.py +74 -75
- prefect/events/schemas/__init__.py +0 -5
- prefect/events/schemas/automations.py +55 -46
- prefect/events/schemas/deployment_triggers.py +7 -197
- prefect/events/schemas/events.py +46 -65
- prefect/events/schemas/labelling.py +10 -14
- prefect/events/utilities.py +4 -5
- prefect/events/worker.py +23 -8
- prefect/exceptions.py +15 -0
- prefect/filesystems.py +30 -529
- prefect/flow_engine.py +827 -0
- prefect/flow_runs.py +379 -7
- prefect/flows.py +470 -360
- prefect/futures.py +382 -331
- prefect/infrastructure/__init__.py +5 -26
- prefect/infrastructure/base.py +3 -320
- prefect/infrastructure/provisioners/__init__.py +5 -3
- prefect/infrastructure/provisioners/cloud_run.py +13 -8
- prefect/infrastructure/provisioners/container_instance.py +14 -9
- prefect/infrastructure/provisioners/ecs.py +10 -8
- prefect/infrastructure/provisioners/modal.py +8 -5
- prefect/input/__init__.py +4 -0
- prefect/input/actions.py +2 -4
- prefect/input/run_input.py +9 -9
- prefect/logging/formatters.py +2 -4
- prefect/logging/handlers.py +9 -14
- prefect/logging/loggers.py +5 -5
- prefect/main.py +72 -0
- prefect/plugins.py +2 -64
- prefect/profiles.toml +16 -2
- prefect/records/__init__.py +1 -0
- prefect/records/base.py +223 -0
- prefect/records/filesystem.py +207 -0
- prefect/records/memory.py +178 -0
- prefect/records/result_store.py +64 -0
- prefect/results.py +577 -504
- prefect/runner/runner.py +117 -47
- prefect/runner/server.py +32 -34
- prefect/runner/storage.py +3 -12
- prefect/runner/submit.py +2 -10
- prefect/runner/utils.py +2 -2
- prefect/runtime/__init__.py +1 -0
- prefect/runtime/deployment.py +1 -0
- prefect/runtime/flow_run.py +40 -5
- prefect/runtime/task_run.py +1 -0
- prefect/serializers.py +28 -39
- prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
- prefect/settings.py +209 -332
- prefect/states.py +160 -63
- prefect/task_engine.py +1478 -57
- prefect/task_runners.py +383 -287
- prefect/task_runs.py +240 -0
- prefect/task_worker.py +463 -0
- prefect/tasks.py +684 -374
- prefect/transactions.py +410 -0
- prefect/types/__init__.py +72 -86
- prefect/types/entrypoint.py +13 -0
- prefect/utilities/annotations.py +4 -3
- prefect/utilities/asyncutils.py +227 -148
- prefect/utilities/callables.py +137 -45
- prefect/utilities/collections.py +134 -86
- prefect/utilities/dispatch.py +27 -14
- prefect/utilities/dockerutils.py +11 -4
- prefect/utilities/engine.py +186 -32
- prefect/utilities/filesystem.py +4 -5
- prefect/utilities/importtools.py +26 -27
- prefect/utilities/pydantic.py +128 -38
- prefect/utilities/schema_tools/hydration.py +18 -1
- prefect/utilities/schema_tools/validation.py +30 -0
- prefect/utilities/services.py +35 -9
- prefect/utilities/templating.py +12 -2
- prefect/utilities/timeout.py +20 -5
- prefect/utilities/urls.py +195 -0
- prefect/utilities/visualization.py +1 -0
- prefect/variables.py +78 -59
- prefect/workers/__init__.py +0 -1
- prefect/workers/base.py +237 -244
- prefect/workers/block.py +5 -226
- prefect/workers/cloud.py +6 -0
- prefect/workers/process.py +265 -12
- prefect/workers/server.py +29 -11
- {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/METADATA +28 -24
- prefect_client-3.0.0.dist-info/RECORD +201 -0
- {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/WHEEL +1 -1
- prefect/_internal/pydantic/_base_model.py +0 -51
- prefect/_internal/pydantic/_compat.py +0 -82
- prefect/_internal/pydantic/_flags.py +0 -20
- prefect/_internal/pydantic/_types.py +0 -8
- prefect/_internal/pydantic/utilities/config_dict.py +0 -72
- prefect/_internal/pydantic/utilities/field_validator.py +0 -150
- prefect/_internal/pydantic/utilities/model_construct.py +0 -56
- prefect/_internal/pydantic/utilities/model_copy.py +0 -55
- prefect/_internal/pydantic/utilities/model_dump.py +0 -136
- prefect/_internal/pydantic/utilities/model_dump_json.py +0 -112
- prefect/_internal/pydantic/utilities/model_fields.py +0 -50
- prefect/_internal/pydantic/utilities/model_fields_set.py +0 -29
- prefect/_internal/pydantic/utilities/model_json_schema.py +0 -82
- prefect/_internal/pydantic/utilities/model_rebuild.py +0 -80
- prefect/_internal/pydantic/utilities/model_validate.py +0 -75
- prefect/_internal/pydantic/utilities/model_validate_json.py +0 -68
- prefect/_internal/pydantic/utilities/model_validator.py +0 -87
- prefect/_internal/pydantic/utilities/type_adapter.py +0 -71
- prefect/_vendor/fastapi/__init__.py +0 -25
- prefect/_vendor/fastapi/applications.py +0 -946
- prefect/_vendor/fastapi/background.py +0 -3
- prefect/_vendor/fastapi/concurrency.py +0 -44
- prefect/_vendor/fastapi/datastructures.py +0 -58
- prefect/_vendor/fastapi/dependencies/__init__.py +0 -0
- prefect/_vendor/fastapi/dependencies/models.py +0 -64
- prefect/_vendor/fastapi/dependencies/utils.py +0 -877
- prefect/_vendor/fastapi/encoders.py +0 -177
- prefect/_vendor/fastapi/exception_handlers.py +0 -40
- prefect/_vendor/fastapi/exceptions.py +0 -46
- prefect/_vendor/fastapi/logger.py +0 -3
- prefect/_vendor/fastapi/middleware/__init__.py +0 -1
- prefect/_vendor/fastapi/middleware/asyncexitstack.py +0 -25
- prefect/_vendor/fastapi/middleware/cors.py +0 -3
- prefect/_vendor/fastapi/middleware/gzip.py +0 -3
- prefect/_vendor/fastapi/middleware/httpsredirect.py +0 -3
- prefect/_vendor/fastapi/middleware/trustedhost.py +0 -3
- prefect/_vendor/fastapi/middleware/wsgi.py +0 -3
- prefect/_vendor/fastapi/openapi/__init__.py +0 -0
- prefect/_vendor/fastapi/openapi/constants.py +0 -2
- prefect/_vendor/fastapi/openapi/docs.py +0 -203
- prefect/_vendor/fastapi/openapi/models.py +0 -480
- prefect/_vendor/fastapi/openapi/utils.py +0 -485
- prefect/_vendor/fastapi/param_functions.py +0 -340
- prefect/_vendor/fastapi/params.py +0 -453
- prefect/_vendor/fastapi/py.typed +0 -0
- prefect/_vendor/fastapi/requests.py +0 -4
- prefect/_vendor/fastapi/responses.py +0 -40
- prefect/_vendor/fastapi/routing.py +0 -1331
- prefect/_vendor/fastapi/security/__init__.py +0 -15
- prefect/_vendor/fastapi/security/api_key.py +0 -98
- prefect/_vendor/fastapi/security/base.py +0 -6
- prefect/_vendor/fastapi/security/http.py +0 -172
- prefect/_vendor/fastapi/security/oauth2.py +0 -227
- prefect/_vendor/fastapi/security/open_id_connect_url.py +0 -34
- prefect/_vendor/fastapi/security/utils.py +0 -10
- prefect/_vendor/fastapi/staticfiles.py +0 -1
- prefect/_vendor/fastapi/templating.py +0 -3
- prefect/_vendor/fastapi/testclient.py +0 -1
- prefect/_vendor/fastapi/types.py +0 -3
- prefect/_vendor/fastapi/utils.py +0 -235
- prefect/_vendor/fastapi/websockets.py +0 -7
- prefect/_vendor/starlette/__init__.py +0 -1
- prefect/_vendor/starlette/_compat.py +0 -28
- prefect/_vendor/starlette/_exception_handler.py +0 -80
- prefect/_vendor/starlette/_utils.py +0 -88
- prefect/_vendor/starlette/applications.py +0 -261
- prefect/_vendor/starlette/authentication.py +0 -159
- prefect/_vendor/starlette/background.py +0 -43
- prefect/_vendor/starlette/concurrency.py +0 -59
- prefect/_vendor/starlette/config.py +0 -151
- prefect/_vendor/starlette/convertors.py +0 -87
- prefect/_vendor/starlette/datastructures.py +0 -707
- prefect/_vendor/starlette/endpoints.py +0 -130
- prefect/_vendor/starlette/exceptions.py +0 -60
- prefect/_vendor/starlette/formparsers.py +0 -276
- prefect/_vendor/starlette/middleware/__init__.py +0 -17
- prefect/_vendor/starlette/middleware/authentication.py +0 -52
- prefect/_vendor/starlette/middleware/base.py +0 -220
- prefect/_vendor/starlette/middleware/cors.py +0 -176
- prefect/_vendor/starlette/middleware/errors.py +0 -265
- prefect/_vendor/starlette/middleware/exceptions.py +0 -74
- prefect/_vendor/starlette/middleware/gzip.py +0 -113
- prefect/_vendor/starlette/middleware/httpsredirect.py +0 -19
- prefect/_vendor/starlette/middleware/sessions.py +0 -82
- prefect/_vendor/starlette/middleware/trustedhost.py +0 -64
- prefect/_vendor/starlette/middleware/wsgi.py +0 -147
- prefect/_vendor/starlette/py.typed +0 -0
- prefect/_vendor/starlette/requests.py +0 -328
- prefect/_vendor/starlette/responses.py +0 -347
- prefect/_vendor/starlette/routing.py +0 -933
- prefect/_vendor/starlette/schemas.py +0 -154
- prefect/_vendor/starlette/staticfiles.py +0 -248
- prefect/_vendor/starlette/status.py +0 -199
- prefect/_vendor/starlette/templating.py +0 -231
- prefect/_vendor/starlette/testclient.py +0 -804
- prefect/_vendor/starlette/types.py +0 -30
- prefect/_vendor/starlette/websockets.py +0 -193
- prefect/blocks/kubernetes.py +0 -119
- prefect/deprecated/__init__.py +0 -0
- prefect/deprecated/data_documents.py +0 -350
- prefect/deprecated/packaging/__init__.py +0 -12
- prefect/deprecated/packaging/base.py +0 -96
- prefect/deprecated/packaging/docker.py +0 -146
- prefect/deprecated/packaging/file.py +0 -92
- prefect/deprecated/packaging/orion.py +0 -80
- prefect/deprecated/packaging/serializers.py +0 -171
- prefect/events/instrument.py +0 -135
- prefect/infrastructure/container.py +0 -824
- prefect/infrastructure/kubernetes.py +0 -920
- prefect/infrastructure/process.py +0 -289
- prefect/manifests.py +0 -20
- prefect/new_flow_engine.py +0 -449
- prefect/new_task_engine.py +0 -423
- prefect/pydantic/__init__.py +0 -76
- prefect/pydantic/main.py +0 -39
- prefect/software/__init__.py +0 -2
- prefect/software/base.py +0 -50
- prefect/software/conda.py +0 -199
- prefect/software/pip.py +0 -122
- prefect/software/python.py +0 -52
- prefect/task_server.py +0 -322
- prefect_client-2.20.4.dist-info/RECORD +0 -294
- /prefect/{_internal/pydantic/utilities → client/types}/__init__.py +0 -0
- /prefect/{_vendor → concurrency/v1}/__init__.py +0 -0
- {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/LICENSE +0 -0
- {prefect_client-2.20.4.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
|
-
|
34
|
-
from prefect.
|
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
|
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
|
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.
|
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
|
-
|
87
|
+
NUM_CHARS_DYNAMIC_KEY = 8
|
77
88
|
|
78
|
-
|
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:
|
195
|
-
should be persisted to result storage. Defaults to `None`, which
|
196
|
-
that
|
197
|
-
|
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"@
|
274
|
-
"
|
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"@
|
284
|
-
"
|
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
|
-
|
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.
|
355
|
-
self.
|
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
|
-
|
374
|
-
|
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]] =
|
377
|
-
cache_expiration: datetime.timedelta = None,
|
378
|
-
retries:
|
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:
|
386
|
-
persist_result:
|
387
|
-
result_storage:
|
388
|
-
result_serializer:
|
389
|
-
result_storage_key:
|
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:
|
393
|
-
refresh_cache:
|
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.
|
527
|
-
on_failure=on_failure or self.
|
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[
|
535
|
-
|
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
|
-
|
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
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
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
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
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
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
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
|
-
|
591
|
-
|
887
|
+
# check wait for dependencies
|
888
|
+
if wait_for:
|
889
|
+
task_inputs["wait_for"] = collect_task_run_inputs_sync(wait_for)
|
592
890
|
|
593
|
-
|
594
|
-
|
595
|
-
|
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
|
-
|
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
|
-
|
620
|
-
|
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
|
-
|
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.
|
663
|
-
|
664
|
-
|
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
|
-
|
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
|
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[
|
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
|
-
) ->
|
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
|
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
|
-
) ->
|
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
|
-
)
|
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.
|
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.
|
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
|
-
|
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
|
-
) ->
|
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
|
-
) ->
|
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
|
-
) ->
|
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
|
-
) ->
|
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
|
-
)
|
1210
|
+
):
|
1048
1211
|
"""
|
1049
1212
|
Submit a mapped run of the task to a worker.
|
1050
1213
|
|
1051
|
-
Must be called within a flow
|
1052
|
-
|
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.
|
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
|
-
>>>
|
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
|
1110
|
-
>>> print(
|
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.
|
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
|
-
|
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
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
|
1179
|
-
|
1180
|
-
|
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
|
-
|
1448
|
+
deferred=True,
|
1183
1449
|
wait_for=wait_for,
|
1184
|
-
|
1185
|
-
task_runner=None,
|
1186
|
-
autonomous=True,
|
1450
|
+
extra_task_inputs=dependencies,
|
1187
1451
|
)
|
1188
|
-
|
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
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1198
|
-
|
1199
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
1244
|
-
|
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
|
-
|
1278
|
-
|
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:
|
1335
|
-
should be persisted to result storage. Defaults to `None`, which
|
1336
|
-
that
|
1337
|
-
|
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,
|