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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +423 -164
  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 +667 -440
  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 -2466
  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 +124 -51
  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 +138 -48
  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.2.dist-info → prefect_client-3.0.0.dist-info}/METADATA +30 -26
  161. prefect_client-3.0.0.dist-info/RECORD +201 -0
  162. {prefect_client-2.20.2.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.2.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.2.dist-info → prefect_client-3.0.0.dist-info}/LICENSE +0 -0
  288. {prefect_client-2.20.2.dist-info → prefect_client-3.0.0.dist-info}/top_level.txt +0 -0
@@ -1,449 +0,0 @@
1
- import inspect
2
- import logging
3
- import os
4
- import time
5
- from contextlib import contextmanager
6
- from dataclasses import dataclass, field
7
- from typing import (
8
- Any,
9
- Coroutine,
10
- Dict,
11
- Generic,
12
- Iterable,
13
- Literal,
14
- Optional,
15
- Tuple,
16
- TypeVar,
17
- Union,
18
- cast,
19
- )
20
- from uuid import UUID
21
-
22
- import anyio
23
- import anyio._backends._asyncio
24
- from sniffio import AsyncLibraryNotFoundError
25
- from typing_extensions import ParamSpec
26
-
27
- from prefect import Task, get_client
28
- from prefect.client.orchestration import SyncPrefectClient
29
- from prefect.client.schemas import FlowRun, TaskRun
30
- from prefect.client.schemas.filters import FlowRunFilter
31
- from prefect.client.schemas.sorting import FlowRunSort
32
- from prefect.context import FlowRunContext
33
- from prefect.deployments import load_flow_from_flow_run
34
- from prefect.exceptions import Abort, Pause
35
- from prefect.flows import Flow, load_flow_from_entrypoint
36
- from prefect.futures import PrefectFuture, resolve_futures_to_states
37
- from prefect.logging.loggers import flow_run_logger, get_logger
38
- from prefect.results import ResultFactory
39
- from prefect.states import (
40
- Pending,
41
- Running,
42
- State,
43
- exception_to_crashed_state,
44
- exception_to_failed_state,
45
- return_value_to_state,
46
- )
47
- from prefect.utilities.asyncutils import A, Async, run_sync
48
- from prefect.utilities.callables import parameters_to_args_kwargs
49
- from prefect.utilities.engine import (
50
- _resolve_custom_flow_run_name,
51
- propose_state_sync,
52
- )
53
-
54
- P = ParamSpec("P")
55
- R = TypeVar("R")
56
-
57
-
58
- def load_flow_and_flow_run(flow_run_id: UUID) -> Tuple[FlowRun, Flow]:
59
- ## TODO: add error handling to update state and log tracebacks
60
- entrypoint = os.environ.get("PREFECT__FLOW_ENTRYPOINT")
61
-
62
- client = get_client(sync_client=True)
63
- flow_run = client.read_flow_run(flow_run_id)
64
- flow = (
65
- load_flow_from_entrypoint(entrypoint)
66
- if entrypoint
67
- else run_sync(load_flow_from_flow_run(flow_run, client=client))
68
- )
69
-
70
- return flow_run, flow
71
-
72
-
73
- @dataclass
74
- class FlowRunEngine(Generic[P, R]):
75
- flow: Optional[Union[Flow[P, R], Flow[P, Coroutine[Any, Any, R]]]] = None
76
- parameters: Optional[Dict[str, Any]] = None
77
- flow_run: Optional[FlowRun] = None
78
- flow_run_id: Optional[UUID] = None
79
- logger: logging.Logger = field(default_factory=lambda: get_logger("engine"))
80
- _is_started: bool = False
81
- _client: Optional[SyncPrefectClient] = None
82
- short_circuit: bool = False
83
-
84
- def __post_init__(self):
85
- if self.flow is None and self.flow_run_id is None:
86
- raise ValueError("Either a flow or a flow_run_id must be provided.")
87
-
88
- if self.parameters is None:
89
- self.parameters = {}
90
-
91
- @property
92
- def client(self) -> SyncPrefectClient:
93
- if not self._is_started or self._client is None:
94
- raise RuntimeError("Engine has not started.")
95
- return self._client
96
-
97
- @property
98
- def state(self) -> State:
99
- return self.flow_run.state # type: ignore
100
-
101
- def begin_run(self) -> State:
102
- new_state = Running()
103
- state = self.set_state(new_state)
104
- while state.is_pending():
105
- time.sleep(0.2)
106
- state = self.set_state(new_state)
107
- return state
108
-
109
- def set_state(self, state: State, force: bool = False) -> State:
110
- """ """
111
- # prevents any state-setting activity
112
- if self.short_circuit:
113
- return self.state
114
-
115
- state = propose_state_sync(
116
- self.client, state, flow_run_id=self.flow_run.id, force=force
117
- ) # type: ignore
118
- self.flow_run.state = state # type: ignore
119
- self.flow_run.state_name = state.name # type: ignore
120
- self.flow_run.state_type = state.type # type: ignore
121
- return state
122
-
123
- def result(self, raise_on_failure: bool = True) -> "Union[R, State, None]":
124
- _result = self.state.result(raise_on_failure=raise_on_failure, fetch=True) # type: ignore
125
- # state.result is a `sync_compatible` function that may or may not return an awaitable
126
- # depending on whether the parent frame is sync or not
127
- if inspect.isawaitable(_result):
128
- _result = run_sync(_result)
129
- return _result
130
-
131
- def handle_success(self, result: R) -> R:
132
- result_factory = getattr(FlowRunContext.get(), "result_factory", None)
133
- if result_factory is None:
134
- raise ValueError("Result factory is not set")
135
- terminal_state = run_sync(
136
- return_value_to_state(
137
- run_sync(resolve_futures_to_states(result)),
138
- result_factory=result_factory,
139
- )
140
- )
141
- self.set_state(terminal_state)
142
- return result
143
-
144
- def handle_exception(
145
- self,
146
- exc: Exception,
147
- msg: Optional[str] = None,
148
- result_factory: Optional[ResultFactory] = None,
149
- ) -> State:
150
- context = FlowRunContext.get()
151
- state = run_sync(
152
- exception_to_failed_state(
153
- exc,
154
- message=msg or "Flow run encountered an exception:",
155
- result_factory=result_factory
156
- or getattr(context, "result_factory", None),
157
- )
158
- )
159
- state = self.set_state(state)
160
- if self.state.is_scheduled():
161
- state = self.set_state(Running())
162
- return state
163
-
164
- def handle_crash(self, exc: BaseException) -> None:
165
- state = run_sync(exception_to_crashed_state(exc))
166
- self.logger.error(f"Crash detected! {state.message}")
167
- self.logger.debug("Crash details:", exc_info=exc)
168
- self.set_state(state, force=True)
169
-
170
- def load_subflow_run(
171
- self,
172
- parent_task_run: TaskRun,
173
- client: SyncPrefectClient,
174
- context: FlowRunContext,
175
- ) -> Union[FlowRun, None]:
176
- """
177
- This method attempts to load an existing flow run for a subflow task
178
- run, if appropriate.
179
-
180
- If the parent task run is in a final but not COMPLETED state, and not
181
- being rerun, then we attempt to load an existing flow run instead of
182
- creating a new one. This will prevent the engine from running the
183
- subflow again.
184
-
185
- If no existing flow run is found, or if the subflow should be rerun,
186
- then no flow run is returned.
187
- """
188
-
189
- # check if the parent flow run is rerunning
190
- rerunning = (
191
- context.flow_run.run_count > 1
192
- if getattr(context, "flow_run", None)
193
- and isinstance(context.flow_run, FlowRun)
194
- else False
195
- )
196
-
197
- # if the parent task run is in a final but not completed state, and
198
- # not rerunning, then retrieve the most recent flow run instead of
199
- # creating a new one. This effectively loads a cached flow run for
200
- # situations where we are confident the flow should not be run
201
- # again.
202
- assert isinstance(parent_task_run.state, State)
203
- if parent_task_run.state.is_final() and not (
204
- rerunning and not parent_task_run.state.is_completed()
205
- ):
206
- # return the most recent flow run, if it exists
207
- flow_runs = client.read_flow_runs(
208
- flow_run_filter=FlowRunFilter(
209
- parent_task_run_id={"any_": [parent_task_run.id]}
210
- ),
211
- sort=FlowRunSort.EXPECTED_START_TIME_ASC,
212
- limit=1,
213
- )
214
- if flow_runs:
215
- return flow_runs[-1]
216
-
217
- def create_flow_run(self, client: SyncPrefectClient) -> FlowRun:
218
- flow_run_ctx = FlowRunContext.get()
219
- parameters = self.parameters or {}
220
-
221
- parent_task_run = None
222
-
223
- # this is a subflow run
224
- if flow_run_ctx:
225
- # add a task to a parent flow run that represents the execution of a subflow run
226
- # reuse the logic from the TaskRunEngine to ensure parents are created correctly
227
- parent_task = Task(
228
- name=self.flow.name, fn=self.flow.fn, version=self.flow.version
229
- )
230
- parent_task_run = run_sync(
231
- parent_task.create_run(
232
- client=self.client,
233
- flow_run_context=flow_run_ctx,
234
- parameters=self.parameters,
235
- )
236
- )
237
-
238
- # check if there is already a flow run for this subflow
239
- if subflow_run := self.load_subflow_run(
240
- parent_task_run=parent_task_run, client=client, context=flow_run_ctx
241
- ):
242
- return subflow_run
243
-
244
- try:
245
- flow_run_name = _resolve_custom_flow_run_name(
246
- flow=self.flow, parameters=parameters
247
- )
248
- except TypeError:
249
- flow_run_name = None
250
-
251
- flow_run = client.create_flow_run(
252
- flow=self.flow,
253
- name=flow_run_name,
254
- parameters=self.flow.serialize_parameters(parameters),
255
- state=Pending(),
256
- parent_task_run_id=getattr(parent_task_run, "id", None),
257
- )
258
- return flow_run
259
-
260
- @contextmanager
261
- def enter_run_context(self, client: Optional[SyncPrefectClient] = None):
262
- if client is None:
263
- client = self.client
264
- if not self.flow_run:
265
- raise ValueError("Flow run not set")
266
-
267
- self.flow_run = client.read_flow_run(self.flow_run.id)
268
-
269
- # if running in a completely synchronous frame, anyio will not detect the
270
- # backend to use for the task group
271
- try:
272
- task_group = anyio.create_task_group()
273
- except AsyncLibraryNotFoundError:
274
- task_group = anyio._backends._asyncio.TaskGroup()
275
-
276
- with FlowRunContext(
277
- flow=self.flow,
278
- log_prints=self.flow.log_prints or False,
279
- flow_run=self.flow_run,
280
- parameters=self.parameters,
281
- client=client,
282
- background_tasks=task_group,
283
- result_factory=run_sync(ResultFactory.from_flow(self.flow)),
284
- task_runner=self.flow.task_runner.duplicate(),
285
- ):
286
- # set the logger to the flow run logger
287
- current_logger = self.logger
288
- try:
289
- self.logger = flow_run_logger(flow_run=self.flow_run, flow=self.flow)
290
- yield
291
- finally:
292
- self.logger = current_logger
293
-
294
- @contextmanager
295
- def start(self):
296
- """
297
- Enters a client context and creates a flow run if needed.
298
- """
299
-
300
- with get_client(sync_client=True) as client:
301
- self._client = client
302
- self._is_started = True
303
-
304
- # this conditional is engaged whenever a run is triggered via deployment
305
- if self.flow_run_id and not self.flow:
306
- self.flow_run = client.read_flow_run(self.flow_run_id)
307
- try:
308
- self.flow = self.load_flow(client)
309
- except Exception as exc:
310
- self.handle_exception(
311
- exc,
312
- msg="Failed to load flow from entrypoint.",
313
- )
314
- self.short_circuit = True
315
-
316
- if not self.flow_run:
317
- self.flow_run = self.create_flow_run(client)
318
- self.logger.debug(f'Created flow run "{self.flow_run.id}"')
319
-
320
- # validate prior to context so that context receives validated params
321
- if self.flow.should_validate_parameters:
322
- try:
323
- self.parameters = self.flow.validate_parameters(
324
- self.parameters or {}
325
- )
326
- except Exception as exc:
327
- self.handle_exception(
328
- exc,
329
- msg="Validation of flow parameters failed with error",
330
- result_factory=run_sync(ResultFactory.from_flow(self.flow)),
331
- )
332
- self.short_circuit = True
333
- try:
334
- yield self
335
- except Exception:
336
- # regular exceptions are caught and re-raised to the user
337
- raise
338
- except (Abort, Pause):
339
- raise
340
- except BaseException as exc:
341
- # BaseExceptions are caught and handled as crashes
342
- self.handle_crash(exc)
343
- raise
344
- finally:
345
- self._is_started = False
346
- self._client = None
347
-
348
- def is_running(self) -> bool:
349
- if getattr(self, "flow_run", None) is None:
350
- return False
351
- return getattr(self, "flow_run").state.is_running()
352
-
353
- def is_pending(self) -> bool:
354
- if getattr(self, "flow_run", None) is None:
355
- return False # TODO: handle this differently?
356
- return getattr(self, "flow_run").state.is_pending()
357
-
358
-
359
- async def run_flow_async(
360
- flow: Optional[Flow[P, Coroutine[Any, Any, R]]] = None,
361
- flow_run: Optional[FlowRun] = None,
362
- flow_run_id: Optional[UUID] = None,
363
- parameters: Optional[Dict[str, Any]] = None,
364
- wait_for: Optional[Iterable[PrefectFuture[A, Async]]] = None,
365
- return_type: Literal["state", "result"] = "result",
366
- ) -> Union[R, None]:
367
- """
368
- Runs a flow against the API.
369
-
370
- We will most likely want to use this logic as a wrapper and return a coroutine for type inference.
371
- """
372
-
373
- engine = FlowRunEngine[P, R](flow, parameters, flow_run, flow_run_id)
374
-
375
- # This is a context manager that keeps track of the state of the flow run.
376
- with engine.start() as run:
377
- run.begin_run()
378
-
379
- while run.is_running():
380
- with run.enter_run_context():
381
- try:
382
- # This is where the flow is actually run.
383
- call_args, call_kwargs = parameters_to_args_kwargs(
384
- flow.fn, run.parameters or {}
385
- )
386
- result = cast(R, await flow.fn(*call_args, **call_kwargs)) # type: ignore
387
- # If the flow run is successful, finalize it.
388
- run.handle_success(result)
389
-
390
- except Exception as exc:
391
- # If the flow fails, and we have retries left, set the flow to retrying.
392
- run.handle_exception(exc)
393
-
394
- if return_type == "state":
395
- return run.state
396
- return run.result()
397
-
398
-
399
- def run_flow_sync(
400
- flow: Flow[P, R],
401
- flow_run: Optional[FlowRun] = None,
402
- parameters: Optional[Dict[str, Any]] = None,
403
- wait_for: Optional[Iterable[PrefectFuture[A, Async]]] = None,
404
- return_type: Literal["state", "result"] = "result",
405
- ) -> Union[R, State, None]:
406
- engine = FlowRunEngine[P, R](flow, parameters, flow_run)
407
-
408
- # This is a context manager that keeps track of the state of the flow run.
409
- with engine.start() as run:
410
- run.begin_run()
411
-
412
- while run.is_running():
413
- with run.enter_run_context():
414
- try:
415
- # This is where the flow is actually run.
416
- call_args, call_kwargs = parameters_to_args_kwargs(
417
- flow.fn, run.parameters or {}
418
- )
419
- result = cast(R, flow.fn(*call_args, **call_kwargs)) # type: ignore
420
- # If the flow run is successful, finalize it.
421
- run.handle_success(result)
422
-
423
- except Exception as exc:
424
- # If the flow fails, and we have retries left, set the flow to retrying.
425
- run.handle_exception(exc)
426
-
427
- if return_type == "state":
428
- return run.state
429
- return run.result()
430
-
431
-
432
- def run_flow(
433
- flow: Flow[P, R],
434
- flow_run: Optional[FlowRun] = None,
435
- parameters: Optional[Dict[str, Any]] = None,
436
- wait_for: Optional[Iterable[PrefectFuture[A, Async]]] = None,
437
- return_type: Literal["state", "result"] = "result",
438
- ) -> Union[R, State, None]:
439
- kwargs = dict(
440
- flow=flow,
441
- flow_run=flow_run,
442
- parameters=parameters,
443
- wait_for=wait_for,
444
- return_type=return_type,
445
- )
446
- if flow.isasync:
447
- return run_flow_async(**kwargs)
448
- else:
449
- return run_flow_sync(**kwargs)