prefect-client 2.19.2__py3-none-any.whl → 3.0.0rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. prefect/__init__.py +8 -56
  2. prefect/_internal/compatibility/deprecated.py +6 -115
  3. prefect/_internal/compatibility/experimental.py +4 -79
  4. prefect/_internal/concurrency/api.py +0 -34
  5. prefect/_internal/concurrency/calls.py +0 -6
  6. prefect/_internal/concurrency/cancellation.py +0 -3
  7. prefect/_internal/concurrency/event_loop.py +0 -20
  8. prefect/_internal/concurrency/inspection.py +3 -3
  9. prefect/_internal/concurrency/threads.py +35 -0
  10. prefect/_internal/concurrency/waiters.py +0 -28
  11. prefect/_internal/pydantic/__init__.py +0 -45
  12. prefect/_internal/pydantic/v1_schema.py +21 -22
  13. prefect/_internal/pydantic/v2_schema.py +0 -2
  14. prefect/_internal/pydantic/v2_validated_func.py +18 -23
  15. prefect/_internal/schemas/bases.py +44 -177
  16. prefect/_internal/schemas/fields.py +1 -43
  17. prefect/_internal/schemas/validators.py +60 -158
  18. prefect/artifacts.py +161 -14
  19. prefect/automations.py +39 -4
  20. prefect/blocks/abstract.py +1 -1
  21. prefect/blocks/core.py +268 -148
  22. prefect/blocks/fields.py +2 -57
  23. prefect/blocks/kubernetes.py +8 -12
  24. prefect/blocks/notifications.py +40 -20
  25. prefect/blocks/system.py +22 -11
  26. prefect/blocks/webhook.py +2 -9
  27. prefect/client/base.py +4 -4
  28. prefect/client/cloud.py +8 -13
  29. prefect/client/orchestration.py +347 -341
  30. prefect/client/schemas/actions.py +92 -86
  31. prefect/client/schemas/filters.py +20 -40
  32. prefect/client/schemas/objects.py +151 -145
  33. prefect/client/schemas/responses.py +16 -24
  34. prefect/client/schemas/schedules.py +47 -35
  35. prefect/client/subscriptions.py +2 -2
  36. prefect/client/utilities.py +5 -2
  37. prefect/concurrency/asyncio.py +3 -1
  38. prefect/concurrency/events.py +1 -1
  39. prefect/concurrency/services.py +6 -3
  40. prefect/context.py +195 -27
  41. prefect/deployments/__init__.py +5 -6
  42. prefect/deployments/base.py +7 -5
  43. prefect/deployments/flow_runs.py +185 -0
  44. prefect/deployments/runner.py +50 -45
  45. prefect/deployments/schedules.py +28 -23
  46. prefect/deployments/steps/__init__.py +0 -1
  47. prefect/deployments/steps/core.py +1 -0
  48. prefect/deployments/steps/pull.py +7 -21
  49. prefect/engine.py +12 -2422
  50. prefect/events/actions.py +17 -23
  51. prefect/events/cli/automations.py +19 -6
  52. prefect/events/clients.py +14 -37
  53. prefect/events/filters.py +14 -18
  54. prefect/events/related.py +2 -2
  55. prefect/events/schemas/__init__.py +0 -5
  56. prefect/events/schemas/automations.py +55 -46
  57. prefect/events/schemas/deployment_triggers.py +7 -197
  58. prefect/events/schemas/events.py +34 -65
  59. prefect/events/schemas/labelling.py +10 -14
  60. prefect/events/utilities.py +2 -3
  61. prefect/events/worker.py +2 -3
  62. prefect/filesystems.py +6 -517
  63. prefect/{new_flow_engine.py → flow_engine.py} +313 -72
  64. prefect/flow_runs.py +377 -5
  65. prefect/flows.py +307 -166
  66. prefect/futures.py +186 -345
  67. prefect/infrastructure/__init__.py +0 -27
  68. prefect/infrastructure/provisioners/__init__.py +5 -3
  69. prefect/infrastructure/provisioners/cloud_run.py +11 -6
  70. prefect/infrastructure/provisioners/container_instance.py +11 -7
  71. prefect/infrastructure/provisioners/ecs.py +6 -4
  72. prefect/infrastructure/provisioners/modal.py +8 -5
  73. prefect/input/actions.py +2 -4
  74. prefect/input/run_input.py +5 -7
  75. prefect/logging/formatters.py +0 -2
  76. prefect/logging/handlers.py +3 -11
  77. prefect/logging/loggers.py +2 -2
  78. prefect/manifests.py +2 -1
  79. prefect/records/__init__.py +1 -0
  80. prefect/records/result_store.py +42 -0
  81. prefect/records/store.py +9 -0
  82. prefect/results.py +43 -39
  83. prefect/runner/runner.py +19 -15
  84. prefect/runner/server.py +6 -10
  85. prefect/runner/storage.py +3 -8
  86. prefect/runner/submit.py +2 -2
  87. prefect/runner/utils.py +2 -2
  88. prefect/serializers.py +24 -35
  89. prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
  90. prefect/settings.py +70 -133
  91. prefect/states.py +17 -47
  92. prefect/task_engine.py +697 -58
  93. prefect/task_runners.py +269 -301
  94. prefect/task_server.py +53 -34
  95. prefect/tasks.py +327 -337
  96. prefect/transactions.py +220 -0
  97. prefect/types/__init__.py +61 -82
  98. prefect/utilities/asyncutils.py +195 -136
  99. prefect/utilities/callables.py +311 -43
  100. prefect/utilities/collections.py +23 -38
  101. prefect/utilities/dispatch.py +11 -3
  102. prefect/utilities/dockerutils.py +4 -0
  103. prefect/utilities/engine.py +140 -20
  104. prefect/utilities/importtools.py +97 -27
  105. prefect/utilities/pydantic.py +128 -38
  106. prefect/utilities/schema_tools/hydration.py +5 -1
  107. prefect/utilities/templating.py +12 -2
  108. prefect/variables.py +78 -61
  109. prefect/workers/__init__.py +0 -1
  110. prefect/workers/base.py +15 -17
  111. prefect/workers/process.py +3 -8
  112. prefect/workers/server.py +2 -2
  113. {prefect_client-2.19.2.dist-info → prefect_client-3.0.0rc1.dist-info}/METADATA +22 -21
  114. prefect_client-3.0.0rc1.dist-info/RECORD +176 -0
  115. prefect/_internal/pydantic/_base_model.py +0 -51
  116. prefect/_internal/pydantic/_compat.py +0 -82
  117. prefect/_internal/pydantic/_flags.py +0 -20
  118. prefect/_internal/pydantic/_types.py +0 -8
  119. prefect/_internal/pydantic/utilities/__init__.py +0 -0
  120. prefect/_internal/pydantic/utilities/config_dict.py +0 -72
  121. prefect/_internal/pydantic/utilities/field_validator.py +0 -150
  122. prefect/_internal/pydantic/utilities/model_construct.py +0 -56
  123. prefect/_internal/pydantic/utilities/model_copy.py +0 -55
  124. prefect/_internal/pydantic/utilities/model_dump.py +0 -136
  125. prefect/_internal/pydantic/utilities/model_dump_json.py +0 -112
  126. prefect/_internal/pydantic/utilities/model_fields.py +0 -50
  127. prefect/_internal/pydantic/utilities/model_fields_set.py +0 -29
  128. prefect/_internal/pydantic/utilities/model_json_schema.py +0 -82
  129. prefect/_internal/pydantic/utilities/model_rebuild.py +0 -80
  130. prefect/_internal/pydantic/utilities/model_validate.py +0 -75
  131. prefect/_internal/pydantic/utilities/model_validate_json.py +0 -68
  132. prefect/_internal/pydantic/utilities/model_validator.py +0 -87
  133. prefect/_internal/pydantic/utilities/type_adapter.py +0 -71
  134. prefect/_vendor/__init__.py +0 -0
  135. prefect/_vendor/fastapi/__init__.py +0 -25
  136. prefect/_vendor/fastapi/applications.py +0 -946
  137. prefect/_vendor/fastapi/background.py +0 -3
  138. prefect/_vendor/fastapi/concurrency.py +0 -44
  139. prefect/_vendor/fastapi/datastructures.py +0 -58
  140. prefect/_vendor/fastapi/dependencies/__init__.py +0 -0
  141. prefect/_vendor/fastapi/dependencies/models.py +0 -64
  142. prefect/_vendor/fastapi/dependencies/utils.py +0 -877
  143. prefect/_vendor/fastapi/encoders.py +0 -177
  144. prefect/_vendor/fastapi/exception_handlers.py +0 -40
  145. prefect/_vendor/fastapi/exceptions.py +0 -46
  146. prefect/_vendor/fastapi/logger.py +0 -3
  147. prefect/_vendor/fastapi/middleware/__init__.py +0 -1
  148. prefect/_vendor/fastapi/middleware/asyncexitstack.py +0 -25
  149. prefect/_vendor/fastapi/middleware/cors.py +0 -3
  150. prefect/_vendor/fastapi/middleware/gzip.py +0 -3
  151. prefect/_vendor/fastapi/middleware/httpsredirect.py +0 -3
  152. prefect/_vendor/fastapi/middleware/trustedhost.py +0 -3
  153. prefect/_vendor/fastapi/middleware/wsgi.py +0 -3
  154. prefect/_vendor/fastapi/openapi/__init__.py +0 -0
  155. prefect/_vendor/fastapi/openapi/constants.py +0 -2
  156. prefect/_vendor/fastapi/openapi/docs.py +0 -203
  157. prefect/_vendor/fastapi/openapi/models.py +0 -480
  158. prefect/_vendor/fastapi/openapi/utils.py +0 -485
  159. prefect/_vendor/fastapi/param_functions.py +0 -340
  160. prefect/_vendor/fastapi/params.py +0 -453
  161. prefect/_vendor/fastapi/requests.py +0 -4
  162. prefect/_vendor/fastapi/responses.py +0 -40
  163. prefect/_vendor/fastapi/routing.py +0 -1331
  164. prefect/_vendor/fastapi/security/__init__.py +0 -15
  165. prefect/_vendor/fastapi/security/api_key.py +0 -98
  166. prefect/_vendor/fastapi/security/base.py +0 -6
  167. prefect/_vendor/fastapi/security/http.py +0 -172
  168. prefect/_vendor/fastapi/security/oauth2.py +0 -227
  169. prefect/_vendor/fastapi/security/open_id_connect_url.py +0 -34
  170. prefect/_vendor/fastapi/security/utils.py +0 -10
  171. prefect/_vendor/fastapi/staticfiles.py +0 -1
  172. prefect/_vendor/fastapi/templating.py +0 -3
  173. prefect/_vendor/fastapi/testclient.py +0 -1
  174. prefect/_vendor/fastapi/types.py +0 -3
  175. prefect/_vendor/fastapi/utils.py +0 -235
  176. prefect/_vendor/fastapi/websockets.py +0 -7
  177. prefect/_vendor/starlette/__init__.py +0 -1
  178. prefect/_vendor/starlette/_compat.py +0 -28
  179. prefect/_vendor/starlette/_exception_handler.py +0 -80
  180. prefect/_vendor/starlette/_utils.py +0 -88
  181. prefect/_vendor/starlette/applications.py +0 -261
  182. prefect/_vendor/starlette/authentication.py +0 -159
  183. prefect/_vendor/starlette/background.py +0 -43
  184. prefect/_vendor/starlette/concurrency.py +0 -59
  185. prefect/_vendor/starlette/config.py +0 -151
  186. prefect/_vendor/starlette/convertors.py +0 -87
  187. prefect/_vendor/starlette/datastructures.py +0 -707
  188. prefect/_vendor/starlette/endpoints.py +0 -130
  189. prefect/_vendor/starlette/exceptions.py +0 -60
  190. prefect/_vendor/starlette/formparsers.py +0 -276
  191. prefect/_vendor/starlette/middleware/__init__.py +0 -17
  192. prefect/_vendor/starlette/middleware/authentication.py +0 -52
  193. prefect/_vendor/starlette/middleware/base.py +0 -220
  194. prefect/_vendor/starlette/middleware/cors.py +0 -176
  195. prefect/_vendor/starlette/middleware/errors.py +0 -265
  196. prefect/_vendor/starlette/middleware/exceptions.py +0 -74
  197. prefect/_vendor/starlette/middleware/gzip.py +0 -113
  198. prefect/_vendor/starlette/middleware/httpsredirect.py +0 -19
  199. prefect/_vendor/starlette/middleware/sessions.py +0 -82
  200. prefect/_vendor/starlette/middleware/trustedhost.py +0 -64
  201. prefect/_vendor/starlette/middleware/wsgi.py +0 -147
  202. prefect/_vendor/starlette/requests.py +0 -328
  203. prefect/_vendor/starlette/responses.py +0 -347
  204. prefect/_vendor/starlette/routing.py +0 -933
  205. prefect/_vendor/starlette/schemas.py +0 -154
  206. prefect/_vendor/starlette/staticfiles.py +0 -248
  207. prefect/_vendor/starlette/status.py +0 -199
  208. prefect/_vendor/starlette/templating.py +0 -231
  209. prefect/_vendor/starlette/testclient.py +0 -804
  210. prefect/_vendor/starlette/types.py +0 -30
  211. prefect/_vendor/starlette/websockets.py +0 -193
  212. prefect/agent.py +0 -698
  213. prefect/deployments/deployments.py +0 -1042
  214. prefect/deprecated/__init__.py +0 -0
  215. prefect/deprecated/data_documents.py +0 -350
  216. prefect/deprecated/packaging/__init__.py +0 -12
  217. prefect/deprecated/packaging/base.py +0 -96
  218. prefect/deprecated/packaging/docker.py +0 -146
  219. prefect/deprecated/packaging/file.py +0 -92
  220. prefect/deprecated/packaging/orion.py +0 -80
  221. prefect/deprecated/packaging/serializers.py +0 -171
  222. prefect/events/instrument.py +0 -135
  223. prefect/infrastructure/base.py +0 -323
  224. prefect/infrastructure/container.py +0 -818
  225. prefect/infrastructure/kubernetes.py +0 -920
  226. prefect/infrastructure/process.py +0 -289
  227. prefect/new_task_engine.py +0 -423
  228. prefect/pydantic/__init__.py +0 -76
  229. prefect/pydantic/main.py +0 -39
  230. prefect/software/__init__.py +0 -2
  231. prefect/software/base.py +0 -50
  232. prefect/software/conda.py +0 -199
  233. prefect/software/pip.py +0 -122
  234. prefect/software/python.py +0 -52
  235. prefect/workers/block.py +0 -218
  236. prefect_client-2.19.2.dist-info/RECORD +0 -292
  237. {prefect_client-2.19.2.dist-info → prefect_client-3.0.0rc1.dist-info}/LICENSE +0 -0
  238. {prefect_client-2.19.2.dist-info → prefect_client-3.0.0rc1.dist-info}/WHEEL +0 -0
  239. {prefect_client-2.19.2.dist-info → prefect_client-3.0.0rc1.dist-info}/top_level.txt +0 -0
prefect/futures.py CHANGED
@@ -1,375 +1,241 @@
1
- """
2
- Futures represent the execution of a task and allow retrieval of the task run's state.
3
-
4
- This module contains the definition for futures as well as utilities for resolving
5
- futures in nested data structures.
6
- """
7
- import asyncio
8
- import warnings
1
+ import abc
2
+ import concurrent.futures
3
+ import inspect
4
+ import time
5
+ import uuid
9
6
  from functools import partial
10
- from typing import (
11
- TYPE_CHECKING,
12
- Any,
13
- Awaitable,
14
- Callable,
15
- Generic,
16
- Optional,
17
- Set,
18
- TypeVar,
19
- Union,
20
- cast,
21
- overload,
22
- )
23
- from uuid import UUID
24
-
25
- import anyio
26
-
27
- from prefect._internal.concurrency.api import create_call, from_async, from_sync
28
- from prefect._internal.concurrency.event_loop import run_coroutine_in_loop_from_async
29
- from prefect.client.orchestration import PrefectClient
30
- from prefect.client.utilities import inject_client
31
- from prefect.states import State
32
- from prefect.utilities.annotations import quote
33
- from prefect.utilities.asyncutils import A, Async, Sync, sync
34
- from prefect.utilities.collections import StopVisiting, visit_collection
7
+ from typing import Any, Generic, Optional, Set, Union, cast
35
8
 
36
- if TYPE_CHECKING:
37
- from prefect.task_runners import BaseTaskRunner
9
+ from typing_extensions import TypeVar
38
10
 
11
+ from prefect.client.orchestration import get_client
12
+ from prefect.client.schemas.objects import TaskRun
13
+ from prefect.exceptions import ObjectNotFound
14
+ from prefect.states import Pending, State
15
+ from prefect.utilities.annotations import quote
16
+ from prefect.utilities.asyncutils import run_coro_as_sync
17
+ from prefect.utilities.collections import StopVisiting, visit_collection
39
18
 
40
- R = TypeVar("R")
19
+ F = TypeVar("F")
41
20
 
42
21
 
43
- class PrefectFuture(Generic[R, A]):
22
+ class PrefectFuture(abc.ABC):
44
23
  """
45
- Represents the result of a computation happening in a task runner.
46
-
47
- When tasks are called, they are submitted to a task runner which creates a future
48
- for access to the state and result of the task.
49
-
50
- Examples:
51
- Define a task that returns a string
52
-
53
- >>> from prefect import flow, task
54
- >>> @task
55
- >>> def my_task() -> str:
56
- >>> return "hello"
57
-
58
- Calls of this task in a flow will return a future
59
-
60
- >>> @flow
61
- >>> def my_flow():
62
- >>> future = my_task.submit() # PrefectFuture[str, Sync] includes result type
63
- >>> future.task_run.id # UUID for the task run
64
-
65
- Wait for the task to complete
66
-
67
- >>> @flow
68
- >>> def my_flow():
69
- >>> future = my_task.submit()
70
- >>> final_state = future.wait()
71
-
72
- Wait N seconds for the task to complete
73
-
74
- >>> @flow
75
- >>> def my_flow():
76
- >>> future = my_task.submit()
77
- >>> final_state = future.wait(0.1)
78
- >>> if final_state:
79
- >>> ... # Task done
80
- >>> else:
81
- >>> ... # Task not done yet
82
-
83
- Wait for a task to complete and retrieve its result
84
-
85
- >>> @flow
86
- >>> def my_flow():
87
- >>> future = my_task.submit()
88
- >>> result = future.result()
89
- >>> assert result == "hello"
90
-
91
- Wait N seconds for a task to complete and retrieve its result
92
-
93
- >>> @flow
94
- >>> def my_flow():
95
- >>> future = my_task.submit()
96
- >>> result = future.result(timeout=5)
97
- >>> assert result == "hello"
98
-
99
- Retrieve the state of a task without waiting for completion
100
-
101
- >>> @flow
102
- >>> def my_flow():
103
- >>> future = my_task.submit()
104
- >>> state = future.get_state()
24
+ Abstract base class for Prefect futures. A Prefect future is a handle to the
25
+ asynchronous execution of a task run. It provides methods to wait for the task
26
+ to complete and to retrieve the result of the task run.
105
27
  """
106
28
 
107
- def __init__(
108
- self,
109
- name: str,
110
- key: UUID,
111
- task_runner: "BaseTaskRunner",
112
- asynchronous: A = True,
113
- _final_state: State[R] = None, # Exposed for testing
114
- ) -> None:
115
- self.key = key
116
- self.name = name
117
- self.asynchronous = asynchronous
118
- self.task_run = None
119
- self._final_state = _final_state
120
- self._exception: Optional[Exception] = None
121
- self._task_runner = task_runner
122
- self._submitted = anyio.Event()
123
-
124
- self._loop = asyncio.get_running_loop()
125
-
126
- @overload
127
- def wait(
128
- self: "PrefectFuture[R, Async]", timeout: None = None
129
- ) -> Awaitable[State[R]]:
130
- ...
131
-
132
- @overload
133
- def wait(self: "PrefectFuture[R, Sync]", timeout: None = None) -> State[R]:
134
- ...
29
+ def __init__(self, task_run_id: uuid.UUID):
30
+ self._task_run_id = task_run_id
31
+ self._final_state = None
135
32
 
136
- @overload
137
- def wait(
138
- self: "PrefectFuture[R, Async]", timeout: float
139
- ) -> Awaitable[Optional[State[R]]]:
140
- ...
33
+ @property
34
+ def task_run_id(self) -> uuid.UUID:
35
+ """The ID of the task run associated with this future"""
36
+ return self._task_run_id
141
37
 
142
- @overload
143
- def wait(self: "PrefectFuture[R, Sync]", timeout: float) -> Optional[State[R]]:
38
+ @property
39
+ def state(self) -> State:
40
+ """The current state of the task run associated with this future"""
41
+ if self._final_state:
42
+ return self._final_state
43
+ client = get_client(sync_client=True)
44
+ try:
45
+ task_run = cast(TaskRun, client.read_task_run(task_run_id=self.task_run_id))
46
+ except ObjectNotFound:
47
+ # We'll be optimistic and assume this task will eventually start
48
+ # TODO: Consider using task run events to wait for the task to start
49
+ return Pending()
50
+ return task_run.state or Pending()
51
+
52
+ @abc.abstractmethod
53
+ def wait(self, timeout: Optional[float] = None) -> None:
144
54
  ...
145
-
146
- def wait(self, timeout=None):
147
- """
148
- Wait for the run to finish and return the final state
149
-
150
- If the timeout is reached before the run reaches a final state,
151
- `None` is returned.
152
55
  """
153
- wait = create_call(self._wait, timeout=timeout)
154
- if self.asynchronous:
155
- return from_async.call_soon_in_loop_thread(wait).aresult()
156
- else:
157
- # type checking cannot handle the overloaded timeout passing
158
- return from_sync.call_soon_in_loop_thread(wait).result() # type: ignore
56
+ Wait for the task run to complete.
159
57
 
160
- @overload
161
- async def _wait(self, timeout: None = None) -> State[R]:
162
- ...
163
-
164
- @overload
165
- async def _wait(self, timeout: float) -> Optional[State[R]]:
166
- ...
58
+ If the task run has already completed, this method will return immediately.
167
59
 
168
- async def _wait(self, timeout=None):
169
- """
170
- Async implementation for `wait`
60
+ Args:
61
+ - timeout: The maximum number of seconds to wait for the task run to complete.
62
+ If the task run has not completed after the timeout has elapsed, this method will return.
171
63
  """
172
- await self._wait_for_submission()
173
64
 
174
- if self._final_state:
175
- return self._final_state
176
-
177
- self._final_state = await self._task_runner.wait(self.key, timeout)
178
- return self._final_state
179
-
180
- @overload
65
+ @abc.abstractmethod
181
66
  def result(
182
- self: "PrefectFuture[R, Sync]",
183
- timeout: float = None,
67
+ self,
68
+ timeout: Optional[float] = None,
184
69
  raise_on_failure: bool = True,
185
- ) -> R:
186
- ...
187
-
188
- @overload
189
- def result(
190
- self: "PrefectFuture[R, Sync]",
191
- timeout: float = None,
192
- raise_on_failure: bool = False,
193
- ) -> Union[R, Exception]:
70
+ ) -> Any:
194
71
  ...
72
+ """
73
+ Get the result of the task run associated with this future.
195
74
 
196
- @overload
197
- def result(
198
- self: "PrefectFuture[R, Async]",
199
- timeout: float = None,
200
- raise_on_failure: bool = True,
201
- ) -> Awaitable[R]:
202
- ...
75
+ If the task run has not completed, this method will wait for the task run to complete.
203
76
 
204
- @overload
205
- def result(
206
- self: "PrefectFuture[R, Async]",
207
- timeout: float = None,
208
- raise_on_failure: bool = False,
209
- ) -> Awaitable[Union[R, Exception]]:
210
- ...
77
+ Args:
78
+ - timeout: The maximum number of seconds to wait for the task run to complete.
79
+ If the task run has not completed after the timeout has elapsed, this method will return.
80
+ - raise_on_failure: If `True`, an exception will be raised if the task run fails.
211
81
 
212
- def result(self, timeout: float = None, raise_on_failure: bool = True):
82
+ Returns:
83
+ The result of the task run.
213
84
  """
214
- Wait for the run to finish and return the final state.
215
85
 
216
- If the timeout is reached before the run reaches a final state, a `TimeoutError`
217
- will be raised.
218
86
 
219
- If `raise_on_failure` is `True` and the task run failed, the task run's
220
- exception will be raised.
221
- """
222
- result = create_call(
223
- self._result, timeout=timeout, raise_on_failure=raise_on_failure
224
- )
225
- if self.asynchronous:
226
- return from_async.call_soon_in_loop_thread(result).aresult()
227
- else:
228
- return from_sync.call_soon_in_loop_thread(result).result()
87
+ class PrefectWrappedFuture(PrefectFuture, abc.ABC, Generic[F]):
88
+ """
89
+ A Prefect future that wraps another future object.
90
+ """
229
91
 
230
- async def _result(self, timeout: float = None, raise_on_failure: bool = True):
231
- """
232
- Async implementation of `result`
233
- """
234
- final_state = await self._wait(timeout=timeout)
235
- if not final_state:
236
- raise TimeoutError("Call timed out before task finished.")
237
- return await final_state.result(raise_on_failure=raise_on_failure, fetch=True)
238
-
239
- @overload
240
- def get_state(
241
- self: "PrefectFuture[R, Async]", client: PrefectClient = None
242
- ) -> Awaitable[State[R]]:
243
- ...
92
+ def __init__(self, task_run_id: uuid.UUID, wrapped_future: F):
93
+ self._wrapped_future = wrapped_future
94
+ super().__init__(task_run_id)
244
95
 
245
- @overload
246
- def get_state(
247
- self: "PrefectFuture[R, Sync]", client: PrefectClient = None
248
- ) -> State[R]:
249
- ...
96
+ @property
97
+ def wrapped_future(self) -> F:
98
+ """The underlying future object wrapped by this Prefect future"""
99
+ return self._wrapped_future
250
100
 
251
- def get_state(self, client: PrefectClient = None):
252
- """
253
- Get the current state of the task run.
254
- """
255
- if self.asynchronous:
256
- return cast(Awaitable[State[R]], self._get_state(client=client))
257
- else:
258
- return cast(State[R], sync(self._get_state, client=client))
259
101
 
260
- @inject_client
261
- async def _get_state(self, client: PrefectClient = None) -> State[R]:
262
- assert client is not None # always injected
102
+ class PrefectConcurrentFuture(PrefectWrappedFuture[concurrent.futures.Future]):
103
+ """
104
+ A Prefect future that wraps a concurrent.futures.Future. This future is used
105
+ when the task run is submitted to a ThreadPoolExecutor.
106
+ """
263
107
 
264
- # We must wait for the task run id to be populated
265
- await self._wait_for_submission()
108
+ def wait(self, timeout: Optional[float] = None) -> None:
109
+ try:
110
+ result = self._wrapped_future.result(timeout=timeout)
111
+ except concurrent.futures.TimeoutError:
112
+ return
113
+ if isinstance(result, State):
114
+ self._final_state = result
266
115
 
267
- task_run = await client.read_task_run(self.task_run.id)
116
+ def result(
117
+ self,
118
+ timeout: Optional[float] = None,
119
+ raise_on_failure: bool = True,
120
+ ) -> Any:
121
+ if not self._final_state:
122
+ try:
123
+ future_result = self._wrapped_future.result(timeout=timeout)
124
+ except concurrent.futures.TimeoutError as exc:
125
+ raise TimeoutError(
126
+ f"Task run {self.task_run_id} did not complete within {timeout} seconds"
127
+ ) from exc
128
+
129
+ if isinstance(future_result, State):
130
+ self._final_state = future_result
131
+ else:
132
+ return future_result
133
+
134
+ _result = self._final_state.result(
135
+ raise_on_failure=raise_on_failure, fetch=True
136
+ )
137
+ # state.result is a `sync_compatible` function that may or may not return an awaitable
138
+ # depending on whether the parent frame is sync or not
139
+ if inspect.isawaitable(_result):
140
+ _result = run_coro_as_sync(_result)
141
+ return _result
268
142
 
269
- if not task_run:
270
- raise RuntimeError("Future has no associated task run in the server.")
271
143
 
272
- # Update the task run reference
273
- self.task_run = task_run
274
- return task_run.state
144
+ class PrefectDistributedFuture(PrefectFuture):
145
+ """
146
+ Represents the result of a computation happening anywhere.
275
147
 
276
- async def _wait_for_submission(self):
277
- await run_coroutine_in_loop_from_async(self._loop, self._submitted.wait())
148
+ This class is typically used to interact with the result of a task run
149
+ scheduled to run in a Prefect task server but can be used to interact with
150
+ any task run scheduled in Prefect's API.
151
+ """
278
152
 
279
- def __hash__(self) -> int:
280
- return hash(self.key)
153
+ def __init__(self, task_run_id: uuid.UUID):
154
+ self._task_run = None
155
+ self._client = None
156
+ super().__init__(task_run_id=task_run_id)
281
157
 
282
- def __repr__(self) -> str:
283
- return f"PrefectFuture({self.name!r})"
158
+ @property
159
+ def client(self):
160
+ if self._client is None:
161
+ self._client = get_client(sync_client=True)
162
+ return self._client
284
163
 
285
- def __bool__(self) -> bool:
286
- warnings.warn(
287
- (
288
- "A 'PrefectFuture' from a task call was cast to a boolean; "
289
- "did you mean to check the result of the task instead? "
290
- "e.g. `if my_task().result(): ...`"
291
- ),
292
- stacklevel=2,
293
- )
294
- return True
164
+ @property
165
+ def task_run(self):
166
+ if self._task_run is None:
167
+ self._task_run = self.client.read_task_run(task_run_id=self.task_run_id)
168
+ return self._task_run
295
169
 
170
+ @task_run.setter
171
+ def task_run(self, task_run):
172
+ self._task_run = task_run
296
173
 
297
- def _collect_futures(futures, expr, context):
298
- # Expressions inside quotes should not be traversed
299
- if isinstance(context.get("annotation"), quote):
300
- raise StopVisiting()
174
+ def wait(
175
+ self, timeout: Optional[float] = None, polling_interval: Optional[float] = 0.2
176
+ ) -> None:
177
+ start_time = time.time()
178
+ # TODO: Websocket implementation?
179
+ while True:
180
+ self.task_run = cast(
181
+ TaskRun, self.client.read_task_run(task_run_id=self.task_run_id)
182
+ )
183
+ if self.task_run.state and self.task_run.state.is_final():
184
+ self._final_state = self.task_run.state
185
+ return
186
+ if timeout is not None and (time.time() - start_time) > timeout:
187
+ return
188
+ time.sleep(polling_interval)
301
189
 
302
- if isinstance(expr, PrefectFuture):
303
- futures.add(expr)
190
+ def result(
191
+ self,
192
+ timeout: Optional[float] = None,
193
+ raise_on_failure: bool = True,
194
+ polling_interval: Optional[float] = 0.2,
195
+ ) -> Any:
196
+ if not self._final_state:
197
+ self.wait(timeout=timeout)
198
+ if not self._final_state:
199
+ raise TimeoutError(
200
+ f"Task run {self.task_run_id} did not complete within {timeout} seconds"
201
+ )
202
+
203
+ _result = self._final_state.result(
204
+ raise_on_failure=raise_on_failure, fetch=True
205
+ )
206
+ # state.result is a `sync_compatible` function that may or may not return an awaitable
207
+ # depending on whether the parent frame is sync or not
208
+ if inspect.isawaitable(_result):
209
+ _result = run_coro_as_sync(_result)
210
+ return _result
304
211
 
305
- return expr
212
+ def __eq__(self, other):
213
+ if not isinstance(other, PrefectDistributedFuture):
214
+ return False
215
+ return self.task_run_id == other.task_run_id
306
216
 
307
217
 
308
- async def resolve_futures_to_data(
309
- expr: Union[PrefectFuture[R, Any], Any],
310
- raise_on_failure: bool = True,
311
- ) -> Union[R, Any]:
218
+ def resolve_futures_to_states(
219
+ expr: Union[PrefectFuture, Any],
220
+ ) -> Union[State, Any]:
312
221
  """
313
222
  Given a Python built-in collection, recursively find `PrefectFutures` and build a
314
- new collection with the same structure with futures resolved to their results.
315
- Resolving futures to their results may wait for execution to complete and require
316
- communication with the API.
223
+ new collection with the same structure with futures resolved to their final states.
224
+ Resolving futures to their final states may wait for execution to complete.
317
225
 
318
226
  Unsupported object types will be returned without modification.
319
227
  """
320
228
  futures: Set[PrefectFuture] = set()
321
229
 
322
- maybe_expr = visit_collection(
323
- expr,
324
- visit_fn=partial(_collect_futures, futures),
325
- return_data=False,
326
- context={},
327
- )
328
- if maybe_expr is not None:
329
- expr = maybe_expr
330
-
331
- # Get results
332
- results = await asyncio.gather(
333
- *[
334
- # We must wait for the future in the thread it was created in
335
- from_async.call_soon_in_loop_thread(
336
- create_call(future._result, raise_on_failure=raise_on_failure)
337
- ).aresult()
338
- for future in futures
339
- ]
340
- )
341
-
342
- results_by_future = dict(zip(futures, results))
343
-
344
- def replace_futures_with_results(expr, context):
345
- # Expressions inside quotes should not be modified
230
+ def _collect_futures(futures, expr, context):
231
+ # Expressions inside quotes should not be traversed
346
232
  if isinstance(context.get("annotation"), quote):
347
233
  raise StopVisiting()
348
234
 
349
235
  if isinstance(expr, PrefectFuture):
350
- return results_by_future[expr]
351
- else:
352
- return expr
236
+ futures.add(expr)
353
237
 
354
- return visit_collection(
355
- expr,
356
- visit_fn=replace_futures_with_results,
357
- return_data=True,
358
- context={},
359
- )
360
-
361
-
362
- async def resolve_futures_to_states(
363
- expr: Union[PrefectFuture[R, Any], Any],
364
- ) -> Union[State[R], Any]:
365
- """
366
- Given a Python built-in collection, recursively find `PrefectFutures` and build a
367
- new collection with the same structure with futures resolved to their final states.
368
- Resolving futures to their final states may wait for execution to complete.
369
-
370
- Unsupported object types will be returned without modification.
371
- """
372
- futures: Set[PrefectFuture] = set()
238
+ return expr
373
239
 
374
240
  visit_collection(
375
241
  expr,
@@ -379,13 +245,10 @@ async def resolve_futures_to_states(
379
245
  )
380
246
 
381
247
  # Get final states for each future
382
- states = await asyncio.gather(
383
- *[
384
- # We must wait for the future in the thread it was created in
385
- from_async.call_soon_in_loop_thread(create_call(future._wait)).aresult()
386
- for future in futures
387
- ]
388
- )
248
+ states = []
249
+ for future in futures:
250
+ future.wait()
251
+ states.append(future.state)
389
252
 
390
253
  states_by_future = dict(zip(futures, states))
391
254
 
@@ -405,25 +268,3 @@ async def resolve_futures_to_states(
405
268
  return_data=True,
406
269
  context={},
407
270
  )
408
-
409
-
410
- def call_repr(__fn: Callable, *args: Any, **kwargs: Any) -> str:
411
- """
412
- Generate a repr for a function call as "fn_name(arg_value, kwarg_name=kwarg_value)"
413
- """
414
-
415
- name = __fn.__name__
416
-
417
- # TODO: If this computation is concerningly expensive, we can iterate checking the
418
- # length at each arg or avoid calling `repr` on args with large amounts of
419
- # data
420
- call_args = ", ".join(
421
- [repr(arg) for arg in args]
422
- + [f"{key}={repr(val)}" for key, val in kwargs.items()]
423
- )
424
-
425
- # Enforce a maximum length
426
- if len(call_args) > 100:
427
- call_args = call_args[:100] + "..."
428
-
429
- return f"{name}({call_args})"
@@ -1,27 +0,0 @@
1
- from prefect.infrastructure.base import Infrastructure, InfrastructureResult
2
- from prefect.infrastructure.container import DockerContainer, DockerContainerResult
3
- from prefect.infrastructure.kubernetes import (
4
- KubernetesClusterConfig,
5
- KubernetesImagePullPolicy,
6
- KubernetesJob,
7
- KubernetesJobResult,
8
- KubernetesManifest,
9
- KubernetesRestartPolicy,
10
- )
11
- from prefect.infrastructure.process import Process, ProcessResult
12
-
13
- # Declare API
14
- __all__ = [
15
- "DockerContainer",
16
- "DockerContainerResult",
17
- "Infrastructure",
18
- "InfrastructureResult",
19
- "KubernetesClusterConfig",
20
- "KubernetesImagePullPolicy",
21
- "KubernetesJob",
22
- "KubernetesJobResult",
23
- "KubernetesManifest",
24
- "KubernetesRestartPolicy",
25
- "Process",
26
- "ProcessResult",
27
- ]
@@ -1,12 +1,14 @@
1
- from typing import Any, Dict, Optional, Protocol, Type
1
+ from typing import TYPE_CHECKING, Any, Dict, Optional, Protocol, Type
2
2
 
3
- from prefect.client.orchestration import PrefectClient
4
3
  from prefect.infrastructure.provisioners.modal import ModalPushProvisioner
5
4
  from .cloud_run import CloudRunPushProvisioner
6
5
  from .container_instance import ContainerInstancePushProvisioner
7
6
  from .ecs import ElasticContainerServicePushProvisioner
8
7
  import rich.console
9
8
 
9
+ if TYPE_CHECKING:
10
+ from prefect.client.orchestration import PrefectClient
11
+
10
12
  _provisioners = {
11
13
  "cloud-run:push": CloudRunPushProvisioner,
12
14
  "cloud-run-v2:push": CloudRunPushProvisioner,
@@ -29,7 +31,7 @@ class Provisioner(Protocol):
29
31
  self,
30
32
  work_pool_name: str,
31
33
  base_job_template: Dict[str, Any],
32
- client: Optional[PrefectClient] = None,
34
+ client: Optional["PrefectClient"] = None,
33
35
  ) -> Dict[str, Any]:
34
36
  ...
35
37