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