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/workers/block.py
CHANGED
@@ -1,227 +1,6 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
"""
|
2
|
+
2024-06-27: This surfaces an actionable error message for moved or removed objects in Prefect 3.0 upgrade.
|
3
|
+
"""
|
4
|
+
from prefect._internal.compatibility.migration import getattr_migration
|
3
5
|
|
4
|
-
|
5
|
-
import anyio.abc
|
6
|
-
|
7
|
-
from prefect._internal.compatibility.deprecated import deprecated_class
|
8
|
-
from prefect._internal.pydantic import HAS_PYDANTIC_V2
|
9
|
-
from prefect._internal.schemas.validators import validate_block_is_infrastructure
|
10
|
-
from prefect.blocks.core import Block
|
11
|
-
from prefect.client.schemas.objects import BlockDocument
|
12
|
-
from prefect.utilities.collections import get_from_dict
|
13
|
-
from prefect.workers.base import BaseWorker, BaseWorkerResult
|
14
|
-
|
15
|
-
if HAS_PYDANTIC_V2:
|
16
|
-
from pydantic.v1 import BaseModel, Field, PrivateAttr, validator
|
17
|
-
else:
|
18
|
-
from pydantic import BaseModel, Field, PrivateAttr, validator
|
19
|
-
|
20
|
-
from prefect.client.orchestration import PrefectClient
|
21
|
-
from prefect.client.utilities import inject_client
|
22
|
-
from prefect.events import RelatedResource
|
23
|
-
from prefect.events.related import object_as_related_resource, tags_as_related_resources
|
24
|
-
from prefect.utilities.templating import apply_values
|
25
|
-
|
26
|
-
if TYPE_CHECKING:
|
27
|
-
from prefect.client.schemas.objects import Flow, FlowRun
|
28
|
-
from prefect.client.schemas.responses import DeploymentResponse
|
29
|
-
|
30
|
-
|
31
|
-
@deprecated_class(
|
32
|
-
start_date="Jun 2024",
|
33
|
-
help="Refer to the upgrade guide for more information: https://docs.prefect.io/latest/guides/upgrade-guide-agents-to-workers/",
|
34
|
-
)
|
35
|
-
class BlockWorkerJobConfiguration(BaseModel):
|
36
|
-
block: Block = Field(
|
37
|
-
default=..., description="The infrastructure block to use for job creation."
|
38
|
-
)
|
39
|
-
|
40
|
-
@validator("block")
|
41
|
-
def _validate_infrastructure_block(cls, v):
|
42
|
-
return validate_block_is_infrastructure(v)
|
43
|
-
|
44
|
-
_related_objects: Dict[str, Any] = PrivateAttr(default_factory=dict)
|
45
|
-
|
46
|
-
@property
|
47
|
-
def is_using_a_runner(self):
|
48
|
-
return (
|
49
|
-
self.block.command is not None
|
50
|
-
and "prefect flow-run execute" in shlex.join(self.block.command)
|
51
|
-
)
|
52
|
-
|
53
|
-
@staticmethod
|
54
|
-
def _get_base_config_defaults(variables: dict) -> dict:
|
55
|
-
"""Get default values from base config for all variables that have them."""
|
56
|
-
defaults = dict()
|
57
|
-
for variable_name, attrs in variables.items():
|
58
|
-
if "default" in attrs:
|
59
|
-
defaults[variable_name] = attrs["default"]
|
60
|
-
|
61
|
-
return defaults
|
62
|
-
|
63
|
-
@classmethod
|
64
|
-
@inject_client
|
65
|
-
async def from_template_and_values(
|
66
|
-
cls, base_job_template: dict, values: dict, client: "PrefectClient" = None
|
67
|
-
):
|
68
|
-
"""Creates a valid worker configuration object from the provided base
|
69
|
-
configuration and overrides.
|
70
|
-
|
71
|
-
Important: this method expects that the base_job_template was already
|
72
|
-
validated server-side.
|
73
|
-
"""
|
74
|
-
job_config: Dict[str, Any] = base_job_template["job_configuration"]
|
75
|
-
variables_schema = base_job_template["variables"]
|
76
|
-
variables = cls._get_base_config_defaults(
|
77
|
-
variables_schema.get("properties", {})
|
78
|
-
)
|
79
|
-
variables.update(values)
|
80
|
-
|
81
|
-
populated_configuration = apply_values(template=job_config, values=variables)
|
82
|
-
|
83
|
-
block_document_id = get_from_dict(
|
84
|
-
populated_configuration, "block.$ref.block_document_id"
|
85
|
-
)
|
86
|
-
if not block_document_id:
|
87
|
-
raise ValueError(
|
88
|
-
"Base job template is invalid for this worker type because it does not"
|
89
|
-
" contain a block_document_id after variable resolution."
|
90
|
-
)
|
91
|
-
|
92
|
-
block_document = await client.read_block_document(
|
93
|
-
block_document_id=block_document_id
|
94
|
-
)
|
95
|
-
infrastructure_block = Block._from_block_document(block_document)
|
96
|
-
|
97
|
-
populated_configuration["block"] = infrastructure_block
|
98
|
-
|
99
|
-
return cls(**populated_configuration)
|
100
|
-
|
101
|
-
@classmethod
|
102
|
-
def json_template(cls) -> dict:
|
103
|
-
"""Returns a dict with job configuration as keys and the corresponding templates as values
|
104
|
-
|
105
|
-
Defaults to using the job configuration parameter name as the template variable name.
|
106
|
-
|
107
|
-
e.g.
|
108
|
-
{
|
109
|
-
key1: '{{ key1 }}', # default variable template
|
110
|
-
key2: '{{ template2 }}', # `template2` specifically provide as template
|
111
|
-
}
|
112
|
-
"""
|
113
|
-
configuration = {}
|
114
|
-
properties = cls.schema()["properties"]
|
115
|
-
for k, v in properties.items():
|
116
|
-
if v.get("template"):
|
117
|
-
template = v["template"]
|
118
|
-
else:
|
119
|
-
template = "{{ " + k + " }}"
|
120
|
-
configuration[k] = template
|
121
|
-
|
122
|
-
return configuration
|
123
|
-
|
124
|
-
def _related_resources(self) -> List[RelatedResource]:
|
125
|
-
tags = set()
|
126
|
-
related = []
|
127
|
-
|
128
|
-
for kind, obj in self._related_objects.items():
|
129
|
-
if obj is None:
|
130
|
-
continue
|
131
|
-
if hasattr(obj, "tags"):
|
132
|
-
tags.update(obj.tags)
|
133
|
-
related.append(object_as_related_resource(kind=kind, role=kind, object=obj))
|
134
|
-
|
135
|
-
return related + tags_as_related_resources(tags)
|
136
|
-
|
137
|
-
def prepare_for_flow_run(
|
138
|
-
self,
|
139
|
-
flow_run: "FlowRun",
|
140
|
-
deployment: Optional["DeploymentResponse"] = None,
|
141
|
-
flow: Optional["Flow"] = None,
|
142
|
-
):
|
143
|
-
self.block = self.block.prepare_for_flow_run(
|
144
|
-
flow_run=flow_run, deployment=deployment, flow=flow
|
145
|
-
)
|
146
|
-
|
147
|
-
|
148
|
-
class BlockWorkerResult(BaseWorkerResult):
|
149
|
-
"""Result of a block worker job"""
|
150
|
-
|
151
|
-
|
152
|
-
@deprecated_class(
|
153
|
-
start_date="Jun 2024",
|
154
|
-
help="Refer to the upgrade guide for more information: https://docs.prefect.io/latest/guides/upgrade-guide-agents-to-workers/",
|
155
|
-
)
|
156
|
-
class BlockWorker(BaseWorker):
|
157
|
-
type = "block"
|
158
|
-
job_configuration = BlockWorkerJobConfiguration
|
159
|
-
|
160
|
-
_description = "Execute flow runs using an infrastructure block as the job creator."
|
161
|
-
_display_name = "Block"
|
162
|
-
|
163
|
-
async def run(
|
164
|
-
self,
|
165
|
-
flow_run: "FlowRun",
|
166
|
-
configuration: BlockWorkerJobConfiguration,
|
167
|
-
task_status: Optional[anyio.abc.TaskStatus] = None,
|
168
|
-
):
|
169
|
-
block = configuration.block
|
170
|
-
|
171
|
-
# logic for applying infra overrides taken from src/prefect/agent.py
|
172
|
-
deployment = await self._client.read_deployment(flow_run.deployment_id)
|
173
|
-
flow = await self._client.read_flow(deployment.flow_id)
|
174
|
-
infra_document = await self._client.read_block_document(
|
175
|
-
configuration.block._block_document_id
|
176
|
-
)
|
177
|
-
|
178
|
-
# this piece of logic applies any overrides that may have been set on the
|
179
|
-
# deployment; overrides are defined as dot.delimited paths on possibly nested
|
180
|
-
# attributes of the infrastructure block
|
181
|
-
doc_dict = infra_document.dict()
|
182
|
-
infra_dict = doc_dict.get("data", {})
|
183
|
-
for override, value in (deployment.job_variables or {}).items():
|
184
|
-
nested_fields = override.split(".")
|
185
|
-
if nested_fields == ["command"]:
|
186
|
-
value = shlex.split(value)
|
187
|
-
data = infra_dict
|
188
|
-
for field in nested_fields[:-1]:
|
189
|
-
data = data[field]
|
190
|
-
|
191
|
-
# once we reach the end, set the value
|
192
|
-
data[nested_fields[-1]] = value
|
193
|
-
|
194
|
-
# reconstruct the infra block
|
195
|
-
doc_dict["data"] = infra_dict
|
196
|
-
infra_document = BlockDocument(**doc_dict)
|
197
|
-
block = Block._from_block_document(infra_document)
|
198
|
-
|
199
|
-
block = block.prepare_for_flow_run(
|
200
|
-
flow_run=flow_run, deployment=deployment, flow=flow
|
201
|
-
)
|
202
|
-
|
203
|
-
result = await block.run(
|
204
|
-
task_status=task_status,
|
205
|
-
)
|
206
|
-
return BlockWorkerResult(
|
207
|
-
identifier=result.identifier, status_code=result.status_code
|
208
|
-
)
|
209
|
-
|
210
|
-
async def kill_infrastructure(
|
211
|
-
self,
|
212
|
-
infrastructure_pid: str,
|
213
|
-
configuration: BlockWorkerJobConfiguration,
|
214
|
-
grace_seconds: int = 30,
|
215
|
-
):
|
216
|
-
block = configuration.block
|
217
|
-
if not hasattr(block, "kill"):
|
218
|
-
self._logger.error(
|
219
|
-
f"Flow run infrastructure block {block.type!r} "
|
220
|
-
"does not support killing created infrastructure. "
|
221
|
-
"Cancellation cannot be guaranteed."
|
222
|
-
)
|
223
|
-
return
|
224
|
-
|
225
|
-
await block.kill(
|
226
|
-
infrastructure_pid=infrastructure_pid, grace_seconds=grace_seconds
|
227
|
-
)
|
6
|
+
__getattr__ = getattr_migration(__name__)
|
prefect/workers/cloud.py
ADDED
prefect/workers/process.py
CHANGED
@@ -21,23 +21,38 @@ import socket
|
|
21
21
|
import subprocess
|
22
22
|
import sys
|
23
23
|
import tempfile
|
24
|
+
import threading
|
25
|
+
from functools import partial
|
24
26
|
from pathlib import Path
|
25
|
-
from typing import TYPE_CHECKING, Dict, Optional, Tuple
|
27
|
+
from typing import TYPE_CHECKING, Callable, Dict, Optional, Tuple
|
26
28
|
|
27
29
|
import anyio
|
28
30
|
import anyio.abc
|
29
|
-
|
30
|
-
from prefect._internal.pydantic import HAS_PYDANTIC_V2
|
31
|
-
|
32
|
-
if HAS_PYDANTIC_V2:
|
33
|
-
from pydantic.v1 import Field, validator
|
34
|
-
else:
|
35
|
-
from pydantic import Field, validator
|
31
|
+
from pydantic import Field, field_validator
|
36
32
|
|
37
33
|
from prefect._internal.schemas.validators import validate_command
|
38
34
|
from prefect.client.schemas import FlowRun
|
39
|
-
from prefect.
|
35
|
+
from prefect.client.schemas.filters import (
|
36
|
+
FlowRunFilter,
|
37
|
+
FlowRunFilterId,
|
38
|
+
FlowRunFilterState,
|
39
|
+
FlowRunFilterStateName,
|
40
|
+
FlowRunFilterStateType,
|
41
|
+
WorkPoolFilter,
|
42
|
+
WorkPoolFilterName,
|
43
|
+
WorkQueueFilter,
|
44
|
+
WorkQueueFilterName,
|
45
|
+
)
|
46
|
+
from prefect.client.schemas.objects import StateType
|
47
|
+
from prefect.events.utilities import emit_event
|
48
|
+
from prefect.exceptions import (
|
49
|
+
InfrastructureNotAvailable,
|
50
|
+
InfrastructureNotFound,
|
51
|
+
ObjectNotFound,
|
52
|
+
)
|
53
|
+
from prefect.settings import PREFECT_WORKER_QUERY_SECONDS
|
40
54
|
from prefect.utilities.processutils import get_sys_executable, run_process
|
55
|
+
from prefect.utilities.services import critical_service_loop
|
41
56
|
from prefect.workers.base import (
|
42
57
|
BaseJobConfiguration,
|
43
58
|
BaseVariables,
|
@@ -68,7 +83,8 @@ class ProcessJobConfiguration(BaseJobConfiguration):
|
|
68
83
|
stream_output: bool = Field(default=True)
|
69
84
|
working_dir: Optional[Path] = Field(default=None)
|
70
85
|
|
71
|
-
@
|
86
|
+
@field_validator("working_dir")
|
87
|
+
@classmethod
|
72
88
|
def validate_command(cls, v):
|
73
89
|
return validate_command(v)
|
74
90
|
|
@@ -133,6 +149,96 @@ class ProcessWorker(BaseWorker):
|
|
133
149
|
)
|
134
150
|
_logo_url = "https://cdn.sanity.io/images/3ugk85nk/production/356e6766a91baf20e1d08bbe16e8b5aaef4d8643-48x48.png"
|
135
151
|
|
152
|
+
async def start(
|
153
|
+
self,
|
154
|
+
run_once: bool = False,
|
155
|
+
with_healthcheck: bool = False,
|
156
|
+
printer: Callable[..., None] = print,
|
157
|
+
):
|
158
|
+
"""
|
159
|
+
Starts the worker and runs the main worker loops.
|
160
|
+
|
161
|
+
By default, the worker will run loops to poll for scheduled/cancelled flow
|
162
|
+
runs and sync with the Prefect API server.
|
163
|
+
|
164
|
+
If `run_once` is set, the worker will only run each loop once and then return.
|
165
|
+
|
166
|
+
If `with_healthcheck` is set, the worker will start a healthcheck server which
|
167
|
+
can be used to determine if the worker is still polling for flow runs and restart
|
168
|
+
the worker if necessary.
|
169
|
+
|
170
|
+
Args:
|
171
|
+
run_once: If set, the worker will only run each loop once then return.
|
172
|
+
with_healthcheck: If set, the worker will start a healthcheck server.
|
173
|
+
printer: A `print`-like function where logs will be reported.
|
174
|
+
"""
|
175
|
+
healthcheck_server = None
|
176
|
+
healthcheck_thread = None
|
177
|
+
try:
|
178
|
+
async with self as worker:
|
179
|
+
# wait for an initial heartbeat to configure the worker
|
180
|
+
await worker.sync_with_backend()
|
181
|
+
# schedule the scheduled flow run polling loop
|
182
|
+
async with anyio.create_task_group() as loops_task_group:
|
183
|
+
loops_task_group.start_soon(
|
184
|
+
partial(
|
185
|
+
critical_service_loop,
|
186
|
+
workload=self.get_and_submit_flow_runs,
|
187
|
+
interval=PREFECT_WORKER_QUERY_SECONDS.value(),
|
188
|
+
run_once=run_once,
|
189
|
+
jitter_range=0.3,
|
190
|
+
backoff=4, # Up to ~1 minute interval during backoff
|
191
|
+
)
|
192
|
+
)
|
193
|
+
# schedule the sync loop
|
194
|
+
loops_task_group.start_soon(
|
195
|
+
partial(
|
196
|
+
critical_service_loop,
|
197
|
+
workload=self.sync_with_backend,
|
198
|
+
interval=self.heartbeat_interval_seconds,
|
199
|
+
run_once=run_once,
|
200
|
+
jitter_range=0.3,
|
201
|
+
backoff=4,
|
202
|
+
)
|
203
|
+
)
|
204
|
+
loops_task_group.start_soon(
|
205
|
+
partial(
|
206
|
+
critical_service_loop,
|
207
|
+
workload=self.check_for_cancelled_flow_runs,
|
208
|
+
interval=PREFECT_WORKER_QUERY_SECONDS.value() * 2,
|
209
|
+
run_once=run_once,
|
210
|
+
jitter_range=0.3,
|
211
|
+
backoff=4,
|
212
|
+
)
|
213
|
+
)
|
214
|
+
|
215
|
+
self._started_event = await self._emit_worker_started_event()
|
216
|
+
|
217
|
+
if with_healthcheck:
|
218
|
+
from prefect.workers.server import build_healthcheck_server
|
219
|
+
|
220
|
+
# we'll start the ASGI server in a separate thread so that
|
221
|
+
# uvicorn does not block the main thread
|
222
|
+
healthcheck_server = build_healthcheck_server(
|
223
|
+
worker=worker,
|
224
|
+
query_interval_seconds=PREFECT_WORKER_QUERY_SECONDS.value(),
|
225
|
+
)
|
226
|
+
healthcheck_thread = threading.Thread(
|
227
|
+
name="healthcheck-server-thread",
|
228
|
+
target=healthcheck_server.run,
|
229
|
+
daemon=True,
|
230
|
+
)
|
231
|
+
healthcheck_thread.start()
|
232
|
+
printer(f"Worker {worker.name!r} started!")
|
233
|
+
finally:
|
234
|
+
if healthcheck_server and healthcheck_thread:
|
235
|
+
self._logger.debug("Stopping healthcheck server...")
|
236
|
+
healthcheck_server.should_exit = True
|
237
|
+
healthcheck_thread.join()
|
238
|
+
self._logger.debug("Healthcheck server stopped.")
|
239
|
+
|
240
|
+
printer(f"Worker {worker.name!r} stopped!")
|
241
|
+
|
136
242
|
async def run(
|
137
243
|
self,
|
138
244
|
flow_run: FlowRun,
|
@@ -214,10 +320,9 @@ class ProcessWorker(BaseWorker):
|
|
214
320
|
status_code=process.returncode, identifier=str(process.pid)
|
215
321
|
)
|
216
322
|
|
217
|
-
async def
|
323
|
+
async def kill_process(
|
218
324
|
self,
|
219
325
|
infrastructure_pid: str,
|
220
|
-
configuration: ProcessJobConfiguration,
|
221
326
|
grace_seconds: int = 30,
|
222
327
|
):
|
223
328
|
hostname, pid = _parse_infrastructure_pid(infrastructure_pid)
|
@@ -268,3 +373,151 @@ class ProcessWorker(BaseWorker):
|
|
268
373
|
# We shouldn't ever end up here, but it's possible that the
|
269
374
|
# process ended right after the check above.
|
270
375
|
return
|
376
|
+
|
377
|
+
async def check_for_cancelled_flow_runs(self):
|
378
|
+
if not self.is_setup:
|
379
|
+
raise RuntimeError(
|
380
|
+
"Worker is not set up. Please make sure you are running this worker "
|
381
|
+
"as an async context manager."
|
382
|
+
)
|
383
|
+
|
384
|
+
self._logger.debug("Checking for cancelled flow runs...")
|
385
|
+
|
386
|
+
work_queue_filter = (
|
387
|
+
WorkQueueFilter(name=WorkQueueFilterName(any_=list(self._work_queues)))
|
388
|
+
if self._work_queues
|
389
|
+
else None
|
390
|
+
)
|
391
|
+
|
392
|
+
named_cancelling_flow_runs = await self._client.read_flow_runs(
|
393
|
+
flow_run_filter=FlowRunFilter(
|
394
|
+
state=FlowRunFilterState(
|
395
|
+
type=FlowRunFilterStateType(any_=[StateType.CANCELLED]),
|
396
|
+
name=FlowRunFilterStateName(any_=["Cancelling"]),
|
397
|
+
),
|
398
|
+
# Avoid duplicate cancellation calls
|
399
|
+
id=FlowRunFilterId(not_any_=list(self._cancelling_flow_run_ids)),
|
400
|
+
),
|
401
|
+
work_pool_filter=WorkPoolFilter(
|
402
|
+
name=WorkPoolFilterName(any_=[self._work_pool_name])
|
403
|
+
),
|
404
|
+
work_queue_filter=work_queue_filter,
|
405
|
+
)
|
406
|
+
|
407
|
+
typed_cancelling_flow_runs = await self._client.read_flow_runs(
|
408
|
+
flow_run_filter=FlowRunFilter(
|
409
|
+
state=FlowRunFilterState(
|
410
|
+
type=FlowRunFilterStateType(any_=[StateType.CANCELLING]),
|
411
|
+
),
|
412
|
+
# Avoid duplicate cancellation calls
|
413
|
+
id=FlowRunFilterId(not_any_=list(self._cancelling_flow_run_ids)),
|
414
|
+
),
|
415
|
+
work_pool_filter=WorkPoolFilter(
|
416
|
+
name=WorkPoolFilterName(any_=[self._work_pool_name])
|
417
|
+
),
|
418
|
+
work_queue_filter=work_queue_filter,
|
419
|
+
)
|
420
|
+
|
421
|
+
cancelling_flow_runs = named_cancelling_flow_runs + typed_cancelling_flow_runs
|
422
|
+
|
423
|
+
if cancelling_flow_runs:
|
424
|
+
self._logger.info(
|
425
|
+
f"Found {len(cancelling_flow_runs)} flow runs awaiting cancellation."
|
426
|
+
)
|
427
|
+
|
428
|
+
for flow_run in cancelling_flow_runs:
|
429
|
+
self._cancelling_flow_run_ids.add(flow_run.id)
|
430
|
+
self._runs_task_group.start_soon(self.cancel_run, flow_run)
|
431
|
+
|
432
|
+
return cancelling_flow_runs
|
433
|
+
|
434
|
+
async def cancel_run(self, flow_run: "FlowRun"):
|
435
|
+
run_logger = self.get_flow_run_logger(flow_run)
|
436
|
+
|
437
|
+
try:
|
438
|
+
configuration = await self._get_configuration(flow_run)
|
439
|
+
except ObjectNotFound:
|
440
|
+
self._logger.warning(
|
441
|
+
f"Flow run {flow_run.id!r} cannot be cancelled by this worker:"
|
442
|
+
f" associated deployment {flow_run.deployment_id!r} does not exist."
|
443
|
+
)
|
444
|
+
await self._mark_flow_run_as_cancelled(
|
445
|
+
flow_run,
|
446
|
+
state_updates={
|
447
|
+
"message": (
|
448
|
+
"This flow run is missing infrastructure configuration information"
|
449
|
+
" and cancellation cannot be guaranteed."
|
450
|
+
)
|
451
|
+
},
|
452
|
+
)
|
453
|
+
return
|
454
|
+
else:
|
455
|
+
if configuration.is_using_a_runner:
|
456
|
+
self._logger.info(
|
457
|
+
f"Skipping cancellation because flow run {str(flow_run.id)!r} is"
|
458
|
+
" using enhanced cancellation. A dedicated runner will handle"
|
459
|
+
" cancellation."
|
460
|
+
)
|
461
|
+
return
|
462
|
+
|
463
|
+
if not flow_run.infrastructure_pid:
|
464
|
+
run_logger.error(
|
465
|
+
f"Flow run '{flow_run.id}' does not have an infrastructure pid"
|
466
|
+
" attached. Cancellation cannot be guaranteed."
|
467
|
+
)
|
468
|
+
await self._mark_flow_run_as_cancelled(
|
469
|
+
flow_run,
|
470
|
+
state_updates={
|
471
|
+
"message": (
|
472
|
+
"This flow run is missing infrastructure tracking information"
|
473
|
+
" and cancellation cannot be guaranteed."
|
474
|
+
)
|
475
|
+
},
|
476
|
+
)
|
477
|
+
return
|
478
|
+
|
479
|
+
try:
|
480
|
+
await self.kill_process(
|
481
|
+
infrastructure_pid=flow_run.infrastructure_pid,
|
482
|
+
)
|
483
|
+
except NotImplementedError:
|
484
|
+
self._logger.error(
|
485
|
+
f"Worker type {self.type!r} does not support killing created "
|
486
|
+
"infrastructure. Cancellation cannot be guaranteed."
|
487
|
+
)
|
488
|
+
except InfrastructureNotFound as exc:
|
489
|
+
self._logger.warning(f"{exc} Marking flow run as cancelled.")
|
490
|
+
await self._mark_flow_run_as_cancelled(flow_run)
|
491
|
+
except InfrastructureNotAvailable as exc:
|
492
|
+
self._logger.warning(f"{exc} Flow run cannot be cancelled by this worker.")
|
493
|
+
except Exception:
|
494
|
+
run_logger.exception(
|
495
|
+
"Encountered exception while killing infrastructure for flow run "
|
496
|
+
f"'{flow_run.id}'. Flow run may not be cancelled."
|
497
|
+
)
|
498
|
+
# We will try again on generic exceptions
|
499
|
+
self._cancelling_flow_run_ids.remove(flow_run.id)
|
500
|
+
return
|
501
|
+
else:
|
502
|
+
self._emit_flow_run_cancelled_event(
|
503
|
+
flow_run=flow_run, configuration=configuration
|
504
|
+
)
|
505
|
+
await self._mark_flow_run_as_cancelled(flow_run)
|
506
|
+
run_logger.info(f"Cancelled flow run '{flow_run.id}'!")
|
507
|
+
|
508
|
+
def _emit_flow_run_cancelled_event(
|
509
|
+
self, flow_run: "FlowRun", configuration: BaseJobConfiguration
|
510
|
+
):
|
511
|
+
related = self._event_related_resources(configuration=configuration)
|
512
|
+
|
513
|
+
for resource in related:
|
514
|
+
if resource.role == "flow-run":
|
515
|
+
resource["prefect.infrastructure.identifier"] = str(
|
516
|
+
flow_run.infrastructure_pid
|
517
|
+
)
|
518
|
+
|
519
|
+
emit_event(
|
520
|
+
event="prefect.worker.cancelled-flow-run",
|
521
|
+
resource=self._event_resource(),
|
522
|
+
related=related,
|
523
|
+
)
|
prefect/workers/server.py
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
from typing import Union
|
2
2
|
|
3
3
|
import uvicorn
|
4
|
-
|
5
|
-
from
|
4
|
+
import uvicorn.server
|
5
|
+
from fastapi import APIRouter, FastAPI, status
|
6
|
+
from fastapi.responses import JSONResponse
|
6
7
|
|
7
8
|
from prefect.settings import (
|
8
9
|
PREFECT_WORKER_WEBSERVER_HOST,
|
@@ -12,19 +13,19 @@ from prefect.workers.base import BaseWorker
|
|
12
13
|
from prefect.workers.process import ProcessWorker
|
13
14
|
|
14
15
|
|
15
|
-
def
|
16
|
+
def build_healthcheck_server(
|
16
17
|
worker: Union[BaseWorker, ProcessWorker],
|
17
|
-
query_interval_seconds:
|
18
|
+
query_interval_seconds: float,
|
18
19
|
log_level: str = "error",
|
19
|
-
)
|
20
|
+
):
|
20
21
|
"""
|
21
|
-
|
22
|
+
Build a healthcheck FastAPI server for a worker.
|
22
23
|
|
23
24
|
Args:
|
24
25
|
worker (BaseWorker | ProcessWorker): the worker whose health we will check
|
25
|
-
log_level (str): the log
|
26
|
+
log_level (str): the log
|
26
27
|
"""
|
27
|
-
|
28
|
+
app = FastAPI()
|
28
29
|
router = APIRouter()
|
29
30
|
|
30
31
|
def perform_health_check():
|
@@ -41,11 +42,28 @@ def start_healthcheck_server(
|
|
41
42
|
|
42
43
|
router.add_api_route("/health", perform_health_check, methods=["GET"])
|
43
44
|
|
44
|
-
|
45
|
+
app.include_router(router)
|
45
46
|
|
46
|
-
uvicorn.
|
47
|
-
|
47
|
+
config = uvicorn.Config(
|
48
|
+
app=app,
|
48
49
|
host=PREFECT_WORKER_WEBSERVER_HOST.value(),
|
49
50
|
port=PREFECT_WORKER_WEBSERVER_PORT.value(),
|
50
51
|
log_level=log_level,
|
51
52
|
)
|
53
|
+
return uvicorn.Server(config=config)
|
54
|
+
|
55
|
+
|
56
|
+
def start_healthcheck_server(
|
57
|
+
worker: Union[BaseWorker, ProcessWorker],
|
58
|
+
query_interval_seconds: float,
|
59
|
+
log_level: str = "error",
|
60
|
+
) -> None:
|
61
|
+
"""
|
62
|
+
Run a healthcheck FastAPI server for a worker.
|
63
|
+
|
64
|
+
Args:
|
65
|
+
worker (BaseWorker | ProcessWorker): the worker whose health we will check
|
66
|
+
log_level (str): the log level to use for the server
|
67
|
+
"""
|
68
|
+
server = build_healthcheck_server(worker, query_interval_seconds, log_level)
|
69
|
+
server.run()
|