prefect-client 2.19.2__py3-none-any.whl → 3.0.0rc1__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 +8 -56
- prefect/_internal/compatibility/deprecated.py +6 -115
- prefect/_internal/compatibility/experimental.py +4 -79
- prefect/_internal/concurrency/api.py +0 -34
- 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/threads.py +35 -0
- prefect/_internal/concurrency/waiters.py +0 -28
- prefect/_internal/pydantic/__init__.py +0 -45
- 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/schemas/bases.py +44 -177
- prefect/_internal/schemas/fields.py +1 -43
- prefect/_internal/schemas/validators.py +60 -158
- prefect/artifacts.py +161 -14
- prefect/automations.py +39 -4
- prefect/blocks/abstract.py +1 -1
- prefect/blocks/core.py +268 -148
- prefect/blocks/fields.py +2 -57
- prefect/blocks/kubernetes.py +8 -12
- prefect/blocks/notifications.py +40 -20
- prefect/blocks/system.py +22 -11
- prefect/blocks/webhook.py +2 -9
- prefect/client/base.py +4 -4
- prefect/client/cloud.py +8 -13
- prefect/client/orchestration.py +347 -341
- prefect/client/schemas/actions.py +92 -86
- prefect/client/schemas/filters.py +20 -40
- prefect/client/schemas/objects.py +151 -145
- prefect/client/schemas/responses.py +16 -24
- prefect/client/schemas/schedules.py +47 -35
- prefect/client/subscriptions.py +2 -2
- prefect/client/utilities.py +5 -2
- prefect/concurrency/asyncio.py +3 -1
- prefect/concurrency/events.py +1 -1
- prefect/concurrency/services.py +6 -3
- prefect/context.py +195 -27
- prefect/deployments/__init__.py +5 -6
- prefect/deployments/base.py +7 -5
- prefect/deployments/flow_runs.py +185 -0
- prefect/deployments/runner.py +50 -45
- prefect/deployments/schedules.py +28 -23
- prefect/deployments/steps/__init__.py +0 -1
- prefect/deployments/steps/core.py +1 -0
- prefect/deployments/steps/pull.py +7 -21
- prefect/engine.py +12 -2422
- prefect/events/actions.py +17 -23
- prefect/events/cli/automations.py +19 -6
- prefect/events/clients.py +14 -37
- prefect/events/filters.py +14 -18
- prefect/events/related.py +2 -2
- 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 +34 -65
- prefect/events/schemas/labelling.py +10 -14
- prefect/events/utilities.py +2 -3
- prefect/events/worker.py +2 -3
- prefect/filesystems.py +6 -517
- prefect/{new_flow_engine.py → flow_engine.py} +313 -72
- prefect/flow_runs.py +377 -5
- prefect/flows.py +307 -166
- prefect/futures.py +186 -345
- prefect/infrastructure/__init__.py +0 -27
- prefect/infrastructure/provisioners/__init__.py +5 -3
- prefect/infrastructure/provisioners/cloud_run.py +11 -6
- prefect/infrastructure/provisioners/container_instance.py +11 -7
- prefect/infrastructure/provisioners/ecs.py +6 -4
- prefect/infrastructure/provisioners/modal.py +8 -5
- prefect/input/actions.py +2 -4
- prefect/input/run_input.py +5 -7
- prefect/logging/formatters.py +0 -2
- prefect/logging/handlers.py +3 -11
- prefect/logging/loggers.py +2 -2
- prefect/manifests.py +2 -1
- prefect/records/__init__.py +1 -0
- prefect/records/result_store.py +42 -0
- prefect/records/store.py +9 -0
- prefect/results.py +43 -39
- prefect/runner/runner.py +19 -15
- prefect/runner/server.py +6 -10
- prefect/runner/storage.py +3 -8
- prefect/runner/submit.py +2 -2
- prefect/runner/utils.py +2 -2
- prefect/serializers.py +24 -35
- prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
- prefect/settings.py +70 -133
- prefect/states.py +17 -47
- prefect/task_engine.py +697 -58
- prefect/task_runners.py +269 -301
- prefect/task_server.py +53 -34
- prefect/tasks.py +327 -337
- prefect/transactions.py +220 -0
- prefect/types/__init__.py +61 -82
- prefect/utilities/asyncutils.py +195 -136
- prefect/utilities/callables.py +311 -43
- prefect/utilities/collections.py +23 -38
- prefect/utilities/dispatch.py +11 -3
- prefect/utilities/dockerutils.py +4 -0
- prefect/utilities/engine.py +140 -20
- prefect/utilities/importtools.py +97 -27
- prefect/utilities/pydantic.py +128 -38
- prefect/utilities/schema_tools/hydration.py +5 -1
- prefect/utilities/templating.py +12 -2
- prefect/variables.py +78 -61
- prefect/workers/__init__.py +0 -1
- prefect/workers/base.py +15 -17
- prefect/workers/process.py +3 -8
- prefect/workers/server.py +2 -2
- {prefect_client-2.19.2.dist-info → prefect_client-3.0.0rc1.dist-info}/METADATA +22 -21
- prefect_client-3.0.0rc1.dist-info/RECORD +176 -0
- 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/__init__.py +0 -0
- 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/__init__.py +0 -0
- 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/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/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/agent.py +0 -698
- prefect/deployments/deployments.py +0 -1042
- 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/base.py +0 -323
- prefect/infrastructure/container.py +0 -818
- prefect/infrastructure/kubernetes.py +0 -920
- prefect/infrastructure/process.py +0 -289
- 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/workers/block.py +0 -218
- prefect_client-2.19.2.dist-info/RECORD +0 -292
- {prefect_client-2.19.2.dist-info → prefect_client-3.0.0rc1.dist-info}/LICENSE +0 -0
- {prefect_client-2.19.2.dist-info → prefect_client-3.0.0rc1.dist-info}/WHEEL +0 -0
- {prefect_client-2.19.2.dist-info → prefect_client-3.0.0rc1.dist-info}/top_level.txt +0 -0
prefect/software/conda.py
DELETED
@@ -1,199 +0,0 @@
|
|
1
|
-
import json
|
2
|
-
import re
|
3
|
-
import subprocess
|
4
|
-
import sys
|
5
|
-
from pathlib import Path
|
6
|
-
from typing import List, Type
|
7
|
-
|
8
|
-
import yaml
|
9
|
-
|
10
|
-
from prefect._internal.pydantic import HAS_PYDANTIC_V2
|
11
|
-
|
12
|
-
if HAS_PYDANTIC_V2:
|
13
|
-
from pydantic.v1 import Field, validate_arguments
|
14
|
-
else:
|
15
|
-
from pydantic import Field, validate_arguments
|
16
|
-
from typing_extensions import Self
|
17
|
-
|
18
|
-
from prefect.software.base import (
|
19
|
-
Requirement,
|
20
|
-
pop_requirement_by_name,
|
21
|
-
remove_duplicate_requirements,
|
22
|
-
)
|
23
|
-
from prefect.software.pip import current_environment_requirements
|
24
|
-
from prefect.software.python import PythonEnvironment
|
25
|
-
from prefect.utilities.collections import listrepr
|
26
|
-
|
27
|
-
# Capture each component of a conda requirement string.
|
28
|
-
#
|
29
|
-
# <name><version_specifier><version><build_specifier><build>
|
30
|
-
#
|
31
|
-
# The specification of these requirements is more relaxed than pip requirements.
|
32
|
-
# Notably, the version specifier is typically a single equal sign and a build can be
|
33
|
-
# included after the version.
|
34
|
-
#
|
35
|
-
# For example, the requirement "requests=2.25.1=pyhd3eb1b0_0" matches as:
|
36
|
-
# name: "requests"
|
37
|
-
# version_specifier: "="
|
38
|
-
# version: "2.25.1"
|
39
|
-
# build_specifier: "="
|
40
|
-
# build: "pyhd3eb1b0_0"
|
41
|
-
#
|
42
|
-
# Regex components:
|
43
|
-
# name (required): any combination of numbers, letters, or dashes
|
44
|
-
# version_specifier (optional): one or more of `>`, `<`, and `=`
|
45
|
-
# version (optional): any combination of numbers, letters, or periods; not valid
|
46
|
-
# unless grouped with a version specifier.
|
47
|
-
# build_specifier (optional): just `=`.
|
48
|
-
# build (optional): any combination of numbers, letters, or underscores; not valid
|
49
|
-
# unless grouped with a build specifier.
|
50
|
-
|
51
|
-
CONDA_REQUIREMENT = re.compile(
|
52
|
-
r"^((?P<channel>[0-9A-Za-z\_\-\/]+)::)?"
|
53
|
-
r"(?P<name>[0-9A-Za-z_\-\.]+)"
|
54
|
-
r"((?P<version_specifier>[>=<]+)(?P<version>[0-9a-zA-Z\.]+))?"
|
55
|
-
r"((?P<build_specifier>=)(?P<build>[0-9A-Za-z\_\[\]\=]+))?$"
|
56
|
-
)
|
57
|
-
|
58
|
-
|
59
|
-
class CondaRequirement(Requirement):
|
60
|
-
"""
|
61
|
-
A parsed requirement for installation with conda.
|
62
|
-
"""
|
63
|
-
|
64
|
-
def __init__(self, requirement_string: str):
|
65
|
-
self._requirement_string = requirement_string
|
66
|
-
|
67
|
-
parsed = CONDA_REQUIREMENT.match(requirement_string)
|
68
|
-
if parsed is None:
|
69
|
-
raise ValueError(
|
70
|
-
f"Invalid requirement {requirement_string!r}: could not be parsed."
|
71
|
-
)
|
72
|
-
self._parts = parsed.groupdict()
|
73
|
-
|
74
|
-
self.name = self._parts["name"]
|
75
|
-
self.version_specifier = self._parts["version_specifier"]
|
76
|
-
self.version = self._parts["version"]
|
77
|
-
self.build_specifier = self._parts["build_specifier"]
|
78
|
-
self.build = self._parts["build"]
|
79
|
-
|
80
|
-
def __str__(self) -> str:
|
81
|
-
return self._requirement_string
|
82
|
-
|
83
|
-
|
84
|
-
class CondaError(RuntimeError):
|
85
|
-
"""
|
86
|
-
Raised if an error occurs in conda.
|
87
|
-
"""
|
88
|
-
|
89
|
-
|
90
|
-
def current_environment_conda_requirements(
|
91
|
-
include_builds: bool = False, explicit_only: bool = True
|
92
|
-
) -> List[CondaRequirement]:
|
93
|
-
"""
|
94
|
-
Return conda requirements by exporting the current environment.
|
95
|
-
|
96
|
-
Skips any pip requirements included in the export. Only requirements that are
|
97
|
-
managed by conda are returned.
|
98
|
-
"""
|
99
|
-
command = ["conda", "env", "export", "--json"]
|
100
|
-
|
101
|
-
if not include_builds:
|
102
|
-
command.append("--no-builds")
|
103
|
-
if explicit_only:
|
104
|
-
command.append("--from-history")
|
105
|
-
|
106
|
-
process = subprocess.run(command, capture_output=True)
|
107
|
-
parsed = json.loads(process.stdout)
|
108
|
-
if "error" in parsed:
|
109
|
-
raise CondaError(
|
110
|
-
"Encountered an exception while exporting the conda environment: "
|
111
|
-
+ parsed["error"]
|
112
|
-
)
|
113
|
-
|
114
|
-
# If no dependencies are given, this field will not be present
|
115
|
-
dependencies = parsed.get("dependencies", [])
|
116
|
-
|
117
|
-
# The string check will exclude nested objects like the 'pip' subtree
|
118
|
-
return [CondaRequirement(dep) for dep in dependencies if isinstance(dep, str)]
|
119
|
-
|
120
|
-
|
121
|
-
class CondaEnvironment(PythonEnvironment):
|
122
|
-
conda_requirements: List[CondaRequirement] = Field(default_factory=list)
|
123
|
-
|
124
|
-
@classmethod
|
125
|
-
def from_environment(cls: Type[Self], exclude_nested: bool = False) -> Self:
|
126
|
-
conda_requirements = (
|
127
|
-
current_environment_conda_requirements()
|
128
|
-
if "conda" in sys.executable
|
129
|
-
else []
|
130
|
-
)
|
131
|
-
pip_requirements = remove_duplicate_requirements(
|
132
|
-
conda_requirements,
|
133
|
-
current_environment_requirements(
|
134
|
-
exclude_nested=exclude_nested, on_uninstallable_requirement="warn"
|
135
|
-
),
|
136
|
-
)
|
137
|
-
python_requirement = pop_requirement_by_name(conda_requirements, "python")
|
138
|
-
python_version = python_requirement.version if python_requirement else None
|
139
|
-
|
140
|
-
return cls(
|
141
|
-
pip_requirements=pip_requirements,
|
142
|
-
conda_requirements=conda_requirements,
|
143
|
-
python_version=python_version,
|
144
|
-
)
|
145
|
-
|
146
|
-
@classmethod
|
147
|
-
@validate_arguments
|
148
|
-
def from_file(cls: Type[Self], path: Path) -> Self:
|
149
|
-
parsed = yaml.safe_load(path.read_text())
|
150
|
-
|
151
|
-
# If no dependencies are given, this field will not be present
|
152
|
-
dependencies = parsed.get("dependencies", [])
|
153
|
-
|
154
|
-
# The string check will exclude nested objects like the 'pip' subtree
|
155
|
-
conda_requirements = [
|
156
|
-
CondaRequirement(dep) for dep in dependencies if isinstance(dep, str)
|
157
|
-
]
|
158
|
-
|
159
|
-
python_requirement = pop_requirement_by_name(conda_requirements, "python")
|
160
|
-
python_version = python_requirement.version if python_requirement else None
|
161
|
-
|
162
|
-
other_requirements = {}
|
163
|
-
|
164
|
-
# Parse nested requirements. We only support 'pip' for now but we'll check for
|
165
|
-
# others
|
166
|
-
for subtree in [dep for dep in dependencies if isinstance(dep, dict)]:
|
167
|
-
key = list(subtree.keys())[0]
|
168
|
-
|
169
|
-
if key in other_requirements:
|
170
|
-
raise ValueError(
|
171
|
-
"Invalid conda requirements specification. "
|
172
|
-
f"Found duplicate key {key!r}."
|
173
|
-
)
|
174
|
-
|
175
|
-
other_requirements[key] = subtree[key]
|
176
|
-
|
177
|
-
pip_requirements = other_requirements.pop("pip", [])
|
178
|
-
|
179
|
-
if other_requirements:
|
180
|
-
raise ValueError(
|
181
|
-
"Found unsupported requirements types in file: "
|
182
|
-
f"{listrepr(other_requirements.keys(), ', ')}"
|
183
|
-
)
|
184
|
-
|
185
|
-
return cls(
|
186
|
-
conda_requirements=conda_requirements,
|
187
|
-
pip_requirements=pip_requirements,
|
188
|
-
python_version=python_version,
|
189
|
-
)
|
190
|
-
|
191
|
-
def install_commands(self) -> List[str]:
|
192
|
-
pip_install_commands = super().install_commands()
|
193
|
-
|
194
|
-
if not self.conda_requirements:
|
195
|
-
return pip_install_commands
|
196
|
-
|
197
|
-
return [
|
198
|
-
["conda", "install", *(str(req) for req in self.conda_requirements)]
|
199
|
-
] + pip_install_commands
|
prefect/software/pip.py
DELETED
@@ -1,122 +0,0 @@
|
|
1
|
-
import site
|
2
|
-
import warnings
|
3
|
-
from pathlib import Path
|
4
|
-
from typing import Dict, List, Type, Union
|
5
|
-
|
6
|
-
import packaging.requirements
|
7
|
-
import packaging.version
|
8
|
-
from typing_extensions import Literal, Self
|
9
|
-
|
10
|
-
from prefect.software.base import Requirement
|
11
|
-
from prefect.utilities.compat import importlib_metadata
|
12
|
-
|
13
|
-
|
14
|
-
class PipRequirement(Requirement, packaging.requirements.Requirement):
|
15
|
-
"""
|
16
|
-
A parsed Python requirement.
|
17
|
-
|
18
|
-
An extension for `packaging.version.Requirement` with Pydantic support.
|
19
|
-
"""
|
20
|
-
|
21
|
-
@classmethod
|
22
|
-
def from_distribution(
|
23
|
-
cls: Type[Self], dist: "importlib_metadata.Distribution"
|
24
|
-
) -> Self:
|
25
|
-
"""
|
26
|
-
Convert a Python distribution object into a requirement
|
27
|
-
"""
|
28
|
-
if _is_editable_install(dist):
|
29
|
-
raise ValueError(
|
30
|
-
f"Distribution {dist!r} is an editable installation and cannot be "
|
31
|
-
"used as a requirement."
|
32
|
-
)
|
33
|
-
|
34
|
-
return cls.validate(
|
35
|
-
packaging.requirements.Requirement(
|
36
|
-
f"{dist.metadata['name']}=={dist.version}"
|
37
|
-
)
|
38
|
-
)
|
39
|
-
|
40
|
-
|
41
|
-
def _get_installed_distributions() -> Dict[str, "importlib_metadata.Distribution"]:
|
42
|
-
return {dist.name: dist for dist in importlib_metadata.distributions()}
|
43
|
-
|
44
|
-
|
45
|
-
def _is_child_path(path: Union[Path, str], parent: Union[Path, str]) -> bool:
|
46
|
-
return Path(parent).resolve() in Path(path).resolve().parents
|
47
|
-
|
48
|
-
|
49
|
-
def _is_same_path(path: Union[Path, str], other: Union[Path, str]) -> bool:
|
50
|
-
return Path(path).resolve() == Path(other).resolve()
|
51
|
-
|
52
|
-
|
53
|
-
def _is_editable_install(dist: "importlib_metadata.Distribution") -> bool:
|
54
|
-
"""
|
55
|
-
Determine if a distribution is an 'editable' install by scanning if it is present
|
56
|
-
in a site-packages directory or not; if not, we presume it is editable.
|
57
|
-
"""
|
58
|
-
site_packages = site.getsitepackages() + [site.getusersitepackages()]
|
59
|
-
|
60
|
-
dist_location = dist.locate_file("")
|
61
|
-
for site_package_dir in site_packages:
|
62
|
-
if _is_same_path(dist_location, site_package_dir) or _is_child_path(
|
63
|
-
dist_location, site_package_dir
|
64
|
-
):
|
65
|
-
return False
|
66
|
-
|
67
|
-
return True
|
68
|
-
|
69
|
-
|
70
|
-
def _remove_distributions_required_by_others(
|
71
|
-
dists: Dict[str, "importlib_metadata.Distribution"],
|
72
|
-
) -> Dict[str, "importlib_metadata.Distribution"]:
|
73
|
-
# Collect all child requirements
|
74
|
-
child_requirement_names = set()
|
75
|
-
for dist in dists.values():
|
76
|
-
child_requirement_names.update(dist.requires or [])
|
77
|
-
|
78
|
-
return {
|
79
|
-
name: dist
|
80
|
-
for name, dist in dists.items()
|
81
|
-
if name not in child_requirement_names
|
82
|
-
}
|
83
|
-
|
84
|
-
|
85
|
-
def current_environment_requirements(
|
86
|
-
exclude_nested: bool = False,
|
87
|
-
on_uninstallable_requirement: Literal["ignore", "warn", "raise"] = "warn",
|
88
|
-
) -> List[PipRequirement]:
|
89
|
-
dists = _get_installed_distributions()
|
90
|
-
if exclude_nested:
|
91
|
-
dists = _remove_distributions_required_by_others(dists)
|
92
|
-
|
93
|
-
requirements = []
|
94
|
-
uninstallable_msgs = []
|
95
|
-
for dist in dists.values():
|
96
|
-
if _is_editable_install(dist):
|
97
|
-
uninstallable_msgs.append(
|
98
|
-
f"- {dist.name}: This distribution is an editable installation."
|
99
|
-
)
|
100
|
-
else:
|
101
|
-
requirements.append(PipRequirement.from_distribution(dist))
|
102
|
-
|
103
|
-
if uninstallable_msgs:
|
104
|
-
message = (
|
105
|
-
"The following requirements will not be installable on another machine:\n"
|
106
|
-
+ "\n".join(uninstallable_msgs)
|
107
|
-
)
|
108
|
-
if on_uninstallable_requirement == "ignore":
|
109
|
-
pass
|
110
|
-
elif on_uninstallable_requirement == "warn":
|
111
|
-
# When warning, include a note that these distributions are excluded
|
112
|
-
warnings.warn(message + "\nThese requirements will be ignored.")
|
113
|
-
elif on_uninstallable_requirement == "raise":
|
114
|
-
raise ValueError(message)
|
115
|
-
else:
|
116
|
-
raise ValueError(
|
117
|
-
"Unknown mode for `on_uninstallable_requirement`: "
|
118
|
-
f"{on_uninstallable_requirement!r}. "
|
119
|
-
"Expected one of: 'ignore', 'warn', 'raise'."
|
120
|
-
)
|
121
|
-
|
122
|
-
return requirements
|
prefect/software/python.py
DELETED
@@ -1,52 +0,0 @@
|
|
1
|
-
from pathlib import Path
|
2
|
-
from typing import List, Type
|
3
|
-
|
4
|
-
from prefect._internal.pydantic import HAS_PYDANTIC_V2
|
5
|
-
from prefect._internal.schemas.validators import infer_python_version
|
6
|
-
|
7
|
-
if HAS_PYDANTIC_V2:
|
8
|
-
from pydantic.v1 import BaseModel, Field, validate_arguments, validator
|
9
|
-
else:
|
10
|
-
from pydantic import BaseModel, Field, validate_arguments, validator
|
11
|
-
|
12
|
-
from typing_extensions import Self
|
13
|
-
|
14
|
-
from prefect.software.pip import PipRequirement, current_environment_requirements
|
15
|
-
|
16
|
-
|
17
|
-
class PythonEnvironment(BaseModel):
|
18
|
-
"""
|
19
|
-
A specification for a Python environment.
|
20
|
-
"""
|
21
|
-
|
22
|
-
python_version: str = None
|
23
|
-
pip_requirements: List[PipRequirement] = Field(default_factory=list)
|
24
|
-
|
25
|
-
@validator("python_version", pre=True, always=True)
|
26
|
-
def validate_python_version(cls, value):
|
27
|
-
return infer_python_version(value)
|
28
|
-
|
29
|
-
@classmethod
|
30
|
-
def from_environment(cls: Type[Self], exclude_nested: bool = False) -> Self:
|
31
|
-
"""
|
32
|
-
Generate requirements from the current environment
|
33
|
-
|
34
|
-
Arguments:
|
35
|
-
exclude_nested: If True, only top-level requirements will be included.
|
36
|
-
Defaults to including all requirements.
|
37
|
-
"""
|
38
|
-
pip_requirements = current_environment_requirements(
|
39
|
-
exclude_nested=exclude_nested, on_uninstallable_requirement="warn"
|
40
|
-
)
|
41
|
-
return cls(pip_requirements=pip_requirements)
|
42
|
-
|
43
|
-
@classmethod
|
44
|
-
@validate_arguments
|
45
|
-
def from_file(cls: Type[Self], path: Path) -> Self:
|
46
|
-
return PythonEnvironment(pip_requirements=path.read_text().strip().splitlines())
|
47
|
-
|
48
|
-
def install_commands(self) -> List[List[str]]:
|
49
|
-
if not self.pip_requirements:
|
50
|
-
return []
|
51
|
-
|
52
|
-
return [["pip", "install", *(str(req) for req in self.pip_requirements)]]
|
prefect/workers/block.py
DELETED
@@ -1,218 +0,0 @@
|
|
1
|
-
import shlex
|
2
|
-
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
3
|
-
|
4
|
-
import anyio
|
5
|
-
import anyio.abc
|
6
|
-
|
7
|
-
from prefect._internal.pydantic import HAS_PYDANTIC_V2
|
8
|
-
from prefect._internal.schemas.validators import validate_block_is_infrastructure
|
9
|
-
from prefect.blocks.core import Block
|
10
|
-
from prefect.client.schemas.objects import BlockDocument
|
11
|
-
from prefect.utilities.collections import get_from_dict
|
12
|
-
from prefect.workers.base import BaseWorker, BaseWorkerResult
|
13
|
-
|
14
|
-
if HAS_PYDANTIC_V2:
|
15
|
-
from pydantic.v1 import BaseModel, Field, PrivateAttr, validator
|
16
|
-
else:
|
17
|
-
from pydantic import BaseModel, Field, PrivateAttr, validator
|
18
|
-
|
19
|
-
from prefect.client.orchestration import PrefectClient
|
20
|
-
from prefect.client.utilities import inject_client
|
21
|
-
from prefect.events import RelatedResource
|
22
|
-
from prefect.events.related import object_as_related_resource, tags_as_related_resources
|
23
|
-
from prefect.utilities.templating import apply_values
|
24
|
-
|
25
|
-
if TYPE_CHECKING:
|
26
|
-
from prefect.client.schemas.objects import Flow, FlowRun
|
27
|
-
from prefect.client.schemas.responses import DeploymentResponse
|
28
|
-
|
29
|
-
|
30
|
-
class BlockWorkerJobConfiguration(BaseModel):
|
31
|
-
block: Block = Field(
|
32
|
-
default=..., description="The infrastructure block to use for job creation."
|
33
|
-
)
|
34
|
-
|
35
|
-
@validator("block")
|
36
|
-
def _validate_infrastructure_block(cls, v):
|
37
|
-
return validate_block_is_infrastructure(v)
|
38
|
-
|
39
|
-
_related_objects: Dict[str, Any] = PrivateAttr(default_factory=dict)
|
40
|
-
|
41
|
-
@property
|
42
|
-
def is_using_a_runner(self):
|
43
|
-
return (
|
44
|
-
self.block.command is not None
|
45
|
-
and "prefect flow-run execute" in shlex.join(self.block.command)
|
46
|
-
)
|
47
|
-
|
48
|
-
@staticmethod
|
49
|
-
def _get_base_config_defaults(variables: dict) -> dict:
|
50
|
-
"""Get default values from base config for all variables that have them."""
|
51
|
-
defaults = dict()
|
52
|
-
for variable_name, attrs in variables.items():
|
53
|
-
if "default" in attrs:
|
54
|
-
defaults[variable_name] = attrs["default"]
|
55
|
-
|
56
|
-
return defaults
|
57
|
-
|
58
|
-
@classmethod
|
59
|
-
@inject_client
|
60
|
-
async def from_template_and_values(
|
61
|
-
cls, base_job_template: dict, values: dict, client: "PrefectClient" = None
|
62
|
-
):
|
63
|
-
"""Creates a valid worker configuration object from the provided base
|
64
|
-
configuration and overrides.
|
65
|
-
|
66
|
-
Important: this method expects that the base_job_template was already
|
67
|
-
validated server-side.
|
68
|
-
"""
|
69
|
-
job_config: Dict[str, Any] = base_job_template["job_configuration"]
|
70
|
-
variables_schema = base_job_template["variables"]
|
71
|
-
variables = cls._get_base_config_defaults(
|
72
|
-
variables_schema.get("properties", {})
|
73
|
-
)
|
74
|
-
variables.update(values)
|
75
|
-
|
76
|
-
populated_configuration = apply_values(template=job_config, values=variables)
|
77
|
-
|
78
|
-
block_document_id = get_from_dict(
|
79
|
-
populated_configuration, "block.$ref.block_document_id"
|
80
|
-
)
|
81
|
-
if not block_document_id:
|
82
|
-
raise ValueError(
|
83
|
-
"Base job template is invalid for this worker type because it does not"
|
84
|
-
" contain a block_document_id after variable resolution."
|
85
|
-
)
|
86
|
-
|
87
|
-
block_document = await client.read_block_document(
|
88
|
-
block_document_id=block_document_id
|
89
|
-
)
|
90
|
-
infrastructure_block = Block._from_block_document(block_document)
|
91
|
-
|
92
|
-
populated_configuration["block"] = infrastructure_block
|
93
|
-
|
94
|
-
return cls(**populated_configuration)
|
95
|
-
|
96
|
-
@classmethod
|
97
|
-
def json_template(cls) -> dict:
|
98
|
-
"""Returns a dict with job configuration as keys and the corresponding templates as values
|
99
|
-
|
100
|
-
Defaults to using the job configuration parameter name as the template variable name.
|
101
|
-
|
102
|
-
e.g.
|
103
|
-
{
|
104
|
-
key1: '{{ key1 }}', # default variable template
|
105
|
-
key2: '{{ template2 }}', # `template2` specifically provide as template
|
106
|
-
}
|
107
|
-
"""
|
108
|
-
configuration = {}
|
109
|
-
properties = cls.schema()["properties"]
|
110
|
-
for k, v in properties.items():
|
111
|
-
if v.get("template"):
|
112
|
-
template = v["template"]
|
113
|
-
else:
|
114
|
-
template = "{{ " + k + " }}"
|
115
|
-
configuration[k] = template
|
116
|
-
|
117
|
-
return configuration
|
118
|
-
|
119
|
-
def _related_resources(self) -> List[RelatedResource]:
|
120
|
-
tags = set()
|
121
|
-
related = []
|
122
|
-
|
123
|
-
for kind, obj in self._related_objects.items():
|
124
|
-
if obj is None:
|
125
|
-
continue
|
126
|
-
if hasattr(obj, "tags"):
|
127
|
-
tags.update(obj.tags)
|
128
|
-
related.append(object_as_related_resource(kind=kind, role=kind, object=obj))
|
129
|
-
|
130
|
-
return related + tags_as_related_resources(tags)
|
131
|
-
|
132
|
-
def prepare_for_flow_run(
|
133
|
-
self,
|
134
|
-
flow_run: "FlowRun",
|
135
|
-
deployment: Optional["DeploymentResponse"] = None,
|
136
|
-
flow: Optional["Flow"] = None,
|
137
|
-
):
|
138
|
-
self.block = self.block.prepare_for_flow_run(
|
139
|
-
flow_run=flow_run, deployment=deployment, flow=flow
|
140
|
-
)
|
141
|
-
|
142
|
-
|
143
|
-
class BlockWorkerResult(BaseWorkerResult):
|
144
|
-
"""Result of a block worker job"""
|
145
|
-
|
146
|
-
|
147
|
-
class BlockWorker(BaseWorker):
|
148
|
-
type = "block"
|
149
|
-
job_configuration = BlockWorkerJobConfiguration
|
150
|
-
|
151
|
-
_description = "Execute flow runs using an infrastructure block as the job creator."
|
152
|
-
_display_name = "Block"
|
153
|
-
|
154
|
-
async def run(
|
155
|
-
self,
|
156
|
-
flow_run: "FlowRun",
|
157
|
-
configuration: BlockWorkerJobConfiguration,
|
158
|
-
task_status: Optional[anyio.abc.TaskStatus] = None,
|
159
|
-
):
|
160
|
-
block = configuration.block
|
161
|
-
|
162
|
-
# logic for applying infra overrides taken from src/prefect/agent.py
|
163
|
-
deployment = await self._client.read_deployment(flow_run.deployment_id)
|
164
|
-
flow = await self._client.read_flow(deployment.flow_id)
|
165
|
-
infra_document = await self._client.read_block_document(
|
166
|
-
configuration.block._block_document_id
|
167
|
-
)
|
168
|
-
|
169
|
-
# this piece of logic applies any overrides that may have been set on the
|
170
|
-
# deployment; overrides are defined as dot.delimited paths on possibly nested
|
171
|
-
# attributes of the infrastructure block
|
172
|
-
doc_dict = infra_document.dict()
|
173
|
-
infra_dict = doc_dict.get("data", {})
|
174
|
-
for override, value in (deployment.job_variables or {}).items():
|
175
|
-
nested_fields = override.split(".")
|
176
|
-
if nested_fields == ["command"]:
|
177
|
-
value = shlex.split(value)
|
178
|
-
data = infra_dict
|
179
|
-
for field in nested_fields[:-1]:
|
180
|
-
data = data[field]
|
181
|
-
|
182
|
-
# once we reach the end, set the value
|
183
|
-
data[nested_fields[-1]] = value
|
184
|
-
|
185
|
-
# reconstruct the infra block
|
186
|
-
doc_dict["data"] = infra_dict
|
187
|
-
infra_document = BlockDocument(**doc_dict)
|
188
|
-
block = Block._from_block_document(infra_document)
|
189
|
-
|
190
|
-
block = block.prepare_for_flow_run(
|
191
|
-
flow_run=flow_run, deployment=deployment, flow=flow
|
192
|
-
)
|
193
|
-
|
194
|
-
result = await block.run(
|
195
|
-
task_status=task_status,
|
196
|
-
)
|
197
|
-
return BlockWorkerResult(
|
198
|
-
identifier=result.identifier, status_code=result.status_code
|
199
|
-
)
|
200
|
-
|
201
|
-
async def kill_infrastructure(
|
202
|
-
self,
|
203
|
-
infrastructure_pid: str,
|
204
|
-
configuration: BlockWorkerJobConfiguration,
|
205
|
-
grace_seconds: int = 30,
|
206
|
-
):
|
207
|
-
block = configuration.block
|
208
|
-
if not hasattr(block, "kill"):
|
209
|
-
self._logger.error(
|
210
|
-
f"Flow run infrastructure block {block.type!r} "
|
211
|
-
"does not support killing created infrastructure. "
|
212
|
-
"Cancellation cannot be guaranteed."
|
213
|
-
)
|
214
|
-
return
|
215
|
-
|
216
|
-
await block.kill(
|
217
|
-
infrastructure_pid=infrastructure_pid, grace_seconds=grace_seconds
|
218
|
-
)
|