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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. prefect/__init__.py +74 -110
  2. prefect/_internal/compatibility/deprecated.py +6 -115
  3. prefect/_internal/compatibility/experimental.py +4 -79
  4. prefect/_internal/compatibility/migration.py +166 -0
  5. prefect/_internal/concurrency/__init__.py +2 -2
  6. prefect/_internal/concurrency/api.py +1 -35
  7. prefect/_internal/concurrency/calls.py +0 -6
  8. prefect/_internal/concurrency/cancellation.py +0 -3
  9. prefect/_internal/concurrency/event_loop.py +0 -20
  10. prefect/_internal/concurrency/inspection.py +3 -3
  11. prefect/_internal/concurrency/primitives.py +1 -0
  12. prefect/_internal/concurrency/services.py +23 -0
  13. prefect/_internal/concurrency/threads.py +35 -0
  14. prefect/_internal/concurrency/waiters.py +0 -28
  15. prefect/_internal/integrations.py +7 -0
  16. prefect/_internal/pydantic/__init__.py +0 -45
  17. prefect/_internal/pydantic/annotations/pendulum.py +2 -2
  18. prefect/_internal/pydantic/v1_schema.py +21 -22
  19. prefect/_internal/pydantic/v2_schema.py +0 -2
  20. prefect/_internal/pydantic/v2_validated_func.py +18 -23
  21. prefect/_internal/pytz.py +1 -1
  22. prefect/_internal/retries.py +61 -0
  23. prefect/_internal/schemas/bases.py +45 -177
  24. prefect/_internal/schemas/fields.py +1 -43
  25. prefect/_internal/schemas/validators.py +47 -233
  26. prefect/agent.py +3 -695
  27. prefect/artifacts.py +173 -14
  28. prefect/automations.py +39 -4
  29. prefect/blocks/abstract.py +1 -1
  30. prefect/blocks/core.py +405 -153
  31. prefect/blocks/fields.py +2 -57
  32. prefect/blocks/notifications.py +43 -28
  33. prefect/blocks/redis.py +168 -0
  34. prefect/blocks/system.py +67 -20
  35. prefect/blocks/webhook.py +2 -9
  36. prefect/cache_policies.py +239 -0
  37. prefect/client/__init__.py +4 -0
  38. prefect/client/base.py +33 -27
  39. prefect/client/cloud.py +65 -20
  40. prefect/client/collections.py +1 -1
  41. prefect/client/orchestration.py +650 -442
  42. prefect/client/schemas/actions.py +115 -100
  43. prefect/client/schemas/filters.py +46 -52
  44. prefect/client/schemas/objects.py +228 -178
  45. prefect/client/schemas/responses.py +18 -36
  46. prefect/client/schemas/schedules.py +55 -36
  47. prefect/client/schemas/sorting.py +2 -0
  48. prefect/client/subscriptions.py +8 -7
  49. prefect/client/types/flexible_schedule_list.py +11 -0
  50. prefect/client/utilities.py +9 -6
  51. prefect/concurrency/asyncio.py +60 -11
  52. prefect/concurrency/context.py +24 -0
  53. prefect/concurrency/events.py +2 -2
  54. prefect/concurrency/services.py +46 -16
  55. prefect/concurrency/sync.py +51 -7
  56. prefect/concurrency/v1/asyncio.py +143 -0
  57. prefect/concurrency/v1/context.py +27 -0
  58. prefect/concurrency/v1/events.py +61 -0
  59. prefect/concurrency/v1/services.py +116 -0
  60. prefect/concurrency/v1/sync.py +92 -0
  61. prefect/context.py +246 -149
  62. prefect/deployments/__init__.py +33 -18
  63. prefect/deployments/base.py +10 -15
  64. prefect/deployments/deployments.py +2 -1048
  65. prefect/deployments/flow_runs.py +178 -0
  66. prefect/deployments/runner.py +72 -173
  67. prefect/deployments/schedules.py +31 -25
  68. prefect/deployments/steps/__init__.py +0 -1
  69. prefect/deployments/steps/core.py +7 -0
  70. prefect/deployments/steps/pull.py +15 -21
  71. prefect/deployments/steps/utility.py +2 -1
  72. prefect/docker/__init__.py +20 -0
  73. prefect/docker/docker_image.py +82 -0
  74. prefect/engine.py +15 -2475
  75. prefect/events/actions.py +17 -23
  76. prefect/events/cli/automations.py +20 -7
  77. prefect/events/clients.py +142 -80
  78. prefect/events/filters.py +14 -18
  79. prefect/events/related.py +74 -75
  80. prefect/events/schemas/__init__.py +0 -5
  81. prefect/events/schemas/automations.py +55 -46
  82. prefect/events/schemas/deployment_triggers.py +7 -197
  83. prefect/events/schemas/events.py +46 -65
  84. prefect/events/schemas/labelling.py +10 -14
  85. prefect/events/utilities.py +4 -5
  86. prefect/events/worker.py +23 -8
  87. prefect/exceptions.py +15 -0
  88. prefect/filesystems.py +30 -529
  89. prefect/flow_engine.py +827 -0
  90. prefect/flow_runs.py +379 -7
  91. prefect/flows.py +470 -360
  92. prefect/futures.py +382 -331
  93. prefect/infrastructure/__init__.py +5 -26
  94. prefect/infrastructure/base.py +3 -320
  95. prefect/infrastructure/provisioners/__init__.py +5 -3
  96. prefect/infrastructure/provisioners/cloud_run.py +13 -8
  97. prefect/infrastructure/provisioners/container_instance.py +14 -9
  98. prefect/infrastructure/provisioners/ecs.py +10 -8
  99. prefect/infrastructure/provisioners/modal.py +8 -5
  100. prefect/input/__init__.py +4 -0
  101. prefect/input/actions.py +2 -4
  102. prefect/input/run_input.py +9 -9
  103. prefect/logging/formatters.py +2 -4
  104. prefect/logging/handlers.py +9 -14
  105. prefect/logging/loggers.py +5 -5
  106. prefect/main.py +72 -0
  107. prefect/plugins.py +2 -64
  108. prefect/profiles.toml +16 -2
  109. prefect/records/__init__.py +1 -0
  110. prefect/records/base.py +223 -0
  111. prefect/records/filesystem.py +207 -0
  112. prefect/records/memory.py +178 -0
  113. prefect/records/result_store.py +64 -0
  114. prefect/results.py +577 -504
  115. prefect/runner/runner.py +117 -47
  116. prefect/runner/server.py +32 -34
  117. prefect/runner/storage.py +3 -12
  118. prefect/runner/submit.py +2 -10
  119. prefect/runner/utils.py +2 -2
  120. prefect/runtime/__init__.py +1 -0
  121. prefect/runtime/deployment.py +1 -0
  122. prefect/runtime/flow_run.py +40 -5
  123. prefect/runtime/task_run.py +1 -0
  124. prefect/serializers.py +28 -39
  125. prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
  126. prefect/settings.py +209 -332
  127. prefect/states.py +160 -63
  128. prefect/task_engine.py +1478 -57
  129. prefect/task_runners.py +383 -287
  130. prefect/task_runs.py +240 -0
  131. prefect/task_worker.py +463 -0
  132. prefect/tasks.py +684 -374
  133. prefect/transactions.py +410 -0
  134. prefect/types/__init__.py +72 -86
  135. prefect/types/entrypoint.py +13 -0
  136. prefect/utilities/annotations.py +4 -3
  137. prefect/utilities/asyncutils.py +227 -148
  138. prefect/utilities/callables.py +137 -45
  139. prefect/utilities/collections.py +134 -86
  140. prefect/utilities/dispatch.py +27 -14
  141. prefect/utilities/dockerutils.py +11 -4
  142. prefect/utilities/engine.py +186 -32
  143. prefect/utilities/filesystem.py +4 -5
  144. prefect/utilities/importtools.py +26 -27
  145. prefect/utilities/pydantic.py +128 -38
  146. prefect/utilities/schema_tools/hydration.py +18 -1
  147. prefect/utilities/schema_tools/validation.py +30 -0
  148. prefect/utilities/services.py +35 -9
  149. prefect/utilities/templating.py +12 -2
  150. prefect/utilities/timeout.py +20 -5
  151. prefect/utilities/urls.py +195 -0
  152. prefect/utilities/visualization.py +1 -0
  153. prefect/variables.py +78 -59
  154. prefect/workers/__init__.py +0 -1
  155. prefect/workers/base.py +237 -244
  156. prefect/workers/block.py +5 -226
  157. prefect/workers/cloud.py +6 -0
  158. prefect/workers/process.py +265 -12
  159. prefect/workers/server.py +29 -11
  160. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/METADATA +28 -24
  161. prefect_client-3.0.0.dist-info/RECORD +201 -0
  162. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/WHEEL +1 -1
  163. prefect/_internal/pydantic/_base_model.py +0 -51
  164. prefect/_internal/pydantic/_compat.py +0 -82
  165. prefect/_internal/pydantic/_flags.py +0 -20
  166. prefect/_internal/pydantic/_types.py +0 -8
  167. prefect/_internal/pydantic/utilities/config_dict.py +0 -72
  168. prefect/_internal/pydantic/utilities/field_validator.py +0 -150
  169. prefect/_internal/pydantic/utilities/model_construct.py +0 -56
  170. prefect/_internal/pydantic/utilities/model_copy.py +0 -55
  171. prefect/_internal/pydantic/utilities/model_dump.py +0 -136
  172. prefect/_internal/pydantic/utilities/model_dump_json.py +0 -112
  173. prefect/_internal/pydantic/utilities/model_fields.py +0 -50
  174. prefect/_internal/pydantic/utilities/model_fields_set.py +0 -29
  175. prefect/_internal/pydantic/utilities/model_json_schema.py +0 -82
  176. prefect/_internal/pydantic/utilities/model_rebuild.py +0 -80
  177. prefect/_internal/pydantic/utilities/model_validate.py +0 -75
  178. prefect/_internal/pydantic/utilities/model_validate_json.py +0 -68
  179. prefect/_internal/pydantic/utilities/model_validator.py +0 -87
  180. prefect/_internal/pydantic/utilities/type_adapter.py +0 -71
  181. prefect/_vendor/fastapi/__init__.py +0 -25
  182. prefect/_vendor/fastapi/applications.py +0 -946
  183. prefect/_vendor/fastapi/background.py +0 -3
  184. prefect/_vendor/fastapi/concurrency.py +0 -44
  185. prefect/_vendor/fastapi/datastructures.py +0 -58
  186. prefect/_vendor/fastapi/dependencies/__init__.py +0 -0
  187. prefect/_vendor/fastapi/dependencies/models.py +0 -64
  188. prefect/_vendor/fastapi/dependencies/utils.py +0 -877
  189. prefect/_vendor/fastapi/encoders.py +0 -177
  190. prefect/_vendor/fastapi/exception_handlers.py +0 -40
  191. prefect/_vendor/fastapi/exceptions.py +0 -46
  192. prefect/_vendor/fastapi/logger.py +0 -3
  193. prefect/_vendor/fastapi/middleware/__init__.py +0 -1
  194. prefect/_vendor/fastapi/middleware/asyncexitstack.py +0 -25
  195. prefect/_vendor/fastapi/middleware/cors.py +0 -3
  196. prefect/_vendor/fastapi/middleware/gzip.py +0 -3
  197. prefect/_vendor/fastapi/middleware/httpsredirect.py +0 -3
  198. prefect/_vendor/fastapi/middleware/trustedhost.py +0 -3
  199. prefect/_vendor/fastapi/middleware/wsgi.py +0 -3
  200. prefect/_vendor/fastapi/openapi/__init__.py +0 -0
  201. prefect/_vendor/fastapi/openapi/constants.py +0 -2
  202. prefect/_vendor/fastapi/openapi/docs.py +0 -203
  203. prefect/_vendor/fastapi/openapi/models.py +0 -480
  204. prefect/_vendor/fastapi/openapi/utils.py +0 -485
  205. prefect/_vendor/fastapi/param_functions.py +0 -340
  206. prefect/_vendor/fastapi/params.py +0 -453
  207. prefect/_vendor/fastapi/py.typed +0 -0
  208. prefect/_vendor/fastapi/requests.py +0 -4
  209. prefect/_vendor/fastapi/responses.py +0 -40
  210. prefect/_vendor/fastapi/routing.py +0 -1331
  211. prefect/_vendor/fastapi/security/__init__.py +0 -15
  212. prefect/_vendor/fastapi/security/api_key.py +0 -98
  213. prefect/_vendor/fastapi/security/base.py +0 -6
  214. prefect/_vendor/fastapi/security/http.py +0 -172
  215. prefect/_vendor/fastapi/security/oauth2.py +0 -227
  216. prefect/_vendor/fastapi/security/open_id_connect_url.py +0 -34
  217. prefect/_vendor/fastapi/security/utils.py +0 -10
  218. prefect/_vendor/fastapi/staticfiles.py +0 -1
  219. prefect/_vendor/fastapi/templating.py +0 -3
  220. prefect/_vendor/fastapi/testclient.py +0 -1
  221. prefect/_vendor/fastapi/types.py +0 -3
  222. prefect/_vendor/fastapi/utils.py +0 -235
  223. prefect/_vendor/fastapi/websockets.py +0 -7
  224. prefect/_vendor/starlette/__init__.py +0 -1
  225. prefect/_vendor/starlette/_compat.py +0 -28
  226. prefect/_vendor/starlette/_exception_handler.py +0 -80
  227. prefect/_vendor/starlette/_utils.py +0 -88
  228. prefect/_vendor/starlette/applications.py +0 -261
  229. prefect/_vendor/starlette/authentication.py +0 -159
  230. prefect/_vendor/starlette/background.py +0 -43
  231. prefect/_vendor/starlette/concurrency.py +0 -59
  232. prefect/_vendor/starlette/config.py +0 -151
  233. prefect/_vendor/starlette/convertors.py +0 -87
  234. prefect/_vendor/starlette/datastructures.py +0 -707
  235. prefect/_vendor/starlette/endpoints.py +0 -130
  236. prefect/_vendor/starlette/exceptions.py +0 -60
  237. prefect/_vendor/starlette/formparsers.py +0 -276
  238. prefect/_vendor/starlette/middleware/__init__.py +0 -17
  239. prefect/_vendor/starlette/middleware/authentication.py +0 -52
  240. prefect/_vendor/starlette/middleware/base.py +0 -220
  241. prefect/_vendor/starlette/middleware/cors.py +0 -176
  242. prefect/_vendor/starlette/middleware/errors.py +0 -265
  243. prefect/_vendor/starlette/middleware/exceptions.py +0 -74
  244. prefect/_vendor/starlette/middleware/gzip.py +0 -113
  245. prefect/_vendor/starlette/middleware/httpsredirect.py +0 -19
  246. prefect/_vendor/starlette/middleware/sessions.py +0 -82
  247. prefect/_vendor/starlette/middleware/trustedhost.py +0 -64
  248. prefect/_vendor/starlette/middleware/wsgi.py +0 -147
  249. prefect/_vendor/starlette/py.typed +0 -0
  250. prefect/_vendor/starlette/requests.py +0 -328
  251. prefect/_vendor/starlette/responses.py +0 -347
  252. prefect/_vendor/starlette/routing.py +0 -933
  253. prefect/_vendor/starlette/schemas.py +0 -154
  254. prefect/_vendor/starlette/staticfiles.py +0 -248
  255. prefect/_vendor/starlette/status.py +0 -199
  256. prefect/_vendor/starlette/templating.py +0 -231
  257. prefect/_vendor/starlette/testclient.py +0 -804
  258. prefect/_vendor/starlette/types.py +0 -30
  259. prefect/_vendor/starlette/websockets.py +0 -193
  260. prefect/blocks/kubernetes.py +0 -119
  261. prefect/deprecated/__init__.py +0 -0
  262. prefect/deprecated/data_documents.py +0 -350
  263. prefect/deprecated/packaging/__init__.py +0 -12
  264. prefect/deprecated/packaging/base.py +0 -96
  265. prefect/deprecated/packaging/docker.py +0 -146
  266. prefect/deprecated/packaging/file.py +0 -92
  267. prefect/deprecated/packaging/orion.py +0 -80
  268. prefect/deprecated/packaging/serializers.py +0 -171
  269. prefect/events/instrument.py +0 -135
  270. prefect/infrastructure/container.py +0 -824
  271. prefect/infrastructure/kubernetes.py +0 -920
  272. prefect/infrastructure/process.py +0 -289
  273. prefect/manifests.py +0 -20
  274. prefect/new_flow_engine.py +0 -449
  275. prefect/new_task_engine.py +0 -423
  276. prefect/pydantic/__init__.py +0 -76
  277. prefect/pydantic/main.py +0 -39
  278. prefect/software/__init__.py +0 -2
  279. prefect/software/base.py +0 -50
  280. prefect/software/conda.py +0 -199
  281. prefect/software/pip.py +0 -122
  282. prefect/software/python.py +0 -52
  283. prefect/task_server.py +0 -322
  284. prefect_client-2.20.4.dist-info/RECORD +0 -294
  285. /prefect/{_internal/pydantic/utilities → client/types}/__init__.py +0 -0
  286. /prefect/{_vendor → concurrency/v1}/__init__.py +0 -0
  287. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/LICENSE +0 -0
  288. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,6 @@ import abc
6
6
  import asyncio
7
7
  import concurrent.futures
8
8
  import contextlib
9
- import threading
10
9
  from typing import (
11
10
  Awaitable,
12
11
  Callable,
@@ -19,7 +18,6 @@ from typing import (
19
18
 
20
19
  from typing_extensions import ParamSpec
21
20
 
22
- from prefect._internal.concurrency.calls import get_current_call
23
21
  from prefect._internal.concurrency.threads import (
24
22
  WorkerThread,
25
23
  get_global_loop,
@@ -29,7 +27,6 @@ from prefect._internal.concurrency.waiters import (
29
27
  AsyncWaiter,
30
28
  Call,
31
29
  SyncWaiter,
32
- get_waiter_for_thread,
33
30
  )
34
31
 
35
32
  P = ParamSpec("P")
@@ -154,7 +151,7 @@ class from_async(_base):
154
151
  __call: Union[Callable[[], T], Call[T]],
155
152
  timeout: Optional[float] = None,
156
153
  done_callbacks: Optional[Iterable[Call]] = None,
157
- ) -> Call[T]:
154
+ ) -> T:
158
155
  call = _cast_to_call(__call)
159
156
  waiter = AsyncWaiter(call=call)
160
157
  for callback in done_callbacks or []:
@@ -163,22 +160,6 @@ class from_async(_base):
163
160
  await waiter.wait()
164
161
  return call.result()
165
162
 
166
- @staticmethod
167
- def call_soon_in_waiting_thread(
168
- __call: Union[Callable[[], T], Call[T]],
169
- thread: threading.Thread,
170
- timeout: Optional[float] = None,
171
- ) -> Call[T]:
172
- call = _cast_to_call(__call)
173
- parent_call = get_current_call()
174
- waiter = get_waiter_for_thread(thread, parent_call)
175
- if waiter is None:
176
- raise RuntimeError(f"No waiter found for thread {thread}.")
177
-
178
- call.set_timeout(timeout)
179
- waiter.submit(call)
180
- return call
181
-
182
163
  @staticmethod
183
164
  def call_in_new_thread(
184
165
  __call: Union[Callable[[], T], Call[T]], timeout: Optional[float] = None
@@ -231,21 +212,6 @@ class from_sync(_base):
231
212
  waiter.wait()
232
213
  return call.result()
233
214
 
234
- @staticmethod
235
- def call_soon_in_waiting_thread(
236
- __call: Union[Callable[[], T], Call[T]],
237
- thread: threading.Thread,
238
- timeout: Optional[float] = None,
239
- ) -> Call[T]:
240
- call = _cast_to_call(__call)
241
- waiter = get_waiter_for_thread(thread)
242
- if waiter is None:
243
- raise RuntimeError(f"No waiter found for thread {thread}.")
244
-
245
- call.set_timeout(timeout)
246
- waiter.submit(call)
247
- return call
248
-
249
215
  @staticmethod
250
216
  def call_in_new_thread(
251
217
  __call: Union[Callable[[], T], Call[T]], timeout: Optional[float] = None
@@ -49,12 +49,6 @@ current_call: contextvars.ContextVar["weakref.ref[Call]"] = ( # novm
49
49
  _ASYNC_TASK_REFS = set()
50
50
 
51
51
 
52
- def get_current_call() -> Optional["Call"]:
53
- call_ref = current_call.get(None)
54
- if call_ref:
55
- return call_ref()
56
-
57
-
58
52
  @contextlib.contextmanager
59
53
  def set_current_call(call: "Call"):
60
54
  token = current_call.set(weakref.ref(call))
@@ -76,9 +76,6 @@ class ThreadShield:
76
76
 
77
77
 
78
78
  class CancelledError(asyncio.CancelledError):
79
- # In Python 3.7, `asyncio.CancelledError` is identical to `concurrent.futures.CancelledError`
80
- # but in 3.8+ it is a separate class that inherits from `BaseException` instead
81
- # See https://bugs.python.org/issue32528
82
79
  # We want our `CancelledError` to be treated as a `BaseException` and defining it
83
80
  # here simplifies downstream logic that needs to know "which" cancelled error to
84
81
  # handle.
@@ -25,26 +25,6 @@ def get_running_loop() -> Optional[asyncio.BaseEventLoop]:
25
25
  return None
26
26
 
27
27
 
28
- def call_in_loop(
29
- __loop: asyncio.AbstractEventLoop,
30
- __fn: Callable[P, T],
31
- *args: P.args,
32
- **kwargs: P.kwargs,
33
- ) -> T:
34
- """
35
- Run a synchronous call in event loop's thread from another thread.
36
-
37
- This function is blocking and not safe to call from an asynchronous context.
38
-
39
- Returns the result of the call.
40
- """
41
- if __loop is get_running_loop():
42
- return __fn(*args, **kwargs)
43
- else:
44
- future = call_soon_in_loop(__loop, __fn, *args, **kwargs)
45
- return future.result()
46
-
47
-
48
28
  def call_soon_in_loop(
49
29
  __loop: asyncio.AbstractEventLoop,
50
30
  __fn: Callable[P, T],
@@ -7,10 +7,10 @@ import linecache
7
7
  import sys
8
8
  import threading
9
9
  from types import FrameType
10
- from typing import List
10
+ from typing import List, Optional
11
11
 
12
12
  """
13
- The following functions are derived from dask/distributed which is licensed under the
13
+ The following functions are derived from dask/distributed which is licensed under the
14
14
  BSD 3-Clause License.
15
15
 
16
16
  Copyright (c) 2015, Anaconda, Inc. and contributors
@@ -75,7 +75,7 @@ def repr_frame(frame: FrameType) -> str:
75
75
  def call_stack(frame: FrameType) -> List[str]:
76
76
  """Create a call text stack from a frame"""
77
77
  L = []
78
- cur_frame: FrameType | None = frame
78
+ cur_frame: Optional[FrameType] = frame
79
79
  while cur_frame:
80
80
  L.append(repr_frame(cur_frame))
81
81
  cur_frame = cur_frame.f_back
@@ -1,6 +1,7 @@
1
1
  """
2
2
  Thread-safe async synchronization primitives.
3
3
  """
4
+
4
5
  import asyncio
5
6
  import collections
6
7
  import threading
@@ -39,6 +39,7 @@ class QueueService(abc.ABC, Generic[T]):
39
39
  daemon=True,
40
40
  name=f"{type(self).__name__}Thread",
41
41
  )
42
+ self._logger = logging.getLogger(f"{type(self).__name__}")
42
43
 
43
44
  def start(self):
44
45
  logger.debug("Starting service %r", self)
@@ -144,13 +145,27 @@ class QueueService(abc.ABC, Generic[T]):
144
145
  self._done_event.set()
145
146
 
146
147
  async def _main_loop(self):
148
+ last_log_time = 0
149
+ log_interval = 4 # log every 4 seconds
150
+
147
151
  while True:
148
152
  item: T = await self._queue_get_thread.submit(
149
153
  create_call(self._queue.get)
150
154
  ).aresult()
151
155
 
156
+ if self._stopped:
157
+ current_time = asyncio.get_event_loop().time()
158
+ queue_size = self._queue.qsize()
159
+
160
+ if current_time - last_log_time >= log_interval and queue_size > 0:
161
+ self._logger.warning(
162
+ f"Still processing items: {queue_size} items remaining..."
163
+ )
164
+ last_log_time = current_time
165
+
152
166
  if item is None:
153
167
  logger.debug("Exiting service %r", self)
168
+ self._queue.task_done()
154
169
  break
155
170
 
156
171
  try:
@@ -164,6 +179,8 @@ class QueueService(abc.ABC, Generic[T]):
164
179
  item,
165
180
  exc_info=log_traceback,
166
181
  )
182
+ finally:
183
+ self._queue.task_done()
167
184
 
168
185
  @abc.abstractmethod
169
186
  async def _handle(self, item: T):
@@ -235,6 +252,12 @@ class QueueService(abc.ABC, Generic[T]):
235
252
  else:
236
253
  return concurrent.futures.wait(futures, timeout=timeout)
237
254
 
255
+ def wait_until_empty(self):
256
+ """
257
+ Wait until the queue is empty and all items have been processed.
258
+ """
259
+ self._queue.join()
260
+
238
261
  @classmethod
239
262
  def instance(cls: Type[Self], *args) -> Self:
240
263
  """
@@ -1,6 +1,7 @@
1
1
  """
2
2
  Utilities for managing worker threads.
3
3
  """
4
+
4
5
  import asyncio
5
6
  import atexit
6
7
  import concurrent.futures
@@ -233,7 +234,10 @@ class EventLoopThread(Portal):
233
234
  self.shutdown()
234
235
 
235
236
 
237
+ # the GLOBAL LOOP is used for background services, like logs
236
238
  GLOBAL_LOOP: Optional[EventLoopThread] = None
239
+ # the RUN SYNC LOOP is used exclusively for running async functions in a sync context via asyncutils.run_sync
240
+ RUN_SYNC_LOOP: Optional[EventLoopThread] = None
237
241
 
238
242
 
239
243
  def get_global_loop() -> EventLoopThread:
@@ -267,6 +271,37 @@ def in_global_loop() -> bool:
267
271
  return get_global_loop()._loop == get_running_loop()
268
272
 
269
273
 
274
+ def get_run_sync_loop() -> EventLoopThread:
275
+ """
276
+ Get the run_sync loop thread.
277
+
278
+ Creates a new one if there is not one available.
279
+ """
280
+ global RUN_SYNC_LOOP
281
+
282
+ # Create a new worker on first call or if the existing worker is dead
283
+ if (
284
+ RUN_SYNC_LOOP is None
285
+ or not RUN_SYNC_LOOP.thread.is_alive()
286
+ or RUN_SYNC_LOOP._shutdown_event.is_set()
287
+ ):
288
+ RUN_SYNC_LOOP = EventLoopThread(daemon=True, name="RunSyncEventLoopThread")
289
+ RUN_SYNC_LOOP.start()
290
+
291
+ return RUN_SYNC_LOOP
292
+
293
+
294
+ def in_run_sync_loop() -> bool:
295
+ """
296
+ Check if called from the global loop.
297
+ """
298
+ if RUN_SYNC_LOOP is None:
299
+ # Avoid creating a global loop if there isn't one
300
+ return False
301
+
302
+ return get_run_sync_loop()._loop == get_running_loop()
303
+
304
+
270
305
  def wait_for_global_loop_exit(timeout: Optional[float] = None) -> None:
271
306
  """
272
307
  Shutdown the global loop and wait for it to exit.
@@ -29,34 +29,6 @@ _WAITERS_BY_THREAD: "WeakKeyDictionary[threading.Thread, deque[Waiter]]" = (
29
29
  )
30
30
 
31
31
 
32
- def get_waiter_for_thread(
33
- thread: threading.Thread, parent_call: Optional[Call] = None
34
- ) -> Optional["Waiter"]:
35
- """
36
- Get the current waiter for a thread and an optional parent call.
37
-
38
- To avoid assigning outer callbacks to inner waiters in the case of nested calls,
39
- the parent call is used to determine which waiter to return. If a parent call is
40
- not provided, we return the most recently created waiter (last in the stack).
41
-
42
- see https://github.com/PrefectHQ/prefect/issues/12036
43
-
44
- Returns `None` if no active waiter is found for the thread.
45
- """
46
-
47
- waiters: "Optional[deque[Waiter]]" = _WAITERS_BY_THREAD.get(thread)
48
-
49
- if waiters and (active_waiters := [w for w in waiters if not w.call_is_done()]):
50
- if parent_call and (
51
- matching_waiter := next(
52
- (w for w in active_waiters if w._call == parent_call), None
53
- )
54
- ): # if exists an active waiter responsible for the parent call, return it
55
- return matching_waiter
56
- else: # otherwise, return the most recently created waiter
57
- return active_waiters[-1]
58
-
59
-
60
32
  def add_waiter_for_thread(waiter: "Waiter", thread: threading.Thread):
61
33
  """
62
34
  Add a waiter for a thread.
@@ -0,0 +1,7 @@
1
+ KNOWN_EXTRAS_FOR_PACKAGES = {
2
+ "prefect-kubernetes": "prefect[kubernetes]",
3
+ "prefect-aws": "prefect[aws]",
4
+ "prefect-gcp": "prefect[gcp]",
5
+ "prefect-azure": "prefect[azure]",
6
+ "prefect-docker": "prefect[docker]",
7
+ }
@@ -1,46 +1 @@
1
- ### A convenience module to allow for easy switching between pydantic versions.
2
1
 
3
- ### Note this introduces a marginally worse import time, since
4
- ### the import of any one of these symbols will import all of them.
5
-
6
- ### This is a tradeoff we're willing to make for now until pydantic v1 is
7
- ### no longer supported.
8
-
9
-
10
- from ._flags import HAS_PYDANTIC_V2
11
-
12
- from ._compat import (
13
- model_dump,
14
- model_json_schema,
15
- model_validate,
16
- model_dump_json,
17
- model_copy,
18
- model_validate_json,
19
- TypeAdapter,
20
- validate_python,
21
- BaseModel,
22
- Field,
23
- FieldInfo,
24
- field_validator,
25
- model_validator,
26
- )
27
-
28
- from ._types import IncEx
29
-
30
- __all__ = [
31
- "model_dump",
32
- "model_json_schema",
33
- "model_validate",
34
- "IncEx",
35
- "model_dump_json",
36
- "model_copy",
37
- "model_validate_json",
38
- "TypeAdapter",
39
- "validate_python",
40
- "BaseModel",
41
- "HAS_PYDANTIC_V2",
42
- "Field",
43
- "FieldInfo",
44
- "field_validator",
45
- "model_validator",
46
- ]
@@ -1,6 +1,6 @@
1
1
  """
2
- This file contains compat code to handle pendulum.DateTime objects during jsonschema
3
- generation and validation.
2
+ This file contains compat code to handle pendulum.DateTime objects during jsonschema
3
+ generation and validation.
4
4
  """
5
5
 
6
6
  import typing as t
@@ -1,37 +1,36 @@
1
1
  import inspect
2
2
  import typing
3
+ import warnings
3
4
 
4
- from prefect._internal.pydantic import HAS_PYDANTIC_V2
5
-
6
- if HAS_PYDANTIC_V2:
7
- from pydantic.v1 import BaseModel as V1BaseModel
8
- else:
9
- from pydantic import BaseModel as V1BaseModel
5
+ import pydantic
6
+ from pydantic.v1 import BaseModel as V1BaseModel
10
7
 
11
8
 
12
9
  def is_v1_model(v) -> bool:
13
- if isinstance(v, V1BaseModel):
14
- return True
15
- try:
16
- if inspect.isclass(v) and issubclass(v, V1BaseModel):
10
+ with warnings.catch_warnings():
11
+ warnings.filterwarnings(
12
+ "ignore", category=pydantic.warnings.PydanticDeprecatedSince20
13
+ )
14
+
15
+ if isinstance(v, V1BaseModel):
17
16
  return True
18
- except TypeError:
19
- pass
17
+ try:
18
+ if inspect.isclass(v) and issubclass(v, V1BaseModel):
19
+ return True
20
+ except TypeError:
21
+ pass
20
22
 
21
- return False
23
+ return False
22
24
 
23
25
 
24
26
  def is_v1_type(v) -> bool:
25
- if HAS_PYDANTIC_V2:
26
- if is_v1_model(v):
27
- return True
28
-
29
- try:
30
- return v.__module__.startswith("pydantic.v1.types")
31
- except AttributeError:
32
- return False
27
+ if is_v1_model(v):
28
+ return True
33
29
 
34
- return True
30
+ try:
31
+ return v.__module__.startswith("pydantic.v1.types")
32
+ except AttributeError:
33
+ return False
35
34
 
36
35
 
37
36
  def has_v1_type_as_param(signature: inspect.Signature) -> bool:
@@ -1,5 +1,3 @@
1
- # Note: This file should only be imported under a HAS_PYDANTIC_V2 flag
2
-
3
1
  import inspect
4
2
  import typing
5
3
  import typing as t
@@ -1,16 +1,15 @@
1
1
  """
2
- This module contains an implementation of pydantic v1's ValidateFunction
2
+ This module contains an implementation of pydantic v1's ValidateFunction
3
3
  modified to validate function arguments and return a pydantic v2 model.
4
4
 
5
- Specifically it allows for us to validate v2 models used as flow/task
5
+ Specifically it allows for us to validate v2 models used as flow/task
6
6
  arguments.
7
7
  """
8
8
 
9
9
  from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, Union
10
10
 
11
11
  # importing directly from v2 to be able to create a v2 model
12
- from pydantic import BaseModel, create_model
13
- from pydantic.v1 import validator
12
+ from pydantic import BaseModel, ConfigDict, create_model, field_validator
14
13
  from pydantic.v1.decorator import ValidatedFunction
15
14
  from pydantic.v1.errors import ConfigError
16
15
  from pydantic.v1.utils import to_camel
@@ -28,28 +27,25 @@ class V2ValidatedFunction(ValidatedFunction):
28
27
  fields: Dict[str, Any],
29
28
  takes_args: bool,
30
29
  takes_kwargs: bool,
31
- config: "ConfigType",
30
+ config: ConfigDict,
32
31
  ) -> None:
33
32
  pos_args = len(self.arg_mapping)
34
33
 
35
- class CustomConfig:
36
- pass
37
-
38
- if not TYPE_CHECKING: # pragma: no branch
39
- if isinstance(config, dict):
40
- CustomConfig = type("Config", (), config) # noqa: F811
41
- elif config is not None:
42
- CustomConfig = config # noqa: F811
43
-
44
- if hasattr(CustomConfig, "fields") or hasattr(CustomConfig, "alias_generator"):
34
+ if config.get("fields") or config.get("alias_generator"):
45
35
  raise ConfigError(
46
36
  'Setting the "fields" and "alias_generator" property on custom Config'
47
37
  " for @validate_arguments is not yet supported, please remove."
48
38
  )
49
39
 
40
+ if "extra" not in config:
41
+ config["extra"] = "forbid"
42
+
50
43
  # This is the key change -- inheriting the BaseModel class from v2
51
44
  class DecoratorBaseModel(BaseModel):
52
- @validator(self.v_args_name, check_fields=False, allow_reuse=True)
45
+ model_config = config
46
+
47
+ @field_validator(self.v_args_name, check_fields=False)
48
+ @classmethod
53
49
  def check_args(cls, v: Optional[List[Any]]) -> Optional[List[Any]]:
54
50
  if takes_args or v is None:
55
51
  return v
@@ -59,7 +55,8 @@ class V2ValidatedFunction(ValidatedFunction):
59
55
  f" {pos_args + len(v)} given"
60
56
  )
61
57
 
62
- @validator(self.v_kwargs_name, check_fields=False, allow_reuse=True)
58
+ @field_validator(self.v_kwargs_name, check_fields=False)
59
+ @classmethod
63
60
  def check_kwargs(
64
61
  cls, v: Optional[Dict[str, Any]]
65
62
  ) -> Optional[Dict[str, Any]]:
@@ -70,7 +67,8 @@ class V2ValidatedFunction(ValidatedFunction):
70
67
  keys = ", ".join(map(repr, v.keys()))
71
68
  raise TypeError(f"unexpected keyword argument{plural}: {keys}")
72
69
 
73
- @validator(V_POSITIONAL_ONLY_NAME, check_fields=False, allow_reuse=True)
70
+ @field_validator(V_POSITIONAL_ONLY_NAME, check_fields=False)
71
+ @classmethod
74
72
  def check_positional_only(cls, v: Optional[List[str]]) -> None:
75
73
  if v is None:
76
74
  return
@@ -82,7 +80,8 @@ class V2ValidatedFunction(ValidatedFunction):
82
80
  f" argument{plural}: {keys}"
83
81
  )
84
82
 
85
- @validator(V_DUPLICATE_KWARGS, check_fields=False, allow_reuse=True)
83
+ @field_validator(V_DUPLICATE_KWARGS, check_fields=False)
84
+ @classmethod
86
85
  def check_duplicate_kwargs(cls, v: Optional[List[str]]) -> None:
87
86
  if v is None:
88
87
  return
@@ -91,10 +90,6 @@ class V2ValidatedFunction(ValidatedFunction):
91
90
  keys = ", ".join(map(repr, v))
92
91
  raise TypeError(f"multiple values for argument{plural}: {keys}")
93
92
 
94
- class Config(CustomConfig):
95
- # extra = getattr(CustomConfig, "extra", Extra.forbid)
96
- extra = getattr(CustomConfig, "extra", "forbid")
97
-
98
93
  self.model = create_model(
99
94
  to_camel(self.raw_function.__name__),
100
95
  __base__=DecoratorBaseModel,
prefect/_internal/pytz.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """
2
- This is a compatibility module that allows us to drop our dependency on pytz in the minimal prefect-client.
2
+ This is a compatibility module that allows us to drop our dependency on pytz in the minimal prefect-client.
3
3
 
4
4
  All values here are taken from:
5
5
 
@@ -0,0 +1,61 @@
1
+ import asyncio
2
+ from functools import wraps
3
+ from typing import Any, Callable, Tuple, Type
4
+
5
+ from prefect.logging.loggers import get_logger
6
+ from prefect.utilities.math import clamped_poisson_interval
7
+
8
+ logger = get_logger("retries")
9
+
10
+
11
+ def exponential_backoff_with_jitter(
12
+ attempt: int, base_delay: float, max_delay: float
13
+ ) -> float:
14
+ average_interval = min(base_delay * (2**attempt), max_delay)
15
+ return clamped_poisson_interval(average_interval, clamping_factor=0.3)
16
+
17
+
18
+ def retry_async_fn(
19
+ max_attempts: int = 3,
20
+ backoff_strategy: Callable[
21
+ [int, float, float], float
22
+ ] = exponential_backoff_with_jitter,
23
+ base_delay: float = 1,
24
+ max_delay: float = 10,
25
+ retry_on_exceptions: Tuple[Type[Exception], ...] = (Exception,),
26
+ ):
27
+ """A decorator for retrying an async function.
28
+
29
+ Args:
30
+ max_attempts: The maximum number of times to retry the function.
31
+ backoff_strategy: A function that takes in the number of attempts, the base
32
+ delay, and the maximum delay, and returns the delay to use for the next
33
+ attempt. Defaults to an exponential backoff with jitter.
34
+ base_delay: The base delay to use for the first attempt.
35
+ max_delay: The maximum delay to use for the last attempt.
36
+ retry_on_exceptions: A tuple of exception types to retry on. Defaults to
37
+ retrying on all exceptions.
38
+ """
39
+
40
+ def decorator(func):
41
+ @wraps(func)
42
+ async def wrapper(*args: Any, **kwargs: Any) -> Any:
43
+ for attempt in range(max_attempts):
44
+ try:
45
+ return await func(*args, **kwargs)
46
+ except retry_on_exceptions as e:
47
+ if attempt == max_attempts - 1:
48
+ logger.exception(
49
+ f"Function {func.__name__!r} failed after {max_attempts} attempts"
50
+ )
51
+ raise
52
+ delay = backoff_strategy(attempt, base_delay, max_delay)
53
+ logger.warning(
54
+ f"Attempt {attempt + 1} of function {func.__name__!r} failed with {type(e).__name__}. "
55
+ f"Retrying in {delay:.2f} seconds..."
56
+ )
57
+ await asyncio.sleep(delay)
58
+
59
+ return wrapper
60
+
61
+ return decorator