prefect-client 2.19.4__py3-none-any.whl → 3.0.0rc2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. prefect/__init__.py +8 -56
  2. prefect/_internal/compatibility/deprecated.py +6 -115
  3. prefect/_internal/compatibility/experimental.py +4 -79
  4. prefect/_internal/concurrency/api.py +0 -34
  5. prefect/_internal/concurrency/calls.py +0 -6
  6. prefect/_internal/concurrency/cancellation.py +0 -3
  7. prefect/_internal/concurrency/event_loop.py +0 -20
  8. prefect/_internal/concurrency/inspection.py +3 -3
  9. prefect/_internal/concurrency/threads.py +35 -0
  10. prefect/_internal/concurrency/waiters.py +0 -28
  11. prefect/_internal/pydantic/__init__.py +0 -45
  12. prefect/_internal/pydantic/v1_schema.py +21 -22
  13. prefect/_internal/pydantic/v2_schema.py +0 -2
  14. prefect/_internal/pydantic/v2_validated_func.py +18 -23
  15. prefect/_internal/schemas/bases.py +44 -177
  16. prefect/_internal/schemas/fields.py +1 -43
  17. prefect/_internal/schemas/validators.py +60 -158
  18. prefect/artifacts.py +161 -14
  19. prefect/automations.py +39 -4
  20. prefect/blocks/abstract.py +1 -1
  21. prefect/blocks/core.py +268 -148
  22. prefect/blocks/fields.py +2 -57
  23. prefect/blocks/kubernetes.py +8 -12
  24. prefect/blocks/notifications.py +40 -20
  25. prefect/blocks/redis.py +168 -0
  26. prefect/blocks/system.py +22 -11
  27. prefect/blocks/webhook.py +2 -9
  28. prefect/client/base.py +4 -4
  29. prefect/client/cloud.py +8 -13
  30. prefect/client/orchestration.py +362 -340
  31. prefect/client/schemas/actions.py +92 -86
  32. prefect/client/schemas/filters.py +20 -40
  33. prefect/client/schemas/objects.py +158 -152
  34. prefect/client/schemas/responses.py +16 -24
  35. prefect/client/schemas/schedules.py +47 -35
  36. prefect/client/subscriptions.py +2 -2
  37. prefect/client/utilities.py +5 -2
  38. prefect/concurrency/asyncio.py +4 -2
  39. prefect/concurrency/events.py +1 -1
  40. prefect/concurrency/services.py +7 -4
  41. prefect/context.py +195 -27
  42. prefect/deployments/__init__.py +5 -6
  43. prefect/deployments/base.py +7 -5
  44. prefect/deployments/flow_runs.py +185 -0
  45. prefect/deployments/runner.py +50 -45
  46. prefect/deployments/schedules.py +28 -23
  47. prefect/deployments/steps/__init__.py +0 -1
  48. prefect/deployments/steps/core.py +1 -0
  49. prefect/deployments/steps/pull.py +7 -21
  50. prefect/engine.py +12 -2422
  51. prefect/events/actions.py +17 -23
  52. prefect/events/cli/automations.py +19 -6
  53. prefect/events/clients.py +14 -37
  54. prefect/events/filters.py +14 -18
  55. prefect/events/related.py +2 -2
  56. prefect/events/schemas/__init__.py +0 -5
  57. prefect/events/schemas/automations.py +55 -46
  58. prefect/events/schemas/deployment_triggers.py +7 -197
  59. prefect/events/schemas/events.py +36 -65
  60. prefect/events/schemas/labelling.py +10 -14
  61. prefect/events/utilities.py +2 -3
  62. prefect/events/worker.py +2 -3
  63. prefect/filesystems.py +6 -517
  64. prefect/{new_flow_engine.py → flow_engine.py} +315 -74
  65. prefect/flow_runs.py +379 -7
  66. prefect/flows.py +248 -165
  67. prefect/futures.py +187 -345
  68. prefect/infrastructure/__init__.py +0 -27
  69. prefect/infrastructure/provisioners/__init__.py +5 -3
  70. prefect/infrastructure/provisioners/cloud_run.py +11 -6
  71. prefect/infrastructure/provisioners/container_instance.py +11 -7
  72. prefect/infrastructure/provisioners/ecs.py +6 -4
  73. prefect/infrastructure/provisioners/modal.py +8 -5
  74. prefect/input/actions.py +2 -4
  75. prefect/input/run_input.py +9 -9
  76. prefect/logging/formatters.py +0 -2
  77. prefect/logging/handlers.py +3 -11
  78. prefect/logging/loggers.py +2 -2
  79. prefect/manifests.py +2 -1
  80. prefect/records/__init__.py +1 -0
  81. prefect/records/cache_policies.py +179 -0
  82. prefect/records/result_store.py +42 -0
  83. prefect/records/store.py +9 -0
  84. prefect/results.py +43 -39
  85. prefect/runner/runner.py +9 -9
  86. prefect/runner/server.py +6 -10
  87. prefect/runner/storage.py +3 -8
  88. prefect/runner/submit.py +2 -2
  89. prefect/runner/utils.py +2 -2
  90. prefect/serializers.py +24 -35
  91. prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
  92. prefect/settings.py +76 -136
  93. prefect/states.py +22 -50
  94. prefect/task_engine.py +666 -56
  95. prefect/task_runners.py +272 -300
  96. prefect/task_runs.py +203 -0
  97. prefect/{task_server.py → task_worker.py} +89 -60
  98. prefect/tasks.py +358 -341
  99. prefect/transactions.py +224 -0
  100. prefect/types/__init__.py +61 -82
  101. prefect/utilities/asyncutils.py +195 -136
  102. prefect/utilities/callables.py +121 -41
  103. prefect/utilities/collections.py +23 -38
  104. prefect/utilities/dispatch.py +11 -3
  105. prefect/utilities/dockerutils.py +4 -0
  106. prefect/utilities/engine.py +140 -20
  107. prefect/utilities/importtools.py +26 -27
  108. prefect/utilities/pydantic.py +128 -38
  109. prefect/utilities/schema_tools/hydration.py +5 -1
  110. prefect/utilities/templating.py +12 -2
  111. prefect/variables.py +84 -62
  112. prefect/workers/__init__.py +0 -1
  113. prefect/workers/base.py +26 -18
  114. prefect/workers/process.py +3 -8
  115. prefect/workers/server.py +2 -2
  116. {prefect_client-2.19.4.dist-info → prefect_client-3.0.0rc2.dist-info}/METADATA +23 -21
  117. prefect_client-3.0.0rc2.dist-info/RECORD +179 -0
  118. prefect/_internal/pydantic/_base_model.py +0 -51
  119. prefect/_internal/pydantic/_compat.py +0 -82
  120. prefect/_internal/pydantic/_flags.py +0 -20
  121. prefect/_internal/pydantic/_types.py +0 -8
  122. prefect/_internal/pydantic/utilities/__init__.py +0 -0
  123. prefect/_internal/pydantic/utilities/config_dict.py +0 -72
  124. prefect/_internal/pydantic/utilities/field_validator.py +0 -150
  125. prefect/_internal/pydantic/utilities/model_construct.py +0 -56
  126. prefect/_internal/pydantic/utilities/model_copy.py +0 -55
  127. prefect/_internal/pydantic/utilities/model_dump.py +0 -136
  128. prefect/_internal/pydantic/utilities/model_dump_json.py +0 -112
  129. prefect/_internal/pydantic/utilities/model_fields.py +0 -50
  130. prefect/_internal/pydantic/utilities/model_fields_set.py +0 -29
  131. prefect/_internal/pydantic/utilities/model_json_schema.py +0 -82
  132. prefect/_internal/pydantic/utilities/model_rebuild.py +0 -80
  133. prefect/_internal/pydantic/utilities/model_validate.py +0 -75
  134. prefect/_internal/pydantic/utilities/model_validate_json.py +0 -68
  135. prefect/_internal/pydantic/utilities/model_validator.py +0 -87
  136. prefect/_internal/pydantic/utilities/type_adapter.py +0 -71
  137. prefect/_vendor/__init__.py +0 -0
  138. prefect/_vendor/fastapi/__init__.py +0 -25
  139. prefect/_vendor/fastapi/applications.py +0 -946
  140. prefect/_vendor/fastapi/background.py +0 -3
  141. prefect/_vendor/fastapi/concurrency.py +0 -44
  142. prefect/_vendor/fastapi/datastructures.py +0 -58
  143. prefect/_vendor/fastapi/dependencies/__init__.py +0 -0
  144. prefect/_vendor/fastapi/dependencies/models.py +0 -64
  145. prefect/_vendor/fastapi/dependencies/utils.py +0 -877
  146. prefect/_vendor/fastapi/encoders.py +0 -177
  147. prefect/_vendor/fastapi/exception_handlers.py +0 -40
  148. prefect/_vendor/fastapi/exceptions.py +0 -46
  149. prefect/_vendor/fastapi/logger.py +0 -3
  150. prefect/_vendor/fastapi/middleware/__init__.py +0 -1
  151. prefect/_vendor/fastapi/middleware/asyncexitstack.py +0 -25
  152. prefect/_vendor/fastapi/middleware/cors.py +0 -3
  153. prefect/_vendor/fastapi/middleware/gzip.py +0 -3
  154. prefect/_vendor/fastapi/middleware/httpsredirect.py +0 -3
  155. prefect/_vendor/fastapi/middleware/trustedhost.py +0 -3
  156. prefect/_vendor/fastapi/middleware/wsgi.py +0 -3
  157. prefect/_vendor/fastapi/openapi/__init__.py +0 -0
  158. prefect/_vendor/fastapi/openapi/constants.py +0 -2
  159. prefect/_vendor/fastapi/openapi/docs.py +0 -203
  160. prefect/_vendor/fastapi/openapi/models.py +0 -480
  161. prefect/_vendor/fastapi/openapi/utils.py +0 -485
  162. prefect/_vendor/fastapi/param_functions.py +0 -340
  163. prefect/_vendor/fastapi/params.py +0 -453
  164. prefect/_vendor/fastapi/requests.py +0 -4
  165. prefect/_vendor/fastapi/responses.py +0 -40
  166. prefect/_vendor/fastapi/routing.py +0 -1331
  167. prefect/_vendor/fastapi/security/__init__.py +0 -15
  168. prefect/_vendor/fastapi/security/api_key.py +0 -98
  169. prefect/_vendor/fastapi/security/base.py +0 -6
  170. prefect/_vendor/fastapi/security/http.py +0 -172
  171. prefect/_vendor/fastapi/security/oauth2.py +0 -227
  172. prefect/_vendor/fastapi/security/open_id_connect_url.py +0 -34
  173. prefect/_vendor/fastapi/security/utils.py +0 -10
  174. prefect/_vendor/fastapi/staticfiles.py +0 -1
  175. prefect/_vendor/fastapi/templating.py +0 -3
  176. prefect/_vendor/fastapi/testclient.py +0 -1
  177. prefect/_vendor/fastapi/types.py +0 -3
  178. prefect/_vendor/fastapi/utils.py +0 -235
  179. prefect/_vendor/fastapi/websockets.py +0 -7
  180. prefect/_vendor/starlette/__init__.py +0 -1
  181. prefect/_vendor/starlette/_compat.py +0 -28
  182. prefect/_vendor/starlette/_exception_handler.py +0 -80
  183. prefect/_vendor/starlette/_utils.py +0 -88
  184. prefect/_vendor/starlette/applications.py +0 -261
  185. prefect/_vendor/starlette/authentication.py +0 -159
  186. prefect/_vendor/starlette/background.py +0 -43
  187. prefect/_vendor/starlette/concurrency.py +0 -59
  188. prefect/_vendor/starlette/config.py +0 -151
  189. prefect/_vendor/starlette/convertors.py +0 -87
  190. prefect/_vendor/starlette/datastructures.py +0 -707
  191. prefect/_vendor/starlette/endpoints.py +0 -130
  192. prefect/_vendor/starlette/exceptions.py +0 -60
  193. prefect/_vendor/starlette/formparsers.py +0 -276
  194. prefect/_vendor/starlette/middleware/__init__.py +0 -17
  195. prefect/_vendor/starlette/middleware/authentication.py +0 -52
  196. prefect/_vendor/starlette/middleware/base.py +0 -220
  197. prefect/_vendor/starlette/middleware/cors.py +0 -176
  198. prefect/_vendor/starlette/middleware/errors.py +0 -265
  199. prefect/_vendor/starlette/middleware/exceptions.py +0 -74
  200. prefect/_vendor/starlette/middleware/gzip.py +0 -113
  201. prefect/_vendor/starlette/middleware/httpsredirect.py +0 -19
  202. prefect/_vendor/starlette/middleware/sessions.py +0 -82
  203. prefect/_vendor/starlette/middleware/trustedhost.py +0 -64
  204. prefect/_vendor/starlette/middleware/wsgi.py +0 -147
  205. prefect/_vendor/starlette/requests.py +0 -328
  206. prefect/_vendor/starlette/responses.py +0 -347
  207. prefect/_vendor/starlette/routing.py +0 -933
  208. prefect/_vendor/starlette/schemas.py +0 -154
  209. prefect/_vendor/starlette/staticfiles.py +0 -248
  210. prefect/_vendor/starlette/status.py +0 -199
  211. prefect/_vendor/starlette/templating.py +0 -231
  212. prefect/_vendor/starlette/testclient.py +0 -804
  213. prefect/_vendor/starlette/types.py +0 -30
  214. prefect/_vendor/starlette/websockets.py +0 -193
  215. prefect/agent.py +0 -698
  216. prefect/deployments/deployments.py +0 -1042
  217. prefect/deprecated/__init__.py +0 -0
  218. prefect/deprecated/data_documents.py +0 -350
  219. prefect/deprecated/packaging/__init__.py +0 -12
  220. prefect/deprecated/packaging/base.py +0 -96
  221. prefect/deprecated/packaging/docker.py +0 -146
  222. prefect/deprecated/packaging/file.py +0 -92
  223. prefect/deprecated/packaging/orion.py +0 -80
  224. prefect/deprecated/packaging/serializers.py +0 -171
  225. prefect/events/instrument.py +0 -135
  226. prefect/infrastructure/base.py +0 -323
  227. prefect/infrastructure/container.py +0 -818
  228. prefect/infrastructure/kubernetes.py +0 -920
  229. prefect/infrastructure/process.py +0 -289
  230. prefect/new_task_engine.py +0 -423
  231. prefect/pydantic/__init__.py +0 -76
  232. prefect/pydantic/main.py +0 -39
  233. prefect/software/__init__.py +0 -2
  234. prefect/software/base.py +0 -50
  235. prefect/software/conda.py +0 -199
  236. prefect/software/pip.py +0 -122
  237. prefect/software/python.py +0 -52
  238. prefect/workers/block.py +0 -218
  239. prefect_client-2.19.4.dist-info/RECORD +0 -292
  240. {prefect_client-2.19.4.dist-info → prefect_client-3.0.0rc2.dist-info}/LICENSE +0 -0
  241. {prefect_client-2.19.4.dist-info → prefect_client-3.0.0rc2.dist-info}/WHEEL +0 -0
  242. {prefect_client-2.19.4.dist-info → prefect_client-3.0.0rc2.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
@@ -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
- )