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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. prefect/__init__.py +8 -56
  2. prefect/_internal/compatibility/deprecated.py +6 -115
  3. prefect/_internal/compatibility/experimental.py +4 -79
  4. prefect/_internal/concurrency/api.py +0 -34
  5. prefect/_internal/concurrency/calls.py +0 -6
  6. prefect/_internal/concurrency/cancellation.py +0 -3
  7. prefect/_internal/concurrency/event_loop.py +0 -20
  8. prefect/_internal/concurrency/inspection.py +3 -3
  9. prefect/_internal/concurrency/threads.py +35 -0
  10. prefect/_internal/concurrency/waiters.py +0 -28
  11. prefect/_internal/pydantic/__init__.py +0 -45
  12. prefect/_internal/pydantic/v1_schema.py +21 -22
  13. prefect/_internal/pydantic/v2_schema.py +0 -2
  14. prefect/_internal/pydantic/v2_validated_func.py +18 -23
  15. prefect/_internal/schemas/bases.py +44 -177
  16. prefect/_internal/schemas/fields.py +1 -43
  17. prefect/_internal/schemas/validators.py +60 -158
  18. prefect/artifacts.py +161 -14
  19. prefect/automations.py +39 -4
  20. prefect/blocks/abstract.py +1 -1
  21. prefect/blocks/core.py +268 -148
  22. prefect/blocks/fields.py +2 -57
  23. prefect/blocks/kubernetes.py +8 -12
  24. prefect/blocks/notifications.py +40 -20
  25. prefect/blocks/redis.py +168 -0
  26. prefect/blocks/system.py +22 -11
  27. prefect/blocks/webhook.py +2 -9
  28. prefect/client/base.py +4 -4
  29. prefect/client/cloud.py +8 -13
  30. prefect/client/orchestration.py +362 -340
  31. prefect/client/schemas/actions.py +92 -86
  32. prefect/client/schemas/filters.py +20 -40
  33. prefect/client/schemas/objects.py +158 -152
  34. prefect/client/schemas/responses.py +16 -24
  35. prefect/client/schemas/schedules.py +47 -35
  36. prefect/client/subscriptions.py +2 -2
  37. prefect/client/utilities.py +5 -2
  38. prefect/concurrency/asyncio.py +4 -2
  39. prefect/concurrency/events.py +1 -1
  40. prefect/concurrency/services.py +7 -4
  41. prefect/context.py +195 -27
  42. prefect/deployments/__init__.py +5 -6
  43. prefect/deployments/base.py +7 -5
  44. prefect/deployments/flow_runs.py +185 -0
  45. prefect/deployments/runner.py +50 -45
  46. prefect/deployments/schedules.py +28 -23
  47. prefect/deployments/steps/__init__.py +0 -1
  48. prefect/deployments/steps/core.py +1 -0
  49. prefect/deployments/steps/pull.py +7 -21
  50. prefect/engine.py +12 -2422
  51. prefect/events/actions.py +17 -23
  52. prefect/events/cli/automations.py +19 -6
  53. prefect/events/clients.py +14 -37
  54. prefect/events/filters.py +14 -18
  55. prefect/events/related.py +2 -2
  56. prefect/events/schemas/__init__.py +0 -5
  57. prefect/events/schemas/automations.py +55 -46
  58. prefect/events/schemas/deployment_triggers.py +7 -197
  59. prefect/events/schemas/events.py +36 -65
  60. prefect/events/schemas/labelling.py +10 -14
  61. prefect/events/utilities.py +2 -3
  62. prefect/events/worker.py +2 -3
  63. prefect/filesystems.py +6 -517
  64. prefect/{new_flow_engine.py → flow_engine.py} +315 -74
  65. prefect/flow_runs.py +379 -7
  66. prefect/flows.py +248 -165
  67. prefect/futures.py +187 -345
  68. prefect/infrastructure/__init__.py +0 -27
  69. prefect/infrastructure/provisioners/__init__.py +5 -3
  70. prefect/infrastructure/provisioners/cloud_run.py +11 -6
  71. prefect/infrastructure/provisioners/container_instance.py +11 -7
  72. prefect/infrastructure/provisioners/ecs.py +6 -4
  73. prefect/infrastructure/provisioners/modal.py +8 -5
  74. prefect/input/actions.py +2 -4
  75. prefect/input/run_input.py +9 -9
  76. prefect/logging/formatters.py +0 -2
  77. prefect/logging/handlers.py +3 -11
  78. prefect/logging/loggers.py +2 -2
  79. prefect/manifests.py +2 -1
  80. prefect/records/__init__.py +1 -0
  81. prefect/records/cache_policies.py +179 -0
  82. prefect/records/result_store.py +42 -0
  83. prefect/records/store.py +9 -0
  84. prefect/results.py +43 -39
  85. prefect/runner/runner.py +9 -9
  86. prefect/runner/server.py +6 -10
  87. prefect/runner/storage.py +3 -8
  88. prefect/runner/submit.py +2 -2
  89. prefect/runner/utils.py +2 -2
  90. prefect/serializers.py +24 -35
  91. prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
  92. prefect/settings.py +76 -136
  93. prefect/states.py +22 -50
  94. prefect/task_engine.py +666 -56
  95. prefect/task_runners.py +272 -300
  96. prefect/task_runs.py +203 -0
  97. prefect/{task_server.py → task_worker.py} +89 -60
  98. prefect/tasks.py +358 -341
  99. prefect/transactions.py +224 -0
  100. prefect/types/__init__.py +61 -82
  101. prefect/utilities/asyncutils.py +195 -136
  102. prefect/utilities/callables.py +121 -41
  103. prefect/utilities/collections.py +23 -38
  104. prefect/utilities/dispatch.py +11 -3
  105. prefect/utilities/dockerutils.py +4 -0
  106. prefect/utilities/engine.py +140 -20
  107. prefect/utilities/importtools.py +26 -27
  108. prefect/utilities/pydantic.py +128 -38
  109. prefect/utilities/schema_tools/hydration.py +5 -1
  110. prefect/utilities/templating.py +12 -2
  111. prefect/variables.py +84 -62
  112. prefect/workers/__init__.py +0 -1
  113. prefect/workers/base.py +26 -18
  114. prefect/workers/process.py +3 -8
  115. prefect/workers/server.py +2 -2
  116. {prefect_client-2.19.4.dist-info → prefect_client-3.0.0rc2.dist-info}/METADATA +23 -21
  117. prefect_client-3.0.0rc2.dist-info/RECORD +179 -0
  118. prefect/_internal/pydantic/_base_model.py +0 -51
  119. prefect/_internal/pydantic/_compat.py +0 -82
  120. prefect/_internal/pydantic/_flags.py +0 -20
  121. prefect/_internal/pydantic/_types.py +0 -8
  122. prefect/_internal/pydantic/utilities/__init__.py +0 -0
  123. prefect/_internal/pydantic/utilities/config_dict.py +0 -72
  124. prefect/_internal/pydantic/utilities/field_validator.py +0 -150
  125. prefect/_internal/pydantic/utilities/model_construct.py +0 -56
  126. prefect/_internal/pydantic/utilities/model_copy.py +0 -55
  127. prefect/_internal/pydantic/utilities/model_dump.py +0 -136
  128. prefect/_internal/pydantic/utilities/model_dump_json.py +0 -112
  129. prefect/_internal/pydantic/utilities/model_fields.py +0 -50
  130. prefect/_internal/pydantic/utilities/model_fields_set.py +0 -29
  131. prefect/_internal/pydantic/utilities/model_json_schema.py +0 -82
  132. prefect/_internal/pydantic/utilities/model_rebuild.py +0 -80
  133. prefect/_internal/pydantic/utilities/model_validate.py +0 -75
  134. prefect/_internal/pydantic/utilities/model_validate_json.py +0 -68
  135. prefect/_internal/pydantic/utilities/model_validator.py +0 -87
  136. prefect/_internal/pydantic/utilities/type_adapter.py +0 -71
  137. prefect/_vendor/__init__.py +0 -0
  138. prefect/_vendor/fastapi/__init__.py +0 -25
  139. prefect/_vendor/fastapi/applications.py +0 -946
  140. prefect/_vendor/fastapi/background.py +0 -3
  141. prefect/_vendor/fastapi/concurrency.py +0 -44
  142. prefect/_vendor/fastapi/datastructures.py +0 -58
  143. prefect/_vendor/fastapi/dependencies/__init__.py +0 -0
  144. prefect/_vendor/fastapi/dependencies/models.py +0 -64
  145. prefect/_vendor/fastapi/dependencies/utils.py +0 -877
  146. prefect/_vendor/fastapi/encoders.py +0 -177
  147. prefect/_vendor/fastapi/exception_handlers.py +0 -40
  148. prefect/_vendor/fastapi/exceptions.py +0 -46
  149. prefect/_vendor/fastapi/logger.py +0 -3
  150. prefect/_vendor/fastapi/middleware/__init__.py +0 -1
  151. prefect/_vendor/fastapi/middleware/asyncexitstack.py +0 -25
  152. prefect/_vendor/fastapi/middleware/cors.py +0 -3
  153. prefect/_vendor/fastapi/middleware/gzip.py +0 -3
  154. prefect/_vendor/fastapi/middleware/httpsredirect.py +0 -3
  155. prefect/_vendor/fastapi/middleware/trustedhost.py +0 -3
  156. prefect/_vendor/fastapi/middleware/wsgi.py +0 -3
  157. prefect/_vendor/fastapi/openapi/__init__.py +0 -0
  158. prefect/_vendor/fastapi/openapi/constants.py +0 -2
  159. prefect/_vendor/fastapi/openapi/docs.py +0 -203
  160. prefect/_vendor/fastapi/openapi/models.py +0 -480
  161. prefect/_vendor/fastapi/openapi/utils.py +0 -485
  162. prefect/_vendor/fastapi/param_functions.py +0 -340
  163. prefect/_vendor/fastapi/params.py +0 -453
  164. prefect/_vendor/fastapi/requests.py +0 -4
  165. prefect/_vendor/fastapi/responses.py +0 -40
  166. prefect/_vendor/fastapi/routing.py +0 -1331
  167. prefect/_vendor/fastapi/security/__init__.py +0 -15
  168. prefect/_vendor/fastapi/security/api_key.py +0 -98
  169. prefect/_vendor/fastapi/security/base.py +0 -6
  170. prefect/_vendor/fastapi/security/http.py +0 -172
  171. prefect/_vendor/fastapi/security/oauth2.py +0 -227
  172. prefect/_vendor/fastapi/security/open_id_connect_url.py +0 -34
  173. prefect/_vendor/fastapi/security/utils.py +0 -10
  174. prefect/_vendor/fastapi/staticfiles.py +0 -1
  175. prefect/_vendor/fastapi/templating.py +0 -3
  176. prefect/_vendor/fastapi/testclient.py +0 -1
  177. prefect/_vendor/fastapi/types.py +0 -3
  178. prefect/_vendor/fastapi/utils.py +0 -235
  179. prefect/_vendor/fastapi/websockets.py +0 -7
  180. prefect/_vendor/starlette/__init__.py +0 -1
  181. prefect/_vendor/starlette/_compat.py +0 -28
  182. prefect/_vendor/starlette/_exception_handler.py +0 -80
  183. prefect/_vendor/starlette/_utils.py +0 -88
  184. prefect/_vendor/starlette/applications.py +0 -261
  185. prefect/_vendor/starlette/authentication.py +0 -159
  186. prefect/_vendor/starlette/background.py +0 -43
  187. prefect/_vendor/starlette/concurrency.py +0 -59
  188. prefect/_vendor/starlette/config.py +0 -151
  189. prefect/_vendor/starlette/convertors.py +0 -87
  190. prefect/_vendor/starlette/datastructures.py +0 -707
  191. prefect/_vendor/starlette/endpoints.py +0 -130
  192. prefect/_vendor/starlette/exceptions.py +0 -60
  193. prefect/_vendor/starlette/formparsers.py +0 -276
  194. prefect/_vendor/starlette/middleware/__init__.py +0 -17
  195. prefect/_vendor/starlette/middleware/authentication.py +0 -52
  196. prefect/_vendor/starlette/middleware/base.py +0 -220
  197. prefect/_vendor/starlette/middleware/cors.py +0 -176
  198. prefect/_vendor/starlette/middleware/errors.py +0 -265
  199. prefect/_vendor/starlette/middleware/exceptions.py +0 -74
  200. prefect/_vendor/starlette/middleware/gzip.py +0 -113
  201. prefect/_vendor/starlette/middleware/httpsredirect.py +0 -19
  202. prefect/_vendor/starlette/middleware/sessions.py +0 -82
  203. prefect/_vendor/starlette/middleware/trustedhost.py +0 -64
  204. prefect/_vendor/starlette/middleware/wsgi.py +0 -147
  205. prefect/_vendor/starlette/requests.py +0 -328
  206. prefect/_vendor/starlette/responses.py +0 -347
  207. prefect/_vendor/starlette/routing.py +0 -933
  208. prefect/_vendor/starlette/schemas.py +0 -154
  209. prefect/_vendor/starlette/staticfiles.py +0 -248
  210. prefect/_vendor/starlette/status.py +0 -199
  211. prefect/_vendor/starlette/templating.py +0 -231
  212. prefect/_vendor/starlette/testclient.py +0 -804
  213. prefect/_vendor/starlette/types.py +0 -30
  214. prefect/_vendor/starlette/websockets.py +0 -193
  215. prefect/agent.py +0 -698
  216. prefect/deployments/deployments.py +0 -1042
  217. prefect/deprecated/__init__.py +0 -0
  218. prefect/deprecated/data_documents.py +0 -350
  219. prefect/deprecated/packaging/__init__.py +0 -12
  220. prefect/deprecated/packaging/base.py +0 -96
  221. prefect/deprecated/packaging/docker.py +0 -146
  222. prefect/deprecated/packaging/file.py +0 -92
  223. prefect/deprecated/packaging/orion.py +0 -80
  224. prefect/deprecated/packaging/serializers.py +0 -171
  225. prefect/events/instrument.py +0 -135
  226. prefect/infrastructure/base.py +0 -323
  227. prefect/infrastructure/container.py +0 -818
  228. prefect/infrastructure/kubernetes.py +0 -920
  229. prefect/infrastructure/process.py +0 -289
  230. prefect/new_task_engine.py +0 -423
  231. prefect/pydantic/__init__.py +0 -76
  232. prefect/pydantic/main.py +0 -39
  233. prefect/software/__init__.py +0 -2
  234. prefect/software/base.py +0 -50
  235. prefect/software/conda.py +0 -199
  236. prefect/software/pip.py +0 -122
  237. prefect/software/python.py +0 -52
  238. prefect/workers/block.py +0 -218
  239. prefect_client-2.19.4.dist-info/RECORD +0 -292
  240. {prefect_client-2.19.4.dist-info → prefect_client-3.0.0rc2.dist-info}/LICENSE +0 -0
  241. {prefect_client-2.19.4.dist-info → prefect_client-3.0.0rc2.dist-info}/WHEEL +0 -0
  242. {prefect_client-2.19.4.dist-info → prefect_client-3.0.0rc2.dist-info}/top_level.txt +0 -0
prefect/futures.py CHANGED
@@ -1,375 +1,242 @@
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 uuid
9
5
  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
6
+ from typing import Any, Generic, Optional, Set, Union, cast
7
+
8
+ from typing_extensions import TypeVar
9
+
10
+ from prefect.client.orchestration import get_client
11
+ from prefect.client.schemas.objects import TaskRun
12
+ from prefect.exceptions import ObjectNotFound
13
+ from prefect.logging.loggers import get_logger
14
+ from prefect.states import Pending, State
15
+ from prefect.task_runs import TaskRunWaiter
32
16
  from prefect.utilities.annotations import quote
33
- from prefect.utilities.asyncutils import A, Async, Sync, sync
17
+ from prefect.utilities.asyncutils import run_coro_as_sync
34
18
  from prefect.utilities.collections import StopVisiting, visit_collection
35
19
 
36
- if TYPE_CHECKING:
37
- from prefect.task_runners import BaseTaskRunner
20
+ F = TypeVar("F")
38
21
 
22
+ logger = get_logger(__name__)
39
23
 
40
- R = TypeVar("R")
41
24
 
42
-
43
- class PrefectFuture(Generic[R, A]):
25
+ class PrefectFuture(abc.ABC):
44
26
  """
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()
27
+ Abstract base class for Prefect futures. A Prefect future is a handle to the
28
+ asynchronous execution of a task run. It provides methods to wait for the task
29
+ to complete and to retrieve the result of the task run.
105
30
  """
106
31
 
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
- ...
32
+ def __init__(self, task_run_id: uuid.UUID):
33
+ self._task_run_id = task_run_id
34
+ self._final_state = None
135
35
 
136
- @overload
137
- def wait(
138
- self: "PrefectFuture[R, Async]", timeout: float
139
- ) -> Awaitable[Optional[State[R]]]:
140
- ...
36
+ @property
37
+ def task_run_id(self) -> uuid.UUID:
38
+ """The ID of the task run associated with this future"""
39
+ return self._task_run_id
141
40
 
142
- @overload
143
- def wait(self: "PrefectFuture[R, Sync]", timeout: float) -> Optional[State[R]]:
41
+ @property
42
+ def state(self) -> State:
43
+ """The current state of the task run associated with this future"""
44
+ if self._final_state:
45
+ return self._final_state
46
+ client = get_client(sync_client=True)
47
+ try:
48
+ task_run = cast(TaskRun, client.read_task_run(task_run_id=self.task_run_id))
49
+ except ObjectNotFound:
50
+ # We'll be optimistic and assume this task will eventually start
51
+ # TODO: Consider using task run events to wait for the task to start
52
+ return Pending()
53
+ return task_run.state or Pending()
54
+
55
+ @abc.abstractmethod
56
+ def wait(self, timeout: Optional[float] = None) -> None:
144
57
  ...
145
-
146
- def wait(self, timeout=None):
147
58
  """
148
- Wait for the run to finish and return the final state
59
+ Wait for the task run to complete.
149
60
 
150
- If the timeout is reached before the run reaches a final state,
151
- `None` is returned.
152
- """
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
61
+ If the task run has already completed, this method will return immediately.
159
62
 
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
- ...
167
-
168
- async def _wait(self, timeout=None):
169
- """
170
- Async implementation for `wait`
63
+ Args:
64
+ - timeout: The maximum number of seconds to wait for the task run to complete.
65
+ If the task run has not completed after the timeout has elapsed, this method will return.
171
66
  """
172
- await self._wait_for_submission()
173
-
174
- if self._final_state:
175
- return self._final_state
176
67
 
177
- self._final_state = await self._task_runner.wait(self.key, timeout)
178
- return self._final_state
179
-
180
- @overload
68
+ @abc.abstractmethod
181
69
  def result(
182
- self: "PrefectFuture[R, Sync]",
183
- timeout: float = None,
70
+ self,
71
+ timeout: Optional[float] = None,
184
72
  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]:
73
+ ) -> Any:
194
74
  ...
75
+ """
76
+ Get the result of the task run associated with this future.
195
77
 
196
- @overload
197
- def result(
198
- self: "PrefectFuture[R, Async]",
199
- timeout: float = None,
200
- raise_on_failure: bool = True,
201
- ) -> Awaitable[R]:
202
- ...
78
+ If the task run has not completed, this method will wait for the task run to complete.
203
79
 
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
- ...
80
+ Args:
81
+ - timeout: The maximum number of seconds to wait for the task run to complete.
82
+ If the task run has not completed after the timeout has elapsed, this method will return.
83
+ - raise_on_failure: If `True`, an exception will be raised if the task run fails.
211
84
 
212
- def result(self, timeout: float = None, raise_on_failure: bool = True):
85
+ Returns:
86
+ The result of the task run.
213
87
  """
214
- Wait for the run to finish and return the final state.
215
88
 
216
- If the timeout is reached before the run reaches a final state, a `TimeoutError`
217
- will be raised.
218
89
 
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()
90
+ class PrefectWrappedFuture(PrefectFuture, abc.ABC, Generic[F]):
91
+ """
92
+ A Prefect future that wraps another future object.
93
+ """
229
94
 
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
- ...
95
+ def __init__(self, task_run_id: uuid.UUID, wrapped_future: F):
96
+ self._wrapped_future = wrapped_future
97
+ super().__init__(task_run_id)
244
98
 
245
- @overload
246
- def get_state(
247
- self: "PrefectFuture[R, Sync]", client: PrefectClient = None
248
- ) -> State[R]:
249
- ...
99
+ @property
100
+ def wrapped_future(self) -> F:
101
+ """The underlying future object wrapped by this Prefect future"""
102
+ return self._wrapped_future
250
103
 
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
104
 
260
- @inject_client
261
- async def _get_state(self, client: PrefectClient = None) -> State[R]:
262
- assert client is not None # always injected
105
+ class PrefectConcurrentFuture(PrefectWrappedFuture[concurrent.futures.Future]):
106
+ """
107
+ A Prefect future that wraps a concurrent.futures.Future. This future is used
108
+ when the task run is submitted to a ThreadPoolExecutor.
109
+ """
263
110
 
264
- # We must wait for the task run id to be populated
265
- await self._wait_for_submission()
111
+ def wait(self, timeout: Optional[float] = None) -> None:
112
+ try:
113
+ result = self._wrapped_future.result(timeout=timeout)
114
+ except concurrent.futures.TimeoutError:
115
+ return
116
+ if isinstance(result, State):
117
+ self._final_state = result
266
118
 
267
- task_run = await client.read_task_run(self.task_run.id)
119
+ def result(
120
+ self,
121
+ timeout: Optional[float] = None,
122
+ raise_on_failure: bool = True,
123
+ ) -> Any:
124
+ if not self._final_state:
125
+ try:
126
+ future_result = self._wrapped_future.result(timeout=timeout)
127
+ except concurrent.futures.TimeoutError as exc:
128
+ raise TimeoutError(
129
+ f"Task run {self.task_run_id} did not complete within {timeout} seconds"
130
+ ) from exc
131
+
132
+ if isinstance(future_result, State):
133
+ self._final_state = future_result
134
+ else:
135
+ return future_result
136
+
137
+ _result = self._final_state.result(
138
+ raise_on_failure=raise_on_failure, fetch=True
139
+ )
140
+ # state.result is a `sync_compatible` function that may or may not return an awaitable
141
+ # depending on whether the parent frame is sync or not
142
+ if inspect.isawaitable(_result):
143
+ _result = run_coro_as_sync(_result)
144
+ return _result
268
145
 
269
- if not task_run:
270
- raise RuntimeError("Future has no associated task run in the server.")
271
146
 
272
- # Update the task run reference
273
- self.task_run = task_run
274
- return task_run.state
147
+ class PrefectDistributedFuture(PrefectFuture):
148
+ """
149
+ Represents the result of a computation happening anywhere.
275
150
 
276
- async def _wait_for_submission(self):
277
- await run_coroutine_in_loop_from_async(self._loop, self._submitted.wait())
151
+ This class is typically used to interact with the result of a task run
152
+ scheduled to run in a Prefect task worker but can be used to interact with
153
+ any task run scheduled in Prefect's API.
154
+ """
278
155
 
279
- def __hash__(self) -> int:
280
- return hash(self.key)
156
+ def wait(self, timeout: Optional[float] = None) -> None:
157
+ return run_coro_as_sync(self.wait_async(timeout=timeout))
281
158
 
282
- def __repr__(self) -> str:
283
- return f"PrefectFuture({self.name!r})"
159
+ async def wait_async(self, timeout: Optional[float] = None):
160
+ if self._final_state:
161
+ logger.debug(
162
+ "Final state already set for %s. Returning...", self.task_run_id
163
+ )
164
+ return
165
+
166
+ # Read task run to see if it is still running
167
+ async with get_client() as client:
168
+ task_run = await client.read_task_run(task_run_id=self._task_run_id)
169
+ if task_run.state.is_final():
170
+ logger.debug(
171
+ "Task run %s already finished. Returning...",
172
+ self.task_run_id,
173
+ )
174
+ self._final_state = task_run.state
175
+ return
176
+
177
+ # If still running, wait for a completed event from the server
178
+ logger.debug(
179
+ "Waiting for completed event for task run %s...",
180
+ self.task_run_id,
181
+ )
182
+ await TaskRunWaiter.wait_for_task_run(self._task_run_id, timeout=timeout)
183
+ task_run = await client.read_task_run(task_run_id=self._task_run_id)
184
+ if task_run.state.is_final():
185
+ self._final_state = task_run.state
186
+ return
284
187
 
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,
188
+ def result(
189
+ self,
190
+ timeout: Optional[float] = None,
191
+ raise_on_failure: bool = True,
192
+ ) -> Any:
193
+ return run_coro_as_sync(
194
+ self.result_async(timeout=timeout, raise_on_failure=raise_on_failure)
293
195
  )
294
- return True
295
-
296
196
 
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()
301
-
302
- if isinstance(expr, PrefectFuture):
303
- futures.add(expr)
197
+ async def result_async(
198
+ self,
199
+ timeout: Optional[float] = None,
200
+ raise_on_failure: bool = True,
201
+ ):
202
+ if not self._final_state:
203
+ await self.wait_async(timeout=timeout)
204
+ if not self._final_state:
205
+ raise TimeoutError(
206
+ f"Task run {self.task_run_id} did not complete within {timeout} seconds"
207
+ )
208
+
209
+ return await self._final_state.result(
210
+ raise_on_failure=raise_on_failure, fetch=True
211
+ )
304
212
 
305
- return expr
213
+ def __eq__(self, other):
214
+ if not isinstance(other, PrefectDistributedFuture):
215
+ return False
216
+ return self.task_run_id == other.task_run_id
306
217
 
307
218
 
308
- async def resolve_futures_to_data(
309
- expr: Union[PrefectFuture[R, Any], Any],
310
- raise_on_failure: bool = True,
311
- ) -> Union[R, Any]:
219
+ def resolve_futures_to_states(
220
+ expr: Union[PrefectFuture, Any],
221
+ ) -> Union[State, Any]:
312
222
  """
313
223
  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.
224
+ new collection with the same structure with futures resolved to their final states.
225
+ Resolving futures to their final states may wait for execution to complete.
317
226
 
318
227
  Unsupported object types will be returned without modification.
319
228
  """
320
229
  futures: Set[PrefectFuture] = set()
321
230
 
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
231
+ def _collect_futures(futures, expr, context):
232
+ # Expressions inside quotes should not be traversed
346
233
  if isinstance(context.get("annotation"), quote):
347
234
  raise StopVisiting()
348
235
 
349
236
  if isinstance(expr, PrefectFuture):
350
- return results_by_future[expr]
351
- else:
352
- return expr
353
-
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.
237
+ futures.add(expr)
369
238
 
370
- Unsupported object types will be returned without modification.
371
- """
372
- futures: Set[PrefectFuture] = set()
239
+ return expr
373
240
 
374
241
  visit_collection(
375
242
  expr,
@@ -379,13 +246,10 @@ async def resolve_futures_to_states(
379
246
  )
380
247
 
381
248
  # 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
- )
249
+ states = []
250
+ for future in futures:
251
+ future.wait()
252
+ states.append(future.state)
389
253
 
390
254
  states_by_future = dict(zip(futures, states))
391
255
 
@@ -405,25 +269,3 @@ async def resolve_futures_to_states(
405
269
  return_data=True,
406
270
  context={},
407
271
  )
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