prefect-client 3.1.5__py3-none-any.whl → 3.1.6__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 (93) hide show
  1. prefect/__init__.py +3 -0
  2. prefect/_internal/compatibility/migration.py +1 -1
  3. prefect/_internal/concurrency/api.py +52 -52
  4. prefect/_internal/concurrency/calls.py +59 -35
  5. prefect/_internal/concurrency/cancellation.py +34 -18
  6. prefect/_internal/concurrency/event_loop.py +7 -6
  7. prefect/_internal/concurrency/threads.py +41 -33
  8. prefect/_internal/concurrency/waiters.py +28 -21
  9. prefect/_internal/pydantic/v1_schema.py +2 -2
  10. prefect/_internal/pydantic/v2_schema.py +10 -9
  11. prefect/_internal/schemas/bases.py +9 -7
  12. prefect/_internal/schemas/validators.py +2 -1
  13. prefect/_version.py +3 -3
  14. prefect/automations.py +53 -47
  15. prefect/blocks/abstract.py +12 -10
  16. prefect/blocks/core.py +4 -2
  17. prefect/cache_policies.py +11 -11
  18. prefect/client/__init__.py +3 -1
  19. prefect/client/base.py +36 -37
  20. prefect/client/cloud.py +26 -19
  21. prefect/client/collections.py +2 -2
  22. prefect/client/orchestration.py +342 -273
  23. prefect/client/schemas/__init__.py +24 -0
  24. prefect/client/schemas/actions.py +123 -116
  25. prefect/client/schemas/objects.py +110 -81
  26. prefect/client/schemas/responses.py +18 -18
  27. prefect/client/schemas/schedules.py +136 -93
  28. prefect/client/subscriptions.py +28 -14
  29. prefect/client/utilities.py +32 -36
  30. prefect/concurrency/asyncio.py +6 -9
  31. prefect/concurrency/sync.py +35 -5
  32. prefect/context.py +39 -31
  33. prefect/deployments/flow_runs.py +3 -5
  34. prefect/docker/__init__.py +1 -1
  35. prefect/events/schemas/events.py +25 -20
  36. prefect/events/utilities.py +1 -2
  37. prefect/filesystems.py +3 -3
  38. prefect/flow_engine.py +61 -21
  39. prefect/flow_runs.py +3 -3
  40. prefect/flows.py +214 -170
  41. prefect/logging/configuration.py +1 -1
  42. prefect/logging/highlighters.py +1 -2
  43. prefect/logging/loggers.py +30 -20
  44. prefect/main.py +17 -24
  45. prefect/runner/runner.py +43 -21
  46. prefect/runner/server.py +30 -32
  47. prefect/runner/submit.py +3 -6
  48. prefect/runner/utils.py +6 -6
  49. prefect/runtime/flow_run.py +7 -0
  50. prefect/settings/constants.py +2 -2
  51. prefect/settings/legacy.py +1 -1
  52. prefect/settings/models/server/events.py +10 -0
  53. prefect/task_engine.py +72 -19
  54. prefect/task_runners.py +2 -2
  55. prefect/tasks.py +46 -33
  56. prefect/telemetry/bootstrap.py +15 -2
  57. prefect/telemetry/run_telemetry.py +107 -0
  58. prefect/transactions.py +14 -14
  59. prefect/types/__init__.py +1 -4
  60. prefect/utilities/_engine.py +96 -0
  61. prefect/utilities/annotations.py +25 -18
  62. prefect/utilities/asyncutils.py +126 -140
  63. prefect/utilities/callables.py +87 -78
  64. prefect/utilities/collections.py +278 -117
  65. prefect/utilities/compat.py +13 -21
  66. prefect/utilities/context.py +6 -5
  67. prefect/utilities/dispatch.py +23 -12
  68. prefect/utilities/dockerutils.py +33 -32
  69. prefect/utilities/engine.py +126 -239
  70. prefect/utilities/filesystem.py +18 -15
  71. prefect/utilities/hashing.py +10 -11
  72. prefect/utilities/importtools.py +40 -27
  73. prefect/utilities/math.py +9 -5
  74. prefect/utilities/names.py +3 -3
  75. prefect/utilities/processutils.py +121 -57
  76. prefect/utilities/pydantic.py +41 -36
  77. prefect/utilities/render_swagger.py +22 -12
  78. prefect/utilities/schema_tools/__init__.py +2 -1
  79. prefect/utilities/schema_tools/hydration.py +50 -43
  80. prefect/utilities/schema_tools/validation.py +52 -42
  81. prefect/utilities/services.py +13 -12
  82. prefect/utilities/templating.py +45 -45
  83. prefect/utilities/text.py +2 -1
  84. prefect/utilities/timeout.py +4 -4
  85. prefect/utilities/urls.py +9 -4
  86. prefect/utilities/visualization.py +46 -24
  87. prefect/variables.py +9 -8
  88. prefect/workers/base.py +15 -8
  89. {prefect_client-3.1.5.dist-info → prefect_client-3.1.6.dist-info}/METADATA +4 -2
  90. {prefect_client-3.1.5.dist-info → prefect_client-3.1.6.dist-info}/RECORD +93 -91
  91. {prefect_client-3.1.5.dist-info → prefect_client-3.1.6.dist-info}/LICENSE +0 -0
  92. {prefect_client-3.1.5.dist-info → prefect_client-3.1.6.dist-info}/WHEEL +0 -0
  93. {prefect_client-3.1.5.dist-info → prefect_client-3.1.6.dist-info}/top_level.txt +0 -0
@@ -12,14 +12,15 @@ import signal
12
12
  import sys
13
13
  import threading
14
14
  import time
15
- from typing import Callable, Dict, Optional, Type
15
+ from types import TracebackType
16
+ from typing import TYPE_CHECKING, Any, Callable, Optional, overload
16
17
 
17
18
  import anyio
18
19
 
19
20
  from prefect._internal.concurrency import logger
20
21
  from prefect._internal.concurrency.event_loop import get_running_loop
21
22
 
22
- _THREAD_SHIELDS: Dict[threading.Thread, "ThreadShield"] = {}
23
+ _THREAD_SHIELDS: dict[threading.Thread, "ThreadShield"] = {}
23
24
  _THREAD_SHIELDS_LOCK = threading.Lock()
24
25
 
25
26
 
@@ -42,14 +43,14 @@ class ThreadShield:
42
43
  # Uses the Python implementation of the RLock instead of the C implementation
43
44
  # because we need to inspect `_count` directly to check if the lock is active
44
45
  # which is needed for delayed exception raising during alarms
45
- self._lock = threading._RLock()
46
+ self._lock = threading._RLock() # type: ignore # yes, we want the private version
46
47
  self._exception = None
47
48
  self._owner = owner
48
49
 
49
50
  def __enter__(self) -> None:
50
51
  self._lock.__enter__()
51
52
 
52
- def __exit__(self, *exc_info):
53
+ def __exit__(self, *exc_info: Any):
53
54
  retval = self._lock.__exit__(*exc_info)
54
55
 
55
56
  # Raise the exception if this is the last shield to exit in the owner thread
@@ -65,14 +66,14 @@ class ThreadShield:
65
66
 
66
67
  return retval
67
68
 
68
- def set_exception(self, exc: Exception):
69
+ def set_exception(self, exc: BaseException):
69
70
  self._exception = exc
70
71
 
71
72
  def active(self) -> bool:
72
73
  """
73
74
  Returns true if the shield is active.
74
75
  """
75
- return self._lock._count > 0
76
+ return getattr(self._lock, "_count") > 0
76
77
 
77
78
 
78
79
  class CancelledError(asyncio.CancelledError):
@@ -82,7 +83,7 @@ class CancelledError(asyncio.CancelledError):
82
83
  pass
83
84
 
84
85
 
85
- def _get_thread_shield(thread) -> ThreadShield:
86
+ def _get_thread_shield(thread: threading.Thread) -> ThreadShield:
86
87
  with _THREAD_SHIELDS_LOCK:
87
88
  if thread not in _THREAD_SHIELDS:
88
89
  _THREAD_SHIELDS[thread] = ThreadShield(thread)
@@ -139,7 +140,7 @@ class CancelScope(abc.ABC):
139
140
  self._end_time = None
140
141
  self._timeout = timeout
141
142
  self._lock = threading.Lock()
142
- self._callbacks = []
143
+ self._callbacks: list[Callable[[], None]] = []
143
144
  super().__init__()
144
145
 
145
146
  def __enter__(self):
@@ -151,7 +152,9 @@ class CancelScope(abc.ABC):
151
152
  logger.debug("%r entered", self)
152
153
  return self
153
154
 
154
- def __exit__(self, *_):
155
+ def __exit__(
156
+ self, exc_type: type[BaseException], exc_val: Exception, exc_tb: TracebackType
157
+ ) -> Optional[bool]:
155
158
  with self._lock:
156
159
  if not self._cancelled:
157
160
  self._completed = True
@@ -195,7 +198,7 @@ class CancelScope(abc.ABC):
195
198
  throw the cancelled error.
196
199
  """
197
200
  with self._lock:
198
- if not self.started:
201
+ if not self._started:
199
202
  raise RuntimeError("Scope has not been entered.")
200
203
 
201
204
  if self._completed:
@@ -247,7 +250,6 @@ class AsyncCancelScope(CancelScope):
247
250
  self, name: Optional[str] = None, timeout: Optional[float] = None
248
251
  ) -> None:
249
252
  super().__init__(name=name, timeout=timeout)
250
- self.loop = None
251
253
 
252
254
  def __enter__(self):
253
255
  self.loop = asyncio.get_running_loop()
@@ -262,7 +264,9 @@ class AsyncCancelScope(CancelScope):
262
264
 
263
265
  return self
264
266
 
265
- def __exit__(self, exc_type, exc_val, exc_tb):
267
+ def __exit__(
268
+ self, exc_type: type[BaseException], exc_val: Exception, exc_tb: TracebackType
269
+ ) -> bool:
266
270
  if self._anyio_scope.cancel_called:
267
271
  # Mark as cancelled
268
272
  self.cancel(throw=False)
@@ -310,7 +314,7 @@ class NullCancelScope(CancelScope):
310
314
  super().__init__(name, timeout)
311
315
  self.reason = reason or "null cancel scope"
312
316
 
313
- def cancel(self):
317
+ def cancel(self, throw: bool = True) -> bool:
314
318
  logger.warning("%r cannot cancel %s.", self, self.reason)
315
319
  return False
316
320
 
@@ -355,7 +359,7 @@ class AlarmCancelScope(CancelScope):
355
359
 
356
360
  return self
357
361
 
358
- def _sigalarm_to_error(self, *args):
362
+ def _sigalarm_to_error(self, *args: object) -> None:
359
363
  logger.debug("%r captured alarm raising as cancelled error", self)
360
364
  if self.cancel(throw=False):
361
365
  shield = _get_thread_shield(threading.main_thread())
@@ -365,11 +369,13 @@ class AlarmCancelScope(CancelScope):
365
369
  else:
366
370
  raise CancelledError()
367
371
 
368
- def __exit__(self, *_):
372
+ def __exit__(self, *_: Any) -> Optional[bool]:
369
373
  retval = super().__exit__(*_)
370
374
 
371
375
  if self.timeout is not None:
372
376
  # Restore the previous timer
377
+ if TYPE_CHECKING:
378
+ assert self._previous_timer is not None
373
379
  signal.setitimer(signal.ITIMER_REAL, *self._previous_timer)
374
380
 
375
381
  # Restore the previous signal handler
@@ -417,7 +423,7 @@ class WatcherThreadCancelScope(CancelScope):
417
423
 
418
424
  return self
419
425
 
420
- def __exit__(self, *_):
426
+ def __exit__(self, *_: Any) -> Optional[bool]:
421
427
  retval = super().__exit__(*_)
422
428
  self._event.set()
423
429
  if self._enforcer_thread:
@@ -466,7 +472,17 @@ class WatcherThreadCancelScope(CancelScope):
466
472
  return True
467
473
 
468
474
 
469
- def get_deadline(timeout: Optional[float]):
475
+ @overload
476
+ def get_deadline(timeout: float) -> float:
477
+ ...
478
+
479
+
480
+ @overload
481
+ def get_deadline(timeout: None) -> None:
482
+ ...
483
+
484
+
485
+ def get_deadline(timeout: Optional[float]) -> Optional[float]:
470
486
  """
471
487
  Compute an deadline given a timeout.
472
488
 
@@ -572,7 +588,7 @@ def cancel_sync_after(timeout: Optional[float], name: Optional[str] = None):
572
588
  yield scope
573
589
 
574
590
 
575
- def _send_exception_to_thread(thread: threading.Thread, exc_type: Type[BaseException]):
591
+ def _send_exception_to_thread(thread: threading.Thread, exc_type: type[BaseException]):
576
592
  """
577
593
  Raise an exception in a thread.
578
594
 
@@ -5,7 +5,8 @@ Thread-safe utilities for working with asynchronous event loops.
5
5
  import asyncio
6
6
  import concurrent.futures
7
7
  import functools
8
- from typing import Awaitable, Callable, Coroutine, Optional, TypeVar
8
+ from collections.abc import Coroutine
9
+ from typing import Any, Callable, Optional, TypeVar
9
10
 
10
11
  from typing_extensions import ParamSpec
11
12
 
@@ -13,7 +14,7 @@ P = ParamSpec("P")
13
14
  T = TypeVar("T")
14
15
 
15
16
 
16
- def get_running_loop() -> Optional[asyncio.BaseEventLoop]:
17
+ def get_running_loop() -> Optional[asyncio.AbstractEventLoop]:
17
18
  """
18
19
  Get the current running loop.
19
20
 
@@ -30,7 +31,7 @@ def call_soon_in_loop(
30
31
  __fn: Callable[P, T],
31
32
  *args: P.args,
32
33
  **kwargs: P.kwargs,
33
- ) -> concurrent.futures.Future:
34
+ ) -> concurrent.futures.Future[T]:
34
35
  """
35
36
  Run a synchronous call in an event loop's thread from another thread.
36
37
 
@@ -38,7 +39,7 @@ def call_soon_in_loop(
38
39
 
39
40
  Returns a future that can be used to retrieve the result of the call.
40
41
  """
41
- future = concurrent.futures.Future()
42
+ future: concurrent.futures.Future[T] = concurrent.futures.Future()
42
43
 
43
44
  @functools.wraps(__fn)
44
45
  def wrapper() -> None:
@@ -62,8 +63,8 @@ def call_soon_in_loop(
62
63
 
63
64
 
64
65
  async def run_coroutine_in_loop_from_async(
65
- __loop: asyncio.AbstractEventLoop, __coro: Coroutine
66
- ) -> Awaitable:
66
+ __loop: asyncio.AbstractEventLoop, __coro: Coroutine[Any, Any, T]
67
+ ) -> T:
67
68
  """
68
69
  Run an asynchronous call in an event loop from an asynchronous context.
69
70
 
@@ -8,7 +8,9 @@ import concurrent.futures
8
8
  import itertools
9
9
  import queue
10
10
  import threading
11
- from typing import List, Optional
11
+ from typing import Any, Optional
12
+
13
+ from typing_extensions import TypeVar
12
14
 
13
15
  from prefect._internal.concurrency import logger
14
16
  from prefect._internal.concurrency.calls import Call, Portal
@@ -16,6 +18,8 @@ from prefect._internal.concurrency.cancellation import CancelledError
16
18
  from prefect._internal.concurrency.event_loop import get_running_loop
17
19
  from prefect._internal.concurrency.primitives import Event
18
20
 
21
+ T = TypeVar("T", infer_variance=True)
22
+
19
23
 
20
24
  class WorkerThread(Portal):
21
25
  """
@@ -33,7 +37,7 @@ class WorkerThread(Portal):
33
37
  self.thread = threading.Thread(
34
38
  name=name, daemon=daemon, target=self._entrypoint
35
39
  )
36
- self._queue = queue.Queue()
40
+ self._queue: queue.Queue[Optional[Call[Any]]] = queue.Queue()
37
41
  self._run_once: bool = run_once
38
42
  self._started: bool = False
39
43
  self._submitted_count: int = 0
@@ -42,7 +46,7 @@ class WorkerThread(Portal):
42
46
  if not daemon:
43
47
  atexit.register(self.shutdown)
44
48
 
45
- def start(self):
49
+ def start(self) -> None:
46
50
  """
47
51
  Start the worker thread.
48
52
  """
@@ -51,7 +55,7 @@ class WorkerThread(Portal):
51
55
  self._started = True
52
56
  self.thread.start()
53
57
 
54
- def submit(self, call: Call) -> Call:
58
+ def submit(self, call: Call[T]) -> Call[T]:
55
59
  if self._submitted_count > 0 and self._run_once:
56
60
  raise RuntimeError(
57
61
  "Worker configured to only run once. A call has already been submitted."
@@ -83,7 +87,7 @@ class WorkerThread(Portal):
83
87
  def name(self) -> str:
84
88
  return self.thread.name
85
89
 
86
- def _entrypoint(self):
90
+ def _entrypoint(self) -> None:
87
91
  """
88
92
  Entrypoint for the thread.
89
93
  """
@@ -129,12 +133,14 @@ class EventLoopThread(Portal):
129
133
  self.thread = threading.Thread(
130
134
  name=name, daemon=daemon, target=self._entrypoint
131
135
  )
132
- self._ready_future = concurrent.futures.Future()
136
+ self._ready_future: concurrent.futures.Future[
137
+ bool
138
+ ] = concurrent.futures.Future()
133
139
  self._loop: Optional[asyncio.AbstractEventLoop] = None
134
140
  self._shutdown_event: Event = Event()
135
141
  self._run_once: bool = run_once
136
142
  self._submitted_count: int = 0
137
- self._on_shutdown: List[Call] = []
143
+ self._on_shutdown: list[Call[Any]] = []
138
144
  self._lock = threading.Lock()
139
145
 
140
146
  if not daemon:
@@ -149,7 +155,7 @@ class EventLoopThread(Portal):
149
155
  self.thread.start()
150
156
  self._ready_future.result()
151
157
 
152
- def submit(self, call: Call) -> Call:
158
+ def submit(self, call: Call[T]) -> Call[T]:
153
159
  if self._loop is None:
154
160
  self.start()
155
161
 
@@ -167,6 +173,7 @@ class EventLoopThread(Portal):
167
173
  call.set_runner(self)
168
174
 
169
175
  # Submit the call to the event loop
176
+ assert self._loop is not None
170
177
  asyncio.run_coroutine_threadsafe(self._run_call(call), self._loop)
171
178
 
172
179
  self._submitted_count += 1
@@ -180,15 +187,16 @@ class EventLoopThread(Portal):
180
187
  Shutdown the worker thread. Does not wait for the thread to stop.
181
188
  """
182
189
  with self._lock:
183
- if self._shutdown_event is None:
184
- return
185
-
186
190
  self._shutdown_event.set()
187
191
 
188
192
  @property
189
193
  def name(self) -> str:
190
194
  return self.thread.name
191
195
 
196
+ @property
197
+ def running(self) -> bool:
198
+ return not self._shutdown_event.is_set()
199
+
192
200
  def _entrypoint(self):
193
201
  """
194
202
  Entrypoint for the thread.
@@ -218,12 +226,12 @@ class EventLoopThread(Portal):
218
226
  # Empty the list to allow calls to be garbage collected. Issue #10338.
219
227
  self._on_shutdown = []
220
228
 
221
- async def _run_call(self, call: Call) -> None:
229
+ async def _run_call(self, call: Call[Any]) -> None:
222
230
  task = call.run()
223
231
  if task is not None:
224
232
  await task
225
233
 
226
- def add_shutdown_call(self, call: Call) -> None:
234
+ def add_shutdown_call(self, call: Call[Any]) -> None:
227
235
  self._on_shutdown.append(call)
228
236
 
229
237
  def __enter__(self):
@@ -235,9 +243,9 @@ class EventLoopThread(Portal):
235
243
 
236
244
 
237
245
  # the GLOBAL LOOP is used for background services, like logs
238
- GLOBAL_LOOP: Optional[EventLoopThread] = None
246
+ _global_loop: Optional[EventLoopThread] = None
239
247
  # 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
248
+ _run_sync_loop: Optional[EventLoopThread] = None
241
249
 
242
250
 
243
251
  def get_global_loop() -> EventLoopThread:
@@ -246,29 +254,29 @@ def get_global_loop() -> EventLoopThread:
246
254
 
247
255
  Creates a new one if there is not one available.
248
256
  """
249
- global GLOBAL_LOOP
257
+ global _global_loop
250
258
 
251
259
  # Create a new worker on first call or if the existing worker is dead
252
260
  if (
253
- GLOBAL_LOOP is None
254
- or not GLOBAL_LOOP.thread.is_alive()
255
- or GLOBAL_LOOP._shutdown_event.is_set()
261
+ _global_loop is None
262
+ or not _global_loop.thread.is_alive()
263
+ or not _global_loop.running
256
264
  ):
257
- GLOBAL_LOOP = EventLoopThread(daemon=True, name="GlobalEventLoopThread")
258
- GLOBAL_LOOP.start()
265
+ _global_loop = EventLoopThread(daemon=True, name="GlobalEventLoopThread")
266
+ _global_loop.start()
259
267
 
260
- return GLOBAL_LOOP
268
+ return _global_loop
261
269
 
262
270
 
263
271
  def in_global_loop() -> bool:
264
272
  """
265
273
  Check if called from the global loop.
266
274
  """
267
- if GLOBAL_LOOP is None:
275
+ if _global_loop is None:
268
276
  # Avoid creating a global loop if there isn't one
269
277
  return False
270
278
 
271
- return get_global_loop()._loop == get_running_loop()
279
+ return getattr(get_global_loop(), "_loop") == get_running_loop()
272
280
 
273
281
 
274
282
  def get_run_sync_loop() -> EventLoopThread:
@@ -277,29 +285,29 @@ def get_run_sync_loop() -> EventLoopThread:
277
285
 
278
286
  Creates a new one if there is not one available.
279
287
  """
280
- global RUN_SYNC_LOOP
288
+ global _run_sync_loop
281
289
 
282
290
  # Create a new worker on first call or if the existing worker is dead
283
291
  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()
292
+ _run_sync_loop is None
293
+ or not _run_sync_loop.thread.is_alive()
294
+ or not _run_sync_loop.running
287
295
  ):
288
- RUN_SYNC_LOOP = EventLoopThread(daemon=True, name="RunSyncEventLoopThread")
289
- RUN_SYNC_LOOP.start()
296
+ _run_sync_loop = EventLoopThread(daemon=True, name="RunSyncEventLoopThread")
297
+ _run_sync_loop.start()
290
298
 
291
- return RUN_SYNC_LOOP
299
+ return _run_sync_loop
292
300
 
293
301
 
294
302
  def in_run_sync_loop() -> bool:
295
303
  """
296
304
  Check if called from the global loop.
297
305
  """
298
- if RUN_SYNC_LOOP is None:
306
+ if _run_sync_loop is None:
299
307
  # Avoid creating a global loop if there isn't one
300
308
  return False
301
309
 
302
- return get_run_sync_loop()._loop == get_running_loop()
310
+ return getattr(get_run_sync_loop(), "_loop") == get_running_loop()
303
311
 
304
312
 
305
313
  def wait_for_global_loop_exit(timeout: Optional[float] = None) -> None:
@@ -10,7 +10,8 @@ import inspect
10
10
  import queue
11
11
  import threading
12
12
  from collections import deque
13
- from typing import Awaitable, Generic, List, Optional, TypeVar, Union
13
+ from collections.abc import Awaitable
14
+ from typing import TYPE_CHECKING, Any, Generic, Optional, TypeVar
14
15
  from weakref import WeakKeyDictionary
15
16
 
16
17
  import anyio
@@ -24,12 +25,12 @@ T = TypeVar("T")
24
25
 
25
26
 
26
27
  # Waiters are stored in a stack for each thread
27
- _WAITERS_BY_THREAD: "WeakKeyDictionary[threading.Thread, deque[Waiter]]" = (
28
+ _WAITERS_BY_THREAD: "WeakKeyDictionary[threading.Thread, deque[Waiter[Any]]]" = (
28
29
  WeakKeyDictionary()
29
30
  )
30
31
 
31
32
 
32
- def add_waiter_for_thread(waiter: "Waiter", thread: threading.Thread):
33
+ def add_waiter_for_thread(waiter: "Waiter[Any]", thread: threading.Thread) -> None:
33
34
  """
34
35
  Add a waiter for a thread.
35
36
  """
@@ -62,7 +63,7 @@ class Waiter(Portal, abc.ABC, Generic[T]):
62
63
  return self._call.future.done()
63
64
 
64
65
  @abc.abstractmethod
65
- def wait(self) -> Union[Awaitable[None], None]:
66
+ def wait(self) -> T:
66
67
  """
67
68
  Wait for the call to finish.
68
69
 
@@ -71,7 +72,7 @@ class Waiter(Portal, abc.ABC, Generic[T]):
71
72
  raise NotImplementedError()
72
73
 
73
74
  @abc.abstractmethod
74
- def add_done_callback(self, callback: Call) -> Call:
75
+ def add_done_callback(self, callback: Call[Any]) -> None:
75
76
  """
76
77
  Schedule a call to run when the waiter is done waiting.
77
78
 
@@ -91,11 +92,11 @@ class SyncWaiter(Waiter[T]):
91
92
 
92
93
  def __init__(self, call: Call[T]) -> None:
93
94
  super().__init__(call=call)
94
- self._queue: queue.Queue = queue.Queue()
95
- self._done_callbacks = []
95
+ self._queue: queue.Queue[Optional[Call[T]]] = queue.Queue()
96
+ self._done_callbacks: list[Call[Any]] = []
96
97
  self._done_event = threading.Event()
97
98
 
98
- def submit(self, call: Call):
99
+ def submit(self, call: Call[T]) -> Call[T]:
99
100
  """
100
101
  Submit a callback to execute while waiting.
101
102
  """
@@ -109,7 +110,7 @@ class SyncWaiter(Waiter[T]):
109
110
  def _handle_waiting_callbacks(self):
110
111
  logger.debug("Waiter %r watching for callbacks", self)
111
112
  while True:
112
- callback: Call = self._queue.get()
113
+ callback = self._queue.get()
113
114
  if callback is None:
114
115
  break
115
116
 
@@ -130,13 +131,13 @@ class SyncWaiter(Waiter[T]):
130
131
  if callback:
131
132
  callback.run()
132
133
 
133
- def add_done_callback(self, callback: Call):
134
+ def add_done_callback(self, callback: Call[Any]) -> None:
134
135
  if self._done_event.is_set():
135
136
  raise RuntimeError("Cannot add done callbacks to done waiters.")
136
137
  else:
137
138
  self._done_callbacks.append(callback)
138
139
 
139
- def wait(self) -> T:
140
+ def wait(self) -> Call[T]:
140
141
  # Stop watching for work once the future is done
141
142
  self._call.future.add_done_callback(lambda _: self._queue.put_nowait(None))
142
143
  self._call.future.add_done_callback(lambda _: self._done_event.set())
@@ -159,13 +160,13 @@ class AsyncWaiter(Waiter[T]):
159
160
 
160
161
  # Delay instantiating loop and queue as there may not be a loop present yet
161
162
  self._loop: Optional[asyncio.AbstractEventLoop] = None
162
- self._queue: Optional[asyncio.Queue] = None
163
- self._early_submissions: List[Call] = []
164
- self._done_callbacks = []
163
+ self._queue: Optional[asyncio.Queue[Optional[Call[T]]]] = None
164
+ self._early_submissions: list[Call[T]] = []
165
+ self._done_callbacks: list[Call[Any]] = []
165
166
  self._done_event = Event()
166
167
  self._done_waiting = False
167
168
 
168
- def submit(self, call: Call):
169
+ def submit(self, call: Call[T]) -> Call[T]:
169
170
  """
170
171
  Submit a callback to execute while waiting.
171
172
  """
@@ -180,11 +181,15 @@ class AsyncWaiter(Waiter[T]):
180
181
  return call
181
182
 
182
183
  # We must put items in the queue from the event loop that owns it
184
+ if TYPE_CHECKING:
185
+ assert self._loop is not None
183
186
  call_soon_in_loop(self._loop, self._queue.put_nowait, call)
184
187
  return call
185
188
 
186
- def _resubmit_early_submissions(self):
187
- assert self._queue
189
+ def _resubmit_early_submissions(self) -> None:
190
+ if TYPE_CHECKING:
191
+ assert self._queue is not None
192
+ assert self._loop is not None
188
193
  for call in self._early_submissions:
189
194
  # We must put items in the queue from the event loop that owns it
190
195
  call_soon_in_loop(self._loop, self._queue.put_nowait, call)
@@ -192,11 +197,11 @@ class AsyncWaiter(Waiter[T]):
192
197
 
193
198
  async def _handle_waiting_callbacks(self):
194
199
  logger.debug("Waiter %r watching for callbacks", self)
195
- tasks = []
200
+ tasks: list[Awaitable[None]] = []
196
201
 
197
202
  try:
198
203
  while True:
199
- callback: Call = await self._queue.get()
204
+ callback = await self._queue.get()
200
205
  if callback is None:
201
206
  break
202
207
 
@@ -228,12 +233,12 @@ class AsyncWaiter(Waiter[T]):
228
233
  with anyio.CancelScope(shield=True):
229
234
  await self._run_done_callback(callback)
230
235
 
231
- async def _run_done_callback(self, callback: Call):
236
+ async def _run_done_callback(self, callback: Call[Any]) -> None:
232
237
  coro = callback.run()
233
238
  if coro:
234
239
  await coro
235
240
 
236
- def add_done_callback(self, callback: Call):
241
+ def add_done_callback(self, callback: Call[Any]) -> None:
237
242
  if self._done_event.is_set():
238
243
  raise RuntimeError("Cannot add done callbacks to done waiters.")
239
244
  else:
@@ -243,6 +248,8 @@ class AsyncWaiter(Waiter[T]):
243
248
  # Only send a `None` to the queue if the waiter is still blocked reading from
244
249
  # the queue. Otherwise, it's possible that the event loop is stopped.
245
250
  if not self._done_waiting:
251
+ assert self._loop is not None
252
+ assert self._queue is not None
246
253
  call_soon_in_loop(self._loop, self._queue.put_nowait, None)
247
254
 
248
255
  async def wait(self) -> Call[T]:
@@ -6,7 +6,7 @@ import pydantic
6
6
  from pydantic.v1 import BaseModel as V1BaseModel
7
7
 
8
8
 
9
- def is_v1_model(v) -> bool:
9
+ def is_v1_model(v: typing.Any) -> bool:
10
10
  with warnings.catch_warnings():
11
11
  warnings.filterwarnings(
12
12
  "ignore", category=pydantic.warnings.PydanticDeprecatedSince20
@@ -23,7 +23,7 @@ def is_v1_model(v) -> bool:
23
23
  return False
24
24
 
25
25
 
26
- def is_v1_type(v) -> bool:
26
+ def is_v1_type(v: typing.Any) -> bool:
27
27
  if is_v1_model(v):
28
28
  return True
29
29
 
@@ -16,7 +16,7 @@ from prefect._internal.pydantic.annotations.pendulum import (
16
16
  from prefect._internal.pydantic.schemas import GenerateEmptySchemaForUserClasses
17
17
 
18
18
 
19
- def is_v2_model(v) -> bool:
19
+ def is_v2_model(v: t.Any) -> bool:
20
20
  if isinstance(v, V2BaseModel):
21
21
  return True
22
22
  try:
@@ -28,7 +28,7 @@ def is_v2_model(v) -> bool:
28
28
  return False
29
29
 
30
30
 
31
- def is_v2_type(v) -> bool:
31
+ def is_v2_type(v: t.Any) -> bool:
32
32
  if is_v2_model(v):
33
33
  return True
34
34
 
@@ -56,9 +56,9 @@ def process_v2_params(
56
56
  param: inspect.Parameter,
57
57
  *,
58
58
  position: int,
59
- docstrings: t.Dict[str, str],
60
- aliases: t.Dict,
61
- ) -> t.Tuple[str, t.Any, "pydantic.Field"]:
59
+ docstrings: dict[str, str],
60
+ aliases: dict[str, str],
61
+ ) -> tuple[str, t.Any, t.Any]:
62
62
  """
63
63
  Generate a sanitized name, type, and pydantic.Field for a given parameter.
64
64
 
@@ -72,7 +72,7 @@ def process_v2_params(
72
72
  else:
73
73
  name = param.name
74
74
 
75
- type_ = t.Any if param.annotation is inspect._empty else param.annotation
75
+ type_ = t.Any if param.annotation is inspect.Parameter.empty else param.annotation
76
76
 
77
77
  # Replace pendulum type annotations with our own so that they are pydantic compatible
78
78
  if type_ == pendulum.DateTime:
@@ -95,12 +95,13 @@ def process_v2_params(
95
95
  def create_v2_schema(
96
96
  name_: str,
97
97
  model_cfg: t.Optional[ConfigDict] = None,
98
- model_base: t.Optional[t.Type[V2BaseModel]] = None,
99
- **model_fields,
100
- ):
98
+ model_base: t.Optional[type[V2BaseModel]] = None,
99
+ model_fields: t.Optional[dict[str, t.Any]] = None,
100
+ ) -> dict[str, t.Any]:
101
101
  """
102
102
  Create a pydantic v2 model and craft a v1 compatible schema from it.
103
103
  """
104
+ model_fields = model_fields or {}
104
105
  model = create_model(
105
106
  name_, __config__=model_cfg, __base__=model_base, **model_fields
106
107
  )