prefect-client 2.20.4__py3-none-any.whl → 3.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. prefect/__init__.py +74 -110
  2. prefect/_internal/compatibility/deprecated.py +6 -115
  3. prefect/_internal/compatibility/experimental.py +4 -79
  4. prefect/_internal/compatibility/migration.py +166 -0
  5. prefect/_internal/concurrency/__init__.py +2 -2
  6. prefect/_internal/concurrency/api.py +1 -35
  7. prefect/_internal/concurrency/calls.py +0 -6
  8. prefect/_internal/concurrency/cancellation.py +0 -3
  9. prefect/_internal/concurrency/event_loop.py +0 -20
  10. prefect/_internal/concurrency/inspection.py +3 -3
  11. prefect/_internal/concurrency/primitives.py +1 -0
  12. prefect/_internal/concurrency/services.py +23 -0
  13. prefect/_internal/concurrency/threads.py +35 -0
  14. prefect/_internal/concurrency/waiters.py +0 -28
  15. prefect/_internal/integrations.py +7 -0
  16. prefect/_internal/pydantic/__init__.py +0 -45
  17. prefect/_internal/pydantic/annotations/pendulum.py +2 -2
  18. prefect/_internal/pydantic/v1_schema.py +21 -22
  19. prefect/_internal/pydantic/v2_schema.py +0 -2
  20. prefect/_internal/pydantic/v2_validated_func.py +18 -23
  21. prefect/_internal/pytz.py +1 -1
  22. prefect/_internal/retries.py +61 -0
  23. prefect/_internal/schemas/bases.py +45 -177
  24. prefect/_internal/schemas/fields.py +1 -43
  25. prefect/_internal/schemas/validators.py +47 -233
  26. prefect/agent.py +3 -695
  27. prefect/artifacts.py +173 -14
  28. prefect/automations.py +39 -4
  29. prefect/blocks/abstract.py +1 -1
  30. prefect/blocks/core.py +405 -153
  31. prefect/blocks/fields.py +2 -57
  32. prefect/blocks/notifications.py +43 -28
  33. prefect/blocks/redis.py +168 -0
  34. prefect/blocks/system.py +67 -20
  35. prefect/blocks/webhook.py +2 -9
  36. prefect/cache_policies.py +239 -0
  37. prefect/client/__init__.py +4 -0
  38. prefect/client/base.py +33 -27
  39. prefect/client/cloud.py +65 -20
  40. prefect/client/collections.py +1 -1
  41. prefect/client/orchestration.py +650 -442
  42. prefect/client/schemas/actions.py +115 -100
  43. prefect/client/schemas/filters.py +46 -52
  44. prefect/client/schemas/objects.py +228 -178
  45. prefect/client/schemas/responses.py +18 -36
  46. prefect/client/schemas/schedules.py +55 -36
  47. prefect/client/schemas/sorting.py +2 -0
  48. prefect/client/subscriptions.py +8 -7
  49. prefect/client/types/flexible_schedule_list.py +11 -0
  50. prefect/client/utilities.py +9 -6
  51. prefect/concurrency/asyncio.py +60 -11
  52. prefect/concurrency/context.py +24 -0
  53. prefect/concurrency/events.py +2 -2
  54. prefect/concurrency/services.py +46 -16
  55. prefect/concurrency/sync.py +51 -7
  56. prefect/concurrency/v1/asyncio.py +143 -0
  57. prefect/concurrency/v1/context.py +27 -0
  58. prefect/concurrency/v1/events.py +61 -0
  59. prefect/concurrency/v1/services.py +116 -0
  60. prefect/concurrency/v1/sync.py +92 -0
  61. prefect/context.py +246 -149
  62. prefect/deployments/__init__.py +33 -18
  63. prefect/deployments/base.py +10 -15
  64. prefect/deployments/deployments.py +2 -1048
  65. prefect/deployments/flow_runs.py +178 -0
  66. prefect/deployments/runner.py +72 -173
  67. prefect/deployments/schedules.py +31 -25
  68. prefect/deployments/steps/__init__.py +0 -1
  69. prefect/deployments/steps/core.py +7 -0
  70. prefect/deployments/steps/pull.py +15 -21
  71. prefect/deployments/steps/utility.py +2 -1
  72. prefect/docker/__init__.py +20 -0
  73. prefect/docker/docker_image.py +82 -0
  74. prefect/engine.py +15 -2475
  75. prefect/events/actions.py +17 -23
  76. prefect/events/cli/automations.py +20 -7
  77. prefect/events/clients.py +142 -80
  78. prefect/events/filters.py +14 -18
  79. prefect/events/related.py +74 -75
  80. prefect/events/schemas/__init__.py +0 -5
  81. prefect/events/schemas/automations.py +55 -46
  82. prefect/events/schemas/deployment_triggers.py +7 -197
  83. prefect/events/schemas/events.py +46 -65
  84. prefect/events/schemas/labelling.py +10 -14
  85. prefect/events/utilities.py +4 -5
  86. prefect/events/worker.py +23 -8
  87. prefect/exceptions.py +15 -0
  88. prefect/filesystems.py +30 -529
  89. prefect/flow_engine.py +827 -0
  90. prefect/flow_runs.py +379 -7
  91. prefect/flows.py +470 -360
  92. prefect/futures.py +382 -331
  93. prefect/infrastructure/__init__.py +5 -26
  94. prefect/infrastructure/base.py +3 -320
  95. prefect/infrastructure/provisioners/__init__.py +5 -3
  96. prefect/infrastructure/provisioners/cloud_run.py +13 -8
  97. prefect/infrastructure/provisioners/container_instance.py +14 -9
  98. prefect/infrastructure/provisioners/ecs.py +10 -8
  99. prefect/infrastructure/provisioners/modal.py +8 -5
  100. prefect/input/__init__.py +4 -0
  101. prefect/input/actions.py +2 -4
  102. prefect/input/run_input.py +9 -9
  103. prefect/logging/formatters.py +2 -4
  104. prefect/logging/handlers.py +9 -14
  105. prefect/logging/loggers.py +5 -5
  106. prefect/main.py +72 -0
  107. prefect/plugins.py +2 -64
  108. prefect/profiles.toml +16 -2
  109. prefect/records/__init__.py +1 -0
  110. prefect/records/base.py +223 -0
  111. prefect/records/filesystem.py +207 -0
  112. prefect/records/memory.py +178 -0
  113. prefect/records/result_store.py +64 -0
  114. prefect/results.py +577 -504
  115. prefect/runner/runner.py +117 -47
  116. prefect/runner/server.py +32 -34
  117. prefect/runner/storage.py +3 -12
  118. prefect/runner/submit.py +2 -10
  119. prefect/runner/utils.py +2 -2
  120. prefect/runtime/__init__.py +1 -0
  121. prefect/runtime/deployment.py +1 -0
  122. prefect/runtime/flow_run.py +40 -5
  123. prefect/runtime/task_run.py +1 -0
  124. prefect/serializers.py +28 -39
  125. prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
  126. prefect/settings.py +209 -332
  127. prefect/states.py +160 -63
  128. prefect/task_engine.py +1478 -57
  129. prefect/task_runners.py +383 -287
  130. prefect/task_runs.py +240 -0
  131. prefect/task_worker.py +463 -0
  132. prefect/tasks.py +684 -374
  133. prefect/transactions.py +410 -0
  134. prefect/types/__init__.py +72 -86
  135. prefect/types/entrypoint.py +13 -0
  136. prefect/utilities/annotations.py +4 -3
  137. prefect/utilities/asyncutils.py +227 -148
  138. prefect/utilities/callables.py +137 -45
  139. prefect/utilities/collections.py +134 -86
  140. prefect/utilities/dispatch.py +27 -14
  141. prefect/utilities/dockerutils.py +11 -4
  142. prefect/utilities/engine.py +186 -32
  143. prefect/utilities/filesystem.py +4 -5
  144. prefect/utilities/importtools.py +26 -27
  145. prefect/utilities/pydantic.py +128 -38
  146. prefect/utilities/schema_tools/hydration.py +18 -1
  147. prefect/utilities/schema_tools/validation.py +30 -0
  148. prefect/utilities/services.py +35 -9
  149. prefect/utilities/templating.py +12 -2
  150. prefect/utilities/timeout.py +20 -5
  151. prefect/utilities/urls.py +195 -0
  152. prefect/utilities/visualization.py +1 -0
  153. prefect/variables.py +78 -59
  154. prefect/workers/__init__.py +0 -1
  155. prefect/workers/base.py +237 -244
  156. prefect/workers/block.py +5 -226
  157. prefect/workers/cloud.py +6 -0
  158. prefect/workers/process.py +265 -12
  159. prefect/workers/server.py +29 -11
  160. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/METADATA +28 -24
  161. prefect_client-3.0.0.dist-info/RECORD +201 -0
  162. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/WHEEL +1 -1
  163. prefect/_internal/pydantic/_base_model.py +0 -51
  164. prefect/_internal/pydantic/_compat.py +0 -82
  165. prefect/_internal/pydantic/_flags.py +0 -20
  166. prefect/_internal/pydantic/_types.py +0 -8
  167. prefect/_internal/pydantic/utilities/config_dict.py +0 -72
  168. prefect/_internal/pydantic/utilities/field_validator.py +0 -150
  169. prefect/_internal/pydantic/utilities/model_construct.py +0 -56
  170. prefect/_internal/pydantic/utilities/model_copy.py +0 -55
  171. prefect/_internal/pydantic/utilities/model_dump.py +0 -136
  172. prefect/_internal/pydantic/utilities/model_dump_json.py +0 -112
  173. prefect/_internal/pydantic/utilities/model_fields.py +0 -50
  174. prefect/_internal/pydantic/utilities/model_fields_set.py +0 -29
  175. prefect/_internal/pydantic/utilities/model_json_schema.py +0 -82
  176. prefect/_internal/pydantic/utilities/model_rebuild.py +0 -80
  177. prefect/_internal/pydantic/utilities/model_validate.py +0 -75
  178. prefect/_internal/pydantic/utilities/model_validate_json.py +0 -68
  179. prefect/_internal/pydantic/utilities/model_validator.py +0 -87
  180. prefect/_internal/pydantic/utilities/type_adapter.py +0 -71
  181. prefect/_vendor/fastapi/__init__.py +0 -25
  182. prefect/_vendor/fastapi/applications.py +0 -946
  183. prefect/_vendor/fastapi/background.py +0 -3
  184. prefect/_vendor/fastapi/concurrency.py +0 -44
  185. prefect/_vendor/fastapi/datastructures.py +0 -58
  186. prefect/_vendor/fastapi/dependencies/__init__.py +0 -0
  187. prefect/_vendor/fastapi/dependencies/models.py +0 -64
  188. prefect/_vendor/fastapi/dependencies/utils.py +0 -877
  189. prefect/_vendor/fastapi/encoders.py +0 -177
  190. prefect/_vendor/fastapi/exception_handlers.py +0 -40
  191. prefect/_vendor/fastapi/exceptions.py +0 -46
  192. prefect/_vendor/fastapi/logger.py +0 -3
  193. prefect/_vendor/fastapi/middleware/__init__.py +0 -1
  194. prefect/_vendor/fastapi/middleware/asyncexitstack.py +0 -25
  195. prefect/_vendor/fastapi/middleware/cors.py +0 -3
  196. prefect/_vendor/fastapi/middleware/gzip.py +0 -3
  197. prefect/_vendor/fastapi/middleware/httpsredirect.py +0 -3
  198. prefect/_vendor/fastapi/middleware/trustedhost.py +0 -3
  199. prefect/_vendor/fastapi/middleware/wsgi.py +0 -3
  200. prefect/_vendor/fastapi/openapi/__init__.py +0 -0
  201. prefect/_vendor/fastapi/openapi/constants.py +0 -2
  202. prefect/_vendor/fastapi/openapi/docs.py +0 -203
  203. prefect/_vendor/fastapi/openapi/models.py +0 -480
  204. prefect/_vendor/fastapi/openapi/utils.py +0 -485
  205. prefect/_vendor/fastapi/param_functions.py +0 -340
  206. prefect/_vendor/fastapi/params.py +0 -453
  207. prefect/_vendor/fastapi/py.typed +0 -0
  208. prefect/_vendor/fastapi/requests.py +0 -4
  209. prefect/_vendor/fastapi/responses.py +0 -40
  210. prefect/_vendor/fastapi/routing.py +0 -1331
  211. prefect/_vendor/fastapi/security/__init__.py +0 -15
  212. prefect/_vendor/fastapi/security/api_key.py +0 -98
  213. prefect/_vendor/fastapi/security/base.py +0 -6
  214. prefect/_vendor/fastapi/security/http.py +0 -172
  215. prefect/_vendor/fastapi/security/oauth2.py +0 -227
  216. prefect/_vendor/fastapi/security/open_id_connect_url.py +0 -34
  217. prefect/_vendor/fastapi/security/utils.py +0 -10
  218. prefect/_vendor/fastapi/staticfiles.py +0 -1
  219. prefect/_vendor/fastapi/templating.py +0 -3
  220. prefect/_vendor/fastapi/testclient.py +0 -1
  221. prefect/_vendor/fastapi/types.py +0 -3
  222. prefect/_vendor/fastapi/utils.py +0 -235
  223. prefect/_vendor/fastapi/websockets.py +0 -7
  224. prefect/_vendor/starlette/__init__.py +0 -1
  225. prefect/_vendor/starlette/_compat.py +0 -28
  226. prefect/_vendor/starlette/_exception_handler.py +0 -80
  227. prefect/_vendor/starlette/_utils.py +0 -88
  228. prefect/_vendor/starlette/applications.py +0 -261
  229. prefect/_vendor/starlette/authentication.py +0 -159
  230. prefect/_vendor/starlette/background.py +0 -43
  231. prefect/_vendor/starlette/concurrency.py +0 -59
  232. prefect/_vendor/starlette/config.py +0 -151
  233. prefect/_vendor/starlette/convertors.py +0 -87
  234. prefect/_vendor/starlette/datastructures.py +0 -707
  235. prefect/_vendor/starlette/endpoints.py +0 -130
  236. prefect/_vendor/starlette/exceptions.py +0 -60
  237. prefect/_vendor/starlette/formparsers.py +0 -276
  238. prefect/_vendor/starlette/middleware/__init__.py +0 -17
  239. prefect/_vendor/starlette/middleware/authentication.py +0 -52
  240. prefect/_vendor/starlette/middleware/base.py +0 -220
  241. prefect/_vendor/starlette/middleware/cors.py +0 -176
  242. prefect/_vendor/starlette/middleware/errors.py +0 -265
  243. prefect/_vendor/starlette/middleware/exceptions.py +0 -74
  244. prefect/_vendor/starlette/middleware/gzip.py +0 -113
  245. prefect/_vendor/starlette/middleware/httpsredirect.py +0 -19
  246. prefect/_vendor/starlette/middleware/sessions.py +0 -82
  247. prefect/_vendor/starlette/middleware/trustedhost.py +0 -64
  248. prefect/_vendor/starlette/middleware/wsgi.py +0 -147
  249. prefect/_vendor/starlette/py.typed +0 -0
  250. prefect/_vendor/starlette/requests.py +0 -328
  251. prefect/_vendor/starlette/responses.py +0 -347
  252. prefect/_vendor/starlette/routing.py +0 -933
  253. prefect/_vendor/starlette/schemas.py +0 -154
  254. prefect/_vendor/starlette/staticfiles.py +0 -248
  255. prefect/_vendor/starlette/status.py +0 -199
  256. prefect/_vendor/starlette/templating.py +0 -231
  257. prefect/_vendor/starlette/testclient.py +0 -804
  258. prefect/_vendor/starlette/types.py +0 -30
  259. prefect/_vendor/starlette/websockets.py +0 -193
  260. prefect/blocks/kubernetes.py +0 -119
  261. prefect/deprecated/__init__.py +0 -0
  262. prefect/deprecated/data_documents.py +0 -350
  263. prefect/deprecated/packaging/__init__.py +0 -12
  264. prefect/deprecated/packaging/base.py +0 -96
  265. prefect/deprecated/packaging/docker.py +0 -146
  266. prefect/deprecated/packaging/file.py +0 -92
  267. prefect/deprecated/packaging/orion.py +0 -80
  268. prefect/deprecated/packaging/serializers.py +0 -171
  269. prefect/events/instrument.py +0 -135
  270. prefect/infrastructure/container.py +0 -824
  271. prefect/infrastructure/kubernetes.py +0 -920
  272. prefect/infrastructure/process.py +0 -289
  273. prefect/manifests.py +0 -20
  274. prefect/new_flow_engine.py +0 -449
  275. prefect/new_task_engine.py +0 -423
  276. prefect/pydantic/__init__.py +0 -76
  277. prefect/pydantic/main.py +0 -39
  278. prefect/software/__init__.py +0 -2
  279. prefect/software/base.py +0 -50
  280. prefect/software/conda.py +0 -199
  281. prefect/software/pip.py +0 -122
  282. prefect/software/python.py +0 -52
  283. prefect/task_server.py +0 -322
  284. prefect_client-2.20.4.dist-info/RECORD +0 -294
  285. /prefect/{_internal/pydantic/utilities → client/types}/__init__.py +0 -0
  286. /prefect/{_vendor → concurrency/v1}/__init__.py +0 -0
  287. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/LICENSE +0 -0
  288. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.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/task_server.py DELETED
@@ -1,322 +0,0 @@
1
- import asyncio
2
- import inspect
3
- import os
4
- import signal
5
- import socket
6
- import sys
7
- from contextlib import AsyncExitStack
8
- from functools import partial
9
- from typing import List, Optional, Type
10
-
11
- import anyio
12
- from exceptiongroup import BaseExceptionGroup # novermin
13
- from websockets.exceptions import InvalidStatusCode
14
-
15
- from prefect import Task, get_client
16
- from prefect._internal.concurrency.api import create_call, from_sync
17
- from prefect.client.schemas.objects import TaskRun
18
- from prefect.client.subscriptions import Subscription
19
- from prefect.engine import emit_task_run_state_change_event, propose_state
20
- from prefect.exceptions import Abort, PrefectHTTPStatusError
21
- from prefect.logging.loggers import get_logger
22
- from prefect.results import ResultFactory
23
- from prefect.settings import (
24
- PREFECT_API_URL,
25
- PREFECT_EXPERIMENTAL_ENABLE_TASK_SCHEDULING,
26
- PREFECT_TASK_SCHEDULING_DELETE_FAILED_SUBMISSIONS,
27
- )
28
- from prefect.states import Pending
29
- from prefect.task_engine import submit_autonomous_task_run_to_engine
30
- from prefect.task_runners import (
31
- BaseTaskRunner,
32
- ConcurrentTaskRunner,
33
- )
34
- from prefect.utilities.asyncutils import asyncnullcontext, sync_compatible
35
- from prefect.utilities.processutils import _register_signal
36
-
37
- logger = get_logger("task_server")
38
-
39
-
40
- class StopTaskServer(Exception):
41
- """Raised when the task server is stopped."""
42
-
43
- pass
44
-
45
-
46
- def should_try_to_read_parameters(task: Task, task_run: TaskRun) -> bool:
47
- """Determines whether a task run should read parameters from the result factory."""
48
- new_enough_state_details = hasattr(
49
- task_run.state.state_details, "task_parameters_id"
50
- )
51
- task_accepts_parameters = bool(inspect.signature(task.fn).parameters)
52
-
53
- return new_enough_state_details and task_accepts_parameters
54
-
55
-
56
- class TaskServer:
57
- """This class is responsible for serving tasks that may be executed in the background
58
- by a task runner via the traditional engine machinery.
59
-
60
- When `start()` is called, the task server will open a websocket connection to a
61
- server-side queue of scheduled task runs. When a scheduled task run is found, the
62
- scheduled task run is submitted to the engine for execution with a minimal `EngineContext`
63
- so that the task run can be governed by orchestration rules.
64
-
65
- Args:
66
- - tasks: A list of tasks to serve. These tasks will be submitted to the engine
67
- when a scheduled task run is found.
68
- - task_runner: The task runner to use for executing the tasks. Defaults to
69
- `ConcurrentTaskRunner`.
70
- """
71
-
72
- def __init__(
73
- self,
74
- *tasks: Task,
75
- task_runner: Optional[Type[BaseTaskRunner]] = None,
76
- ):
77
- self.tasks: List[Task] = tasks
78
-
79
- self.task_runner: BaseTaskRunner = task_runner or ConcurrentTaskRunner()
80
- self.started: bool = False
81
- self.stopping: bool = False
82
-
83
- self._client = get_client()
84
- self._exit_stack = AsyncExitStack()
85
-
86
- if not asyncio.get_event_loop().is_running():
87
- raise RuntimeError(
88
- "TaskServer must be initialized within an async context."
89
- )
90
-
91
- self._runs_task_group: anyio.abc.TaskGroup = anyio.create_task_group()
92
-
93
- @property
94
- def _client_id(self) -> str:
95
- return f"{socket.gethostname()}-{os.getpid()}"
96
-
97
- def handle_sigterm(self, signum, frame):
98
- """
99
- Shuts down the task server when a SIGTERM is received.
100
- """
101
- logger.info("SIGTERM received, initiating graceful shutdown...")
102
- from_sync.call_in_loop_thread(create_call(self.stop))
103
-
104
- sys.exit(0)
105
-
106
- @sync_compatible
107
- async def start(self) -> None:
108
- """
109
- Starts a task server, which runs the tasks provided in the constructor.
110
- """
111
- _register_signal(signal.SIGTERM, self.handle_sigterm)
112
-
113
- async with asyncnullcontext() if self.started else self:
114
- logger.info("Starting task server...")
115
- try:
116
- await self._subscribe_to_task_scheduling()
117
- except InvalidStatusCode as exc:
118
- if exc.status_code == 403:
119
- logger.error(
120
- "Could not establish a connection to the `/task_runs/subscriptions/scheduled`"
121
- f" endpoint found at:\n\n {PREFECT_API_URL.value()}"
122
- "\n\nPlease double-check the values of your"
123
- " `PREFECT_API_URL` and `PREFECT_API_KEY` environment variables."
124
- )
125
- else:
126
- raise
127
-
128
- @sync_compatible
129
- async def stop(self):
130
- """Stops the task server's polling cycle."""
131
- if not self.started:
132
- raise RuntimeError(
133
- "Task server has not yet started. Please start the task server by"
134
- " calling .start()"
135
- )
136
-
137
- self.started = False
138
- self.stopping = True
139
-
140
- raise StopTaskServer
141
-
142
- async def _subscribe_to_task_scheduling(self):
143
- logger.info(
144
- f"Subscribing to tasks: {' | '.join(t.task_key.split('.')[-1] for t in self.tasks)}"
145
- )
146
- async for task_run in Subscription(
147
- model=TaskRun,
148
- path="/task_runs/subscriptions/scheduled",
149
- keys=[task.task_key for task in self.tasks],
150
- client_id=self._client_id,
151
- ):
152
- logger.info(f"Received task run: {task_run.id} - {task_run.name}")
153
- await self._submit_scheduled_task_run(task_run)
154
-
155
- async def _submit_scheduled_task_run(self, task_run: TaskRun):
156
- logger.debug(
157
- f"Found task run: {task_run.name!r} in state: {task_run.state.name!r}"
158
- )
159
-
160
- task = next((t for t in self.tasks if t.task_key == task_run.task_key), None)
161
-
162
- if not task:
163
- if PREFECT_TASK_SCHEDULING_DELETE_FAILED_SUBMISSIONS.value():
164
- logger.warning(
165
- f"Task {task_run.name!r} not found in task server registry."
166
- )
167
- await self._client._client.delete(f"/task_runs/{task_run.id}")
168
-
169
- return
170
-
171
- # The ID of the parameters for this run are stored in the Scheduled state's
172
- # state_details. If there is no parameters_id, then the task was created
173
- # without parameters.
174
- parameters = {}
175
- if should_try_to_read_parameters(task, task_run):
176
- parameters_id = task_run.state.state_details.task_parameters_id
177
- task.persist_result = True
178
- factory = await ResultFactory.from_autonomous_task(task)
179
- try:
180
- parameters = await factory.read_parameters(parameters_id)
181
- except Exception as exc:
182
- logger.exception(
183
- f"Failed to read parameters for task run {task_run.id!r}",
184
- exc_info=exc,
185
- )
186
- if PREFECT_TASK_SCHEDULING_DELETE_FAILED_SUBMISSIONS.value():
187
- logger.info(
188
- f"Deleting task run {task_run.id!r} because it failed to submit"
189
- )
190
- await self._client._client.delete(f"/task_runs/{task_run.id}")
191
- return
192
-
193
- logger.debug(
194
- f"Submitting run {task_run.name!r} of task {task.name!r} to engine"
195
- )
196
-
197
- try:
198
- state = await propose_state(
199
- client=get_client(), # TODO prove that we cannot use self._client here
200
- state=Pending(),
201
- task_run_id=task_run.id,
202
- )
203
- except Abort as exc:
204
- logger.exception(
205
- f"Failed to submit task run {task_run.id!r} to engine", exc_info=exc
206
- )
207
- return
208
- except PrefectHTTPStatusError as exc:
209
- if exc.response.status_code == 404:
210
- logger.warning(
211
- f"Task run {task_run.id!r} not found. It may have been deleted."
212
- )
213
- return
214
- raise
215
-
216
- if not state.is_pending():
217
- logger.warning(
218
- f"Cancelling submission of task run {task_run.id!r} -"
219
- f" server returned a non-pending state {state.type.value!r}."
220
- )
221
- return
222
-
223
- emit_task_run_state_change_event(
224
- task_run=task_run,
225
- initial_state=task_run.state,
226
- validated_state=state,
227
- )
228
-
229
- try:
230
- self._runs_task_group.start_soon(
231
- partial(
232
- submit_autonomous_task_run_to_engine,
233
- task=task,
234
- task_run=task_run,
235
- parameters=parameters,
236
- task_runner=self.task_runner,
237
- client=self._client,
238
- )
239
- )
240
- except BaseException as exc:
241
- logger.exception(
242
- f"Failed to submit task run {task_run.id!r} to engine", exc_info=exc
243
- )
244
-
245
- async def execute_task_run(self, task_run: TaskRun):
246
- """Execute a task run in the task server."""
247
- async with self if not self.started else asyncnullcontext():
248
- await self._submit_scheduled_task_run(task_run)
249
-
250
- async def __aenter__(self):
251
- logger.debug("Starting task server...")
252
-
253
- if self._client._closed:
254
- self._client = get_client()
255
-
256
- await self._exit_stack.enter_async_context(self._client)
257
- await self._exit_stack.enter_async_context(self.task_runner.start())
258
- await self._runs_task_group.__aenter__()
259
-
260
- self.started = True
261
- return self
262
-
263
- async def __aexit__(self, *exc_info):
264
- logger.debug("Stopping task server...")
265
- self.started = False
266
- await self._runs_task_group.__aexit__(*exc_info)
267
- await self._exit_stack.__aexit__(*exc_info)
268
-
269
-
270
- @sync_compatible
271
- async def serve(*tasks: Task, task_runner: Optional[Type[BaseTaskRunner]] = None):
272
- """Serve the provided tasks so that their runs may be submitted to and executed.
273
- in the engine. Tasks do not need to be within a flow run context to be submitted.
274
- You must `.submit` the same task object that you pass to `serve`.
275
-
276
- Args:
277
- - tasks: A list of tasks to serve. When a scheduled task run is found for a
278
- given task, the task run will be submitted to the engine for execution.
279
- - task_runner: The task runner to use for executing the tasks. Defaults to
280
- `ConcurrentTaskRunner`.
281
-
282
- Example:
283
- ```python
284
- from prefect import task
285
- from prefect.task_server import serve
286
-
287
- @task(log_prints=True)
288
- def say(message: str):
289
- print(message)
290
-
291
- @task(log_prints=True)
292
- def yell(message: str):
293
- print(message.upper())
294
-
295
- # starts a long-lived process that listens for scheduled runs of these tasks
296
- if __name__ == "__main__":
297
- serve(say, yell)
298
- ```
299
- """
300
- if not PREFECT_EXPERIMENTAL_ENABLE_TASK_SCHEDULING.value():
301
- raise RuntimeError(
302
- "To enable task scheduling, set PREFECT_EXPERIMENTAL_ENABLE_TASK_SCHEDULING"
303
- " to True."
304
- )
305
-
306
- task_server = TaskServer(*tasks, task_runner=task_runner)
307
- try:
308
- await task_server.start()
309
-
310
- except BaseExceptionGroup as exc: # novermin
311
- exceptions = exc.exceptions
312
- n_exceptions = len(exceptions)
313
- logger.error(
314
- f"Task worker stopped with {n_exceptions} exception{'s' if n_exceptions != 1 else ''}:"
315
- f"\n" + "\n".join(str(e) for e in exceptions)
316
- )
317
-
318
- except StopTaskServer:
319
- logger.info("Task server stopped.")
320
-
321
- except asyncio.CancelledError:
322
- logger.info("Task server interrupted, stopping...")