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.
- 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 +423 -164
- 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 +667 -440
- 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 -2466
- 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 +124 -51
- 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 +138 -48
- 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.2.dist-info → prefect_client-3.0.0.dist-info}/METADATA +30 -26
- prefect_client-3.0.0.dist-info/RECORD +201 -0
- {prefect_client-2.20.2.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.2.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.2.dist-info → prefect_client-3.0.0.dist-info}/LICENSE +0 -0
- {prefect_client-2.20.2.dist-info → prefect_client-3.0.0.dist-info}/top_level.txt +0 -0
prefect/task_runners.py
CHANGED
@@ -1,365 +1,461 @@
|
|
1
|
-
"""
|
2
|
-
Interface and implementations of various task runners.
|
3
|
-
|
4
|
-
[Task Runners](/concepts/task-runners/) in Prefect are responsible for managing the execution of Prefect task runs. Generally speaking, users are not expected to interact with task runners outside of configuring and initializing them for a flow.
|
5
|
-
|
6
|
-
Example:
|
7
|
-
```
|
8
|
-
>>> from prefect import flow, task
|
9
|
-
>>> from prefect.task_runners import SequentialTaskRunner
|
10
|
-
>>> from typing import List
|
11
|
-
>>>
|
12
|
-
>>> @task
|
13
|
-
>>> def say_hello(name):
|
14
|
-
... print(f"hello {name}")
|
15
|
-
>>>
|
16
|
-
>>> @task
|
17
|
-
>>> def say_goodbye(name):
|
18
|
-
... print(f"goodbye {name}")
|
19
|
-
>>>
|
20
|
-
>>> @flow(task_runner=SequentialTaskRunner())
|
21
|
-
>>> def greetings(names: List[str]):
|
22
|
-
... for name in names:
|
23
|
-
... say_hello(name)
|
24
|
-
... say_goodbye(name)
|
25
|
-
>>>
|
26
|
-
>>> greetings(["arthur", "trillian", "ford", "marvin"])
|
27
|
-
hello arthur
|
28
|
-
goodbye arthur
|
29
|
-
hello trillian
|
30
|
-
goodbye trillian
|
31
|
-
hello ford
|
32
|
-
goodbye ford
|
33
|
-
hello marvin
|
34
|
-
goodbye marvin
|
35
|
-
```
|
36
|
-
|
37
|
-
Switching to a `DaskTaskRunner`:
|
38
|
-
```
|
39
|
-
>>> from prefect_dask.task_runners import DaskTaskRunner
|
40
|
-
>>> flow.task_runner = DaskTaskRunner()
|
41
|
-
>>> greetings(["arthur", "trillian", "ford", "marvin"])
|
42
|
-
hello arthur
|
43
|
-
goodbye arthur
|
44
|
-
hello trillian
|
45
|
-
hello ford
|
46
|
-
goodbye marvin
|
47
|
-
hello marvin
|
48
|
-
goodbye ford
|
49
|
-
goodbye trillian
|
50
|
-
```
|
51
|
-
|
52
|
-
For usage details, see the [Task Runners](/concepts/task-runners/) documentation.
|
53
|
-
"""
|
54
1
|
import abc
|
55
|
-
|
2
|
+
import asyncio
|
3
|
+
import sys
|
4
|
+
import threading
|
5
|
+
import uuid
|
6
|
+
from concurrent.futures import ThreadPoolExecutor
|
7
|
+
from contextvars import copy_context
|
56
8
|
from typing import (
|
57
9
|
TYPE_CHECKING,
|
58
10
|
Any,
|
59
|
-
|
60
|
-
Awaitable,
|
61
|
-
Callable,
|
11
|
+
Coroutine,
|
62
12
|
Dict,
|
13
|
+
Generic,
|
14
|
+
Iterable,
|
15
|
+
List,
|
63
16
|
Optional,
|
64
17
|
Set,
|
65
|
-
|
18
|
+
overload,
|
66
19
|
)
|
67
|
-
from uuid import UUID
|
68
20
|
|
69
|
-
import
|
21
|
+
from typing_extensions import ParamSpec, Self, TypeVar
|
70
22
|
|
71
|
-
from prefect.
|
72
|
-
from prefect.
|
73
|
-
from prefect.
|
74
|
-
|
75
|
-
|
23
|
+
from prefect.client.schemas.objects import TaskRunInput
|
24
|
+
from prefect.exceptions import MappingLengthMismatch, MappingMissingIterable
|
25
|
+
from prefect.futures import (
|
26
|
+
PrefectConcurrentFuture,
|
27
|
+
PrefectDistributedFuture,
|
28
|
+
PrefectFuture,
|
29
|
+
PrefectFutureList,
|
30
|
+
)
|
31
|
+
from prefect.logging.loggers import get_logger, get_run_logger
|
32
|
+
from prefect.utilities.annotations import allow_failure, quote, unmapped
|
33
|
+
from prefect.utilities.callables import (
|
34
|
+
collapse_variadic_parameters,
|
35
|
+
explode_variadic_parameter,
|
36
|
+
get_parameter_defaults,
|
37
|
+
)
|
38
|
+
from prefect.utilities.collections import isiterable
|
76
39
|
|
77
40
|
if TYPE_CHECKING:
|
78
|
-
import
|
41
|
+
from prefect.tasks import Task
|
79
42
|
|
80
|
-
|
81
|
-
T = TypeVar("T"
|
43
|
+
P = ParamSpec("P")
|
44
|
+
T = TypeVar("T")
|
82
45
|
R = TypeVar("R")
|
46
|
+
F = TypeVar("F", bound=PrefectFuture, default=PrefectConcurrentFuture)
|
83
47
|
|
84
48
|
|
85
|
-
class
|
86
|
-
|
87
|
-
|
88
|
-
PARALLEL = AutoEnum.auto()
|
89
|
-
|
49
|
+
class TaskRunner(abc.ABC, Generic[F]):
|
50
|
+
"""
|
51
|
+
Abstract base class for task runners.
|
90
52
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
TaskConcurrencyType.PARALLEL: "in parallel",
|
95
|
-
}
|
53
|
+
A task runner is responsible for submitting tasks to the task run engine running
|
54
|
+
in an execution environment. Submitted tasks are non-blocking and return a future
|
55
|
+
object that can be used to wait for the task to complete and retrieve the result.
|
96
56
|
|
57
|
+
Task runners are context managers and should be used in a `with` block to ensure
|
58
|
+
proper cleanup of resources.
|
59
|
+
"""
|
97
60
|
|
98
|
-
|
99
|
-
def __init__(self) -> None:
|
61
|
+
def __init__(self):
|
100
62
|
self.logger = get_logger(f"task_runner.{self.name}")
|
101
|
-
self._started
|
102
|
-
|
103
|
-
@property
|
104
|
-
@abc.abstractmethod
|
105
|
-
def concurrency_type(self) -> TaskConcurrencyType:
|
106
|
-
pass # noqa
|
63
|
+
self._started = False
|
107
64
|
|
108
65
|
@property
|
109
66
|
def name(self):
|
67
|
+
"""The name of this task runner"""
|
110
68
|
return type(self).__name__.lower().replace("taskrunner", "")
|
111
69
|
|
112
|
-
|
113
|
-
|
114
|
-
Return a new task runner
|
115
|
-
|
116
|
-
# The base class returns `NotImplemented` to indicate that this is not yet
|
117
|
-
# implemented by a given task runner.
|
118
|
-
return NotImplemented
|
119
|
-
|
120
|
-
def __eq__(self, other: object) -> bool:
|
121
|
-
"""
|
122
|
-
Returns true if the task runners use the same options.
|
123
|
-
"""
|
124
|
-
if type(other) == type(self) and (
|
125
|
-
# Compare public attributes for naive equality check
|
126
|
-
# Subclasses should implement this method with a check init option equality
|
127
|
-
{k: v for k, v in self.__dict__.items() if not k.startswith("_")}
|
128
|
-
== {k: v for k, v in other.__dict__.items() if not k.startswith("_")}
|
129
|
-
):
|
130
|
-
return True
|
131
|
-
else:
|
132
|
-
return NotImplemented
|
70
|
+
@abc.abstractmethod
|
71
|
+
def duplicate(self) -> Self:
|
72
|
+
"""Return a new instance of this task runner with the same configuration."""
|
73
|
+
...
|
133
74
|
|
134
75
|
@abc.abstractmethod
|
135
|
-
|
76
|
+
def submit(
|
136
77
|
self,
|
137
|
-
|
138
|
-
|
139
|
-
|
78
|
+
task: "Task",
|
79
|
+
parameters: Dict[str, Any],
|
80
|
+
wait_for: Optional[Iterable[PrefectFuture]] = None,
|
81
|
+
dependencies: Optional[Dict[str, Set[TaskRunInput]]] = None,
|
82
|
+
) -> F:
|
140
83
|
"""
|
141
|
-
Submit a
|
142
|
-
get the call result.
|
84
|
+
Submit a task to the task run engine.
|
143
85
|
|
144
86
|
Args:
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
call: The function to be executed
|
149
|
-
run_kwargs: A dict of keyword arguments to pass to `call`
|
87
|
+
task: The task to submit.
|
88
|
+
parameters: The parameters to use when running the task.
|
89
|
+
wait_for: A list of futures that the task depends on.
|
150
90
|
|
151
91
|
Returns:
|
152
|
-
A future
|
92
|
+
A future object that can be used to wait for the task to complete and
|
93
|
+
retrieve the result.
|
153
94
|
"""
|
154
|
-
|
95
|
+
...
|
155
96
|
|
156
|
-
|
157
|
-
|
97
|
+
def map(
|
98
|
+
self,
|
99
|
+
task: "Task",
|
100
|
+
parameters: Dict[str, Any],
|
101
|
+
wait_for: Optional[Iterable[PrefectFuture]] = None,
|
102
|
+
) -> PrefectFutureList[F]:
|
158
103
|
"""
|
159
|
-
|
160
|
-
If it is not finished after the timeout expires, `None` should be returned.
|
104
|
+
Submit multiple tasks to the task run engine.
|
161
105
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
106
|
+
Args:
|
107
|
+
task: The task to submit.
|
108
|
+
parameters: The parameters to use when running the task.
|
109
|
+
wait_for: A list of futures that the task depends on.
|
166
110
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
) -> AsyncIterator[T]:
|
111
|
+
Returns:
|
112
|
+
An iterable of future objects that can be used to wait for the tasks to
|
113
|
+
complete and retrieve the results.
|
171
114
|
"""
|
172
|
-
|
115
|
+
if not self._started:
|
116
|
+
raise RuntimeError(
|
117
|
+
"The task runner must be started before submitting work."
|
118
|
+
)
|
173
119
|
|
174
|
-
|
120
|
+
from prefect.utilities.engine import (
|
121
|
+
collect_task_run_inputs_sync,
|
122
|
+
resolve_inputs_sync,
|
123
|
+
)
|
175
124
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
125
|
+
# We need to resolve some futures to map over their data, collect the upstream
|
126
|
+
# links beforehand to retain relationship tracking.
|
127
|
+
task_inputs = {
|
128
|
+
k: collect_task_run_inputs_sync(v, max_depth=0)
|
129
|
+
for k, v in parameters.items()
|
130
|
+
}
|
131
|
+
|
132
|
+
# Resolve the top-level parameters in order to get mappable data of a known length.
|
133
|
+
# Nested parameters will be resolved in each mapped child where their relationships
|
134
|
+
# will also be tracked.
|
135
|
+
parameters = resolve_inputs_sync(parameters, max_depth=0)
|
136
|
+
|
137
|
+
# Ensure that any parameters in kwargs are expanded before this check
|
138
|
+
parameters = explode_variadic_parameter(task.fn, parameters)
|
139
|
+
|
140
|
+
iterable_parameters = {}
|
141
|
+
static_parameters = {}
|
142
|
+
annotated_parameters = {}
|
143
|
+
for key, val in parameters.items():
|
144
|
+
if isinstance(val, (allow_failure, quote)):
|
145
|
+
# Unwrap annotated parameters to determine if they are iterable
|
146
|
+
annotated_parameters[key] = val
|
147
|
+
val = val.unwrap()
|
148
|
+
|
149
|
+
if isinstance(val, unmapped):
|
150
|
+
static_parameters[key] = val.value
|
151
|
+
elif isiterable(val):
|
152
|
+
iterable_parameters[key] = list(val)
|
153
|
+
else:
|
154
|
+
static_parameters[key] = val
|
155
|
+
|
156
|
+
if not len(iterable_parameters):
|
157
|
+
raise MappingMissingIterable(
|
158
|
+
"No iterable parameters were received. Parameters for map must "
|
159
|
+
f"include at least one iterable. Parameters: {parameters}"
|
160
|
+
)
|
195
161
|
|
196
|
-
|
197
|
-
|
198
|
-
|
162
|
+
iterable_parameter_lengths = {
|
163
|
+
key: len(val) for key, val in iterable_parameters.items()
|
164
|
+
}
|
165
|
+
lengths = set(iterable_parameter_lengths.values())
|
166
|
+
if len(lengths) > 1:
|
167
|
+
raise MappingLengthMismatch(
|
168
|
+
"Received iterable parameters with different lengths. Parameters for map"
|
169
|
+
f" must all be the same length. Got lengths: {iterable_parameter_lengths}"
|
170
|
+
)
|
199
171
|
|
200
|
-
|
201
|
-
return type(self).__name__
|
172
|
+
map_length = list(lengths)[0]
|
202
173
|
|
174
|
+
futures: List[PrefectFuture] = []
|
175
|
+
for i in range(map_length):
|
176
|
+
call_parameters = {
|
177
|
+
key: value[i] for key, value in iterable_parameters.items()
|
178
|
+
}
|
179
|
+
call_parameters.update(
|
180
|
+
{key: value for key, value in static_parameters.items()}
|
181
|
+
)
|
203
182
|
|
204
|
-
|
205
|
-
|
206
|
-
|
183
|
+
# Add default values for parameters; these are skipped earlier since they should
|
184
|
+
# not be mapped over
|
185
|
+
for key, value in get_parameter_defaults(task.fn).items():
|
186
|
+
call_parameters.setdefault(key, value)
|
187
|
+
|
188
|
+
# Re-apply annotations to each key again
|
189
|
+
for key, annotation in annotated_parameters.items():
|
190
|
+
call_parameters[key] = annotation.rewrap(call_parameters[key])
|
191
|
+
|
192
|
+
# Collapse any previously exploded kwargs
|
193
|
+
call_parameters = collapse_variadic_parameters(task.fn, call_parameters)
|
194
|
+
|
195
|
+
futures.append(
|
196
|
+
self.submit(
|
197
|
+
task=task,
|
198
|
+
parameters=call_parameters,
|
199
|
+
wait_for=wait_for,
|
200
|
+
dependencies=task_inputs,
|
201
|
+
)
|
202
|
+
)
|
207
203
|
|
208
|
-
|
209
|
-
If writing async tasks, this runner will execute tasks sequentially unless grouped
|
210
|
-
using `anyio.create_task_group` or `asyncio.gather`.
|
211
|
-
"""
|
204
|
+
return PrefectFutureList(futures)
|
212
205
|
|
213
|
-
def
|
214
|
-
|
215
|
-
|
206
|
+
def __enter__(self):
|
207
|
+
if self._started:
|
208
|
+
raise RuntimeError("This task runner is already started")
|
216
209
|
|
217
|
-
|
218
|
-
|
219
|
-
return
|
210
|
+
self.logger.debug("Starting task runner")
|
211
|
+
self._started = True
|
212
|
+
return self
|
220
213
|
|
221
|
-
def
|
222
|
-
|
214
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
215
|
+
self.logger.debug("Stopping task runner")
|
216
|
+
self._started = False
|
223
217
|
|
224
|
-
async def submit(
|
225
|
-
self,
|
226
|
-
key: UUID,
|
227
|
-
call: Callable[..., Awaitable[State[R]]],
|
228
|
-
) -> None:
|
229
|
-
# Run the function immediately and store the result in memory
|
230
|
-
try:
|
231
|
-
result = await call()
|
232
|
-
except BaseException as exc:
|
233
|
-
result = await exception_to_crashed_state(exc)
|
234
218
|
|
235
|
-
|
219
|
+
class ThreadPoolTaskRunner(TaskRunner[PrefectConcurrentFuture]):
|
220
|
+
def __init__(self, max_workers: Optional[int] = None):
|
221
|
+
super().__init__()
|
222
|
+
self._executor: Optional[ThreadPoolExecutor] = None
|
223
|
+
self._max_workers = sys.maxsize if max_workers is None else max_workers
|
224
|
+
self._cancel_events: Dict[uuid.UUID, threading.Event] = {}
|
236
225
|
|
237
|
-
|
238
|
-
return self.
|
226
|
+
def duplicate(self) -> "ThreadPoolTaskRunner":
|
227
|
+
return type(self)(max_workers=self._max_workers)
|
239
228
|
|
229
|
+
@overload
|
230
|
+
def submit(
|
231
|
+
self,
|
232
|
+
task: "Task[P, Coroutine[Any, Any, R]]",
|
233
|
+
parameters: Dict[str, Any],
|
234
|
+
wait_for: Optional[Iterable[PrefectFuture]] = None,
|
235
|
+
dependencies: Optional[Dict[str, Set[TaskRunInput]]] = None,
|
236
|
+
) -> PrefectConcurrentFuture[R]:
|
237
|
+
...
|
238
|
+
|
239
|
+
@overload
|
240
|
+
def submit(
|
241
|
+
self,
|
242
|
+
task: "Task[Any, R]",
|
243
|
+
parameters: Dict[str, Any],
|
244
|
+
wait_for: Optional[Iterable[PrefectFuture]] = None,
|
245
|
+
dependencies: Optional[Dict[str, Set[TaskRunInput]]] = None,
|
246
|
+
) -> PrefectConcurrentFuture[R]:
|
247
|
+
...
|
248
|
+
|
249
|
+
def submit(
|
250
|
+
self,
|
251
|
+
task: "Task",
|
252
|
+
parameters: Dict[str, Any],
|
253
|
+
wait_for: Optional[Iterable[PrefectFuture]] = None,
|
254
|
+
dependencies: Optional[Dict[str, Set[TaskRunInput]]] = None,
|
255
|
+
):
|
256
|
+
"""
|
257
|
+
Submit a task to the task run engine running in a separate thread.
|
240
258
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
Example:
|
247
|
-
```
|
248
|
-
Using a thread for concurrency:
|
249
|
-
>>> from prefect import flow
|
250
|
-
>>> from prefect.task_runners import ConcurrentTaskRunner
|
251
|
-
>>> @flow(task_runner=ConcurrentTaskRunner)
|
252
|
-
>>> def my_flow():
|
253
|
-
>>> ...
|
254
|
-
```
|
255
|
-
"""
|
259
|
+
Args:
|
260
|
+
task: The task to submit.
|
261
|
+
parameters: The parameters to use when running the task.
|
262
|
+
wait_for: A list of futures that the task depends on.
|
256
263
|
|
257
|
-
|
258
|
-
|
264
|
+
Returns:
|
265
|
+
A future object that can be used to wait for the task to complete and
|
266
|
+
retrieve the result.
|
267
|
+
"""
|
268
|
+
if not self._started or self._executor is None:
|
269
|
+
raise RuntimeError("Task runner is not started")
|
259
270
|
|
260
|
-
|
261
|
-
|
262
|
-
self._result_events: Dict[UUID, Event] = {}
|
263
|
-
self._results: Dict[UUID, Any] = {}
|
264
|
-
self._keys: Set[UUID] = set()
|
271
|
+
from prefect.context import FlowRunContext
|
272
|
+
from prefect.task_engine import run_task_async, run_task_sync
|
265
273
|
|
266
|
-
|
274
|
+
task_run_id = uuid.uuid4()
|
275
|
+
cancel_event = threading.Event()
|
276
|
+
self._cancel_events[task_run_id] = cancel_event
|
277
|
+
context = copy_context()
|
267
278
|
|
268
|
-
|
269
|
-
|
270
|
-
|
279
|
+
flow_run_ctx = FlowRunContext.get()
|
280
|
+
if flow_run_ctx:
|
281
|
+
get_run_logger(flow_run_ctx).info(
|
282
|
+
f"Submitting task {task.name} to thread pool executor..."
|
283
|
+
)
|
284
|
+
else:
|
285
|
+
self.logger.info(f"Submitting task {task.name} to thread pool executor...")
|
286
|
+
|
287
|
+
submit_kwargs = dict(
|
288
|
+
task=task,
|
289
|
+
task_run_id=task_run_id,
|
290
|
+
parameters=parameters,
|
291
|
+
wait_for=wait_for,
|
292
|
+
return_type="state",
|
293
|
+
dependencies=dependencies,
|
294
|
+
context=dict(cancel_event=cancel_event),
|
295
|
+
)
|
271
296
|
|
272
|
-
|
273
|
-
|
297
|
+
if task.isasync:
|
298
|
+
# TODO: Explore possibly using a long-lived thread with an event loop
|
299
|
+
# for better performance
|
300
|
+
future = self._executor.submit(
|
301
|
+
context.run,
|
302
|
+
asyncio.run,
|
303
|
+
run_task_async(**submit_kwargs),
|
304
|
+
)
|
305
|
+
else:
|
306
|
+
future = self._executor.submit(
|
307
|
+
context.run,
|
308
|
+
run_task_sync,
|
309
|
+
**submit_kwargs,
|
310
|
+
)
|
311
|
+
prefect_future = PrefectConcurrentFuture(
|
312
|
+
task_run_id=task_run_id, wrapped_future=future
|
313
|
+
)
|
314
|
+
return prefect_future
|
274
315
|
|
275
|
-
|
316
|
+
@overload
|
317
|
+
def map(
|
276
318
|
self,
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
319
|
+
task: "Task[P, Coroutine[Any, Any, R]]",
|
320
|
+
parameters: Dict[str, Any],
|
321
|
+
wait_for: Optional[Iterable[PrefectFuture]] = None,
|
322
|
+
) -> PrefectFutureList[PrefectConcurrentFuture[R]]:
|
323
|
+
...
|
324
|
+
|
325
|
+
@overload
|
326
|
+
def map(
|
327
|
+
self,
|
328
|
+
task: "Task[Any, R]",
|
329
|
+
parameters: Dict[str, Any],
|
330
|
+
wait_for: Optional[Iterable[PrefectFuture]] = None,
|
331
|
+
) -> PrefectFutureList[PrefectConcurrentFuture[R]]:
|
332
|
+
...
|
284
333
|
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
334
|
+
def map(
|
335
|
+
self,
|
336
|
+
task: "Task",
|
337
|
+
parameters: Dict[str, Any],
|
338
|
+
wait_for: Optional[Iterable[PrefectFuture]] = None,
|
339
|
+
):
|
340
|
+
return super().map(task, parameters, wait_for)
|
290
341
|
|
291
|
-
|
292
|
-
self.
|
342
|
+
def cancel_all(self):
|
343
|
+
for event in self._cancel_events.values():
|
344
|
+
event.set()
|
345
|
+
self.logger.debug("Set cancel event")
|
293
346
|
|
294
|
-
|
295
|
-
|
347
|
+
if self._executor is not None:
|
348
|
+
self._executor.shutdown(cancel_futures=True)
|
349
|
+
self._executor = None
|
296
350
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
) -> Optional[State]:
|
302
|
-
if not self._task_group:
|
303
|
-
raise RuntimeError(
|
304
|
-
"The concurrent task runner cannot be used to wait for work after "
|
305
|
-
"serialization."
|
306
|
-
)
|
351
|
+
def __enter__(self):
|
352
|
+
super().__enter__()
|
353
|
+
self._executor = ThreadPoolExecutor(max_workers=self._max_workers)
|
354
|
+
return self
|
307
355
|
|
308
|
-
|
356
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
357
|
+
self.cancel_all()
|
358
|
+
if self._executor is not None:
|
359
|
+
self._executor.shutdown(cancel_futures=True)
|
360
|
+
self._executor = None
|
361
|
+
super().__exit__(exc_type, exc_value, traceback)
|
309
362
|
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
Simple utility to store the orchestration result in memory on completion
|
363
|
+
def __eq__(self, value: object) -> bool:
|
364
|
+
if not isinstance(value, ThreadPoolTaskRunner):
|
365
|
+
return False
|
366
|
+
return self._max_workers == value._max_workers
|
315
367
|
|
316
|
-
Since this run is occurring on the main thread, we capture exceptions to prevent
|
317
|
-
task crashes from crashing the flow run.
|
318
|
-
"""
|
319
|
-
try:
|
320
|
-
result = await call()
|
321
|
-
except BaseException as exc:
|
322
|
-
result = await exception_to_crashed_state(exc)
|
323
368
|
|
324
|
-
|
325
|
-
|
369
|
+
# Here, we alias ConcurrentTaskRunner to ThreadPoolTaskRunner for backwards compatibility
|
370
|
+
ConcurrentTaskRunner = ThreadPoolTaskRunner
|
326
371
|
|
327
|
-
async def _get_run_result(
|
328
|
-
self, key: UUID, timeout: float = None
|
329
|
-
) -> Optional[State]:
|
330
|
-
"""
|
331
|
-
Block until the run result has been populated.
|
332
|
-
"""
|
333
|
-
result = None # retval on timeout
|
334
372
|
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
with anyio.move_on_after(timeout):
|
339
|
-
await self._result_events[key].wait()
|
340
|
-
result = self._results[key]
|
373
|
+
class PrefectTaskRunner(TaskRunner[PrefectDistributedFuture]):
|
374
|
+
def __init__(self):
|
375
|
+
super().__init__()
|
341
376
|
|
342
|
-
|
377
|
+
def duplicate(self) -> "PrefectTaskRunner":
|
378
|
+
return type(self)()
|
343
379
|
|
344
|
-
|
380
|
+
@overload
|
381
|
+
def submit(
|
382
|
+
self,
|
383
|
+
task: "Task[P, Coroutine[Any, Any, R]]",
|
384
|
+
parameters: Dict[str, Any],
|
385
|
+
wait_for: Optional[Iterable[PrefectFuture]] = None,
|
386
|
+
dependencies: Optional[Dict[str, Set[TaskRunInput]]] = None,
|
387
|
+
) -> PrefectDistributedFuture[R]:
|
388
|
+
...
|
389
|
+
|
390
|
+
@overload
|
391
|
+
def submit(
|
392
|
+
self,
|
393
|
+
task: "Task[Any, R]",
|
394
|
+
parameters: Dict[str, Any],
|
395
|
+
wait_for: Optional[Iterable[PrefectFuture]] = None,
|
396
|
+
dependencies: Optional[Dict[str, Set[TaskRunInput]]] = None,
|
397
|
+
) -> PrefectDistributedFuture[R]:
|
398
|
+
...
|
399
|
+
|
400
|
+
def submit(
|
401
|
+
self,
|
402
|
+
task: "Task",
|
403
|
+
parameters: Dict[str, Any],
|
404
|
+
wait_for: Optional[Iterable[PrefectFuture]] = None,
|
405
|
+
dependencies: Optional[Dict[str, Set[TaskRunInput]]] = None,
|
406
|
+
):
|
345
407
|
"""
|
346
|
-
|
408
|
+
Submit a task to the task run engine running in a separate thread.
|
409
|
+
|
410
|
+
Args:
|
411
|
+
task: The task to submit.
|
412
|
+
parameters: The parameters to use when running the task.
|
413
|
+
wait_for: A list of futures that the task depends on.
|
414
|
+
|
415
|
+
Returns:
|
416
|
+
A future object that can be used to wait for the task to complete and
|
417
|
+
retrieve the result.
|
347
418
|
"""
|
348
|
-
|
349
|
-
|
419
|
+
if not self._started:
|
420
|
+
raise RuntimeError("Task runner is not started")
|
421
|
+
from prefect.context import FlowRunContext
|
422
|
+
|
423
|
+
flow_run_ctx = FlowRunContext.get()
|
424
|
+
if flow_run_ctx:
|
425
|
+
get_run_logger(flow_run_ctx).info(
|
426
|
+
f"Submitting task {task.name} to for execution by a Prefect task worker..."
|
427
|
+
)
|
428
|
+
else:
|
429
|
+
self.logger.info(
|
430
|
+
f"Submitting task {task.name} to for execution by a Prefect task worker..."
|
431
|
+
)
|
432
|
+
|
433
|
+
return task.apply_async(
|
434
|
+
kwargs=parameters, wait_for=wait_for, dependencies=dependencies
|
350
435
|
)
|
351
436
|
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
""
|
356
|
-
|
357
|
-
|
358
|
-
|
437
|
+
@overload
|
438
|
+
def map(
|
439
|
+
self,
|
440
|
+
task: "Task[P, Coroutine[Any, Any, R]]",
|
441
|
+
parameters: Dict[str, Any],
|
442
|
+
wait_for: Optional[Iterable[PrefectFuture]] = None,
|
443
|
+
) -> PrefectFutureList[PrefectDistributedFuture[R]]:
|
444
|
+
...
|
445
|
+
|
446
|
+
@overload
|
447
|
+
def map(
|
448
|
+
self,
|
449
|
+
task: "Task[Any, R]",
|
450
|
+
parameters: Dict[str, Any],
|
451
|
+
wait_for: Optional[Iterable[PrefectFuture]] = None,
|
452
|
+
) -> PrefectFutureList[PrefectDistributedFuture[R]]:
|
453
|
+
...
|
359
454
|
|
360
|
-
def
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
455
|
+
def map(
|
456
|
+
self,
|
457
|
+
task: "Task",
|
458
|
+
parameters: Dict[str, Any],
|
459
|
+
wait_for: Optional[Iterable[PrefectFuture]] = None,
|
460
|
+
):
|
461
|
+
return super().map(task, parameters, wait_for)
|