prefect-client 3.1.5__py3-none-any.whl → 3.1.7__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 (114) hide show
  1. prefect/__init__.py +3 -0
  2. prefect/_experimental/__init__.py +0 -0
  3. prefect/_experimental/lineage.py +181 -0
  4. prefect/_internal/compatibility/async_dispatch.py +38 -9
  5. prefect/_internal/compatibility/migration.py +1 -1
  6. prefect/_internal/concurrency/api.py +52 -52
  7. prefect/_internal/concurrency/calls.py +59 -35
  8. prefect/_internal/concurrency/cancellation.py +34 -18
  9. prefect/_internal/concurrency/event_loop.py +7 -6
  10. prefect/_internal/concurrency/threads.py +41 -33
  11. prefect/_internal/concurrency/waiters.py +28 -21
  12. prefect/_internal/pydantic/v1_schema.py +2 -2
  13. prefect/_internal/pydantic/v2_schema.py +10 -9
  14. prefect/_internal/pydantic/v2_validated_func.py +15 -10
  15. prefect/_internal/retries.py +15 -6
  16. prefect/_internal/schemas/bases.py +11 -8
  17. prefect/_internal/schemas/validators.py +7 -5
  18. prefect/_version.py +3 -3
  19. prefect/automations.py +53 -47
  20. prefect/blocks/abstract.py +12 -10
  21. prefect/blocks/core.py +148 -19
  22. prefect/blocks/system.py +2 -1
  23. prefect/cache_policies.py +11 -11
  24. prefect/client/__init__.py +3 -1
  25. prefect/client/base.py +36 -37
  26. prefect/client/cloud.py +26 -19
  27. prefect/client/collections.py +2 -2
  28. prefect/client/orchestration.py +430 -273
  29. prefect/client/schemas/__init__.py +24 -0
  30. prefect/client/schemas/actions.py +128 -121
  31. prefect/client/schemas/filters.py +1 -1
  32. prefect/client/schemas/objects.py +114 -85
  33. prefect/client/schemas/responses.py +19 -20
  34. prefect/client/schemas/schedules.py +136 -93
  35. prefect/client/subscriptions.py +30 -15
  36. prefect/client/utilities.py +46 -36
  37. prefect/concurrency/asyncio.py +6 -9
  38. prefect/concurrency/sync.py +35 -5
  39. prefect/context.py +40 -32
  40. prefect/deployments/flow_runs.py +6 -8
  41. prefect/deployments/runner.py +14 -14
  42. prefect/deployments/steps/core.py +3 -1
  43. prefect/deployments/steps/pull.py +60 -12
  44. prefect/docker/__init__.py +1 -1
  45. prefect/events/clients.py +55 -4
  46. prefect/events/filters.py +1 -1
  47. prefect/events/related.py +2 -1
  48. prefect/events/schemas/events.py +26 -21
  49. prefect/events/utilities.py +3 -2
  50. prefect/events/worker.py +8 -0
  51. prefect/filesystems.py +3 -3
  52. prefect/flow_engine.py +87 -87
  53. prefect/flow_runs.py +7 -5
  54. prefect/flows.py +218 -176
  55. prefect/logging/configuration.py +1 -1
  56. prefect/logging/highlighters.py +1 -2
  57. prefect/logging/loggers.py +30 -20
  58. prefect/main.py +17 -24
  59. prefect/results.py +43 -22
  60. prefect/runner/runner.py +43 -21
  61. prefect/runner/server.py +30 -32
  62. prefect/runner/storage.py +3 -3
  63. prefect/runner/submit.py +3 -6
  64. prefect/runner/utils.py +6 -6
  65. prefect/runtime/flow_run.py +7 -0
  66. prefect/serializers.py +28 -24
  67. prefect/settings/constants.py +2 -2
  68. prefect/settings/legacy.py +1 -1
  69. prefect/settings/models/experiments.py +5 -0
  70. prefect/settings/models/server/events.py +10 -0
  71. prefect/task_engine.py +87 -26
  72. prefect/task_runners.py +2 -2
  73. prefect/task_worker.py +43 -25
  74. prefect/tasks.py +148 -142
  75. prefect/telemetry/bootstrap.py +15 -2
  76. prefect/telemetry/instrumentation.py +1 -1
  77. prefect/telemetry/processors.py +10 -7
  78. prefect/telemetry/run_telemetry.py +231 -0
  79. prefect/transactions.py +14 -14
  80. prefect/types/__init__.py +5 -5
  81. prefect/utilities/_engine.py +96 -0
  82. prefect/utilities/annotations.py +25 -18
  83. prefect/utilities/asyncutils.py +126 -140
  84. prefect/utilities/callables.py +87 -78
  85. prefect/utilities/collections.py +278 -117
  86. prefect/utilities/compat.py +13 -21
  87. prefect/utilities/context.py +6 -5
  88. prefect/utilities/dispatch.py +23 -12
  89. prefect/utilities/dockerutils.py +33 -32
  90. prefect/utilities/engine.py +126 -239
  91. prefect/utilities/filesystem.py +18 -15
  92. prefect/utilities/hashing.py +10 -11
  93. prefect/utilities/importtools.py +40 -27
  94. prefect/utilities/math.py +9 -5
  95. prefect/utilities/names.py +3 -3
  96. prefect/utilities/processutils.py +121 -57
  97. prefect/utilities/pydantic.py +41 -36
  98. prefect/utilities/render_swagger.py +22 -12
  99. prefect/utilities/schema_tools/__init__.py +2 -1
  100. prefect/utilities/schema_tools/hydration.py +50 -43
  101. prefect/utilities/schema_tools/validation.py +52 -42
  102. prefect/utilities/services.py +13 -12
  103. prefect/utilities/templating.py +45 -45
  104. prefect/utilities/text.py +2 -1
  105. prefect/utilities/timeout.py +4 -4
  106. prefect/utilities/urls.py +9 -4
  107. prefect/utilities/visualization.py +46 -24
  108. prefect/variables.py +136 -27
  109. prefect/workers/base.py +15 -8
  110. {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/METADATA +5 -2
  111. {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/RECORD +114 -110
  112. {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/LICENSE +0 -0
  113. {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/WHEEL +0 -0
  114. {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/top_level.txt +0 -0
@@ -12,18 +12,20 @@ import dataclasses
12
12
  import inspect
13
13
  import threading
14
14
  import weakref
15
+ from collections.abc import Awaitable, Generator
15
16
  from concurrent.futures._base import (
16
17
  CANCELLED,
17
18
  CANCELLED_AND_NOTIFIED,
18
19
  FINISHED,
19
20
  RUNNING,
20
21
  )
21
- from typing import Any, Awaitable, Callable, Dict, Generic, Optional, Tuple, TypeVar
22
+ from typing import TYPE_CHECKING, Any, Callable, Generic, Optional, Union
22
23
 
23
- from typing_extensions import ParamSpec
24
+ from typing_extensions import ParamSpec, Self, TypeAlias, TypeVar, TypeVarTuple
24
25
 
25
26
  from prefect._internal.concurrency import logger
26
27
  from prefect._internal.concurrency.cancellation import (
28
+ AsyncCancelScope,
27
29
  CancelledError,
28
30
  cancel_async_at,
29
31
  cancel_sync_at,
@@ -31,9 +33,13 @@ from prefect._internal.concurrency.cancellation import (
31
33
  )
32
34
  from prefect._internal.concurrency.event_loop import get_running_loop
33
35
 
34
- T = TypeVar("T")
36
+ T = TypeVar("T", infer_variance=True)
37
+ Ts = TypeVarTuple("Ts")
35
38
  P = ParamSpec("P")
36
39
 
40
+ _SyncOrAsyncCallable: TypeAlias = Callable[P, Union[T, Awaitable[T]]]
41
+
42
+
37
43
  # Tracks the current call being executed. Note that storing the `Call`
38
44
  # object for an async call directly in the contextvar appears to create a
39
45
  # memory leak, despite the fact that we `reset` when leaving the context
@@ -41,16 +47,16 @@ P = ParamSpec("P")
41
47
  # we already have strong references to the `Call` objects in other places
42
48
  # and b) this is used for performance optimizations where we have fallback
43
49
  # behavior if this weakref is garbage collected. A fix for issue #10952.
44
- current_call: contextvars.ContextVar["weakref.ref[Call]"] = ( # novm
50
+ current_call: contextvars.ContextVar["weakref.ref[Call[Any]]"] = ( # novm
45
51
  contextvars.ContextVar("current_call")
46
52
  )
47
53
 
48
54
  # Create a strong reference to tasks to prevent destruction during execution errors
49
- _ASYNC_TASK_REFS = set()
55
+ _ASYNC_TASK_REFS: set[asyncio.Task[None]] = set()
50
56
 
51
57
 
52
58
  @contextlib.contextmanager
53
- def set_current_call(call: "Call"):
59
+ def set_current_call(call: "Call[Any]") -> Generator[None, Any, None]:
54
60
  token = current_call.set(weakref.ref(call))
55
61
  try:
56
62
  yield
@@ -58,7 +64,7 @@ def set_current_call(call: "Call"):
58
64
  current_call.reset(token)
59
65
 
60
66
 
61
- class Future(concurrent.futures.Future):
67
+ class Future(concurrent.futures.Future[T]):
62
68
  """
63
69
  Extension of `concurrent.futures.Future` with support for cancellation of running
64
70
  futures.
@@ -70,7 +76,7 @@ class Future(concurrent.futures.Future):
70
76
  super().__init__()
71
77
  self._cancel_scope = None
72
78
  self._deadline = None
73
- self._cancel_callbacks = []
79
+ self._cancel_callbacks: list[Callable[[], None]] = []
74
80
  self._name = name
75
81
  self._timed_out = False
76
82
 
@@ -79,7 +85,7 @@ class Future(concurrent.futures.Future):
79
85
  return super().set_running_or_notify_cancel()
80
86
 
81
87
  @contextlib.contextmanager
82
- def enforce_async_deadline(self):
88
+ def enforce_async_deadline(self) -> Generator[AsyncCancelScope]:
83
89
  with cancel_async_at(self._deadline, name=self._name) as self._cancel_scope:
84
90
  for callback in self._cancel_callbacks:
85
91
  self._cancel_scope.add_cancel_callback(callback)
@@ -92,7 +98,7 @@ class Future(concurrent.futures.Future):
92
98
  self._cancel_scope.add_cancel_callback(callback)
93
99
  yield self._cancel_scope
94
100
 
95
- def add_cancel_callback(self, callback: Callable[[], None]):
101
+ def add_cancel_callback(self, callback: Callable[[], Any]) -> None:
96
102
  """
97
103
  Add a callback to be enforced on cancellation.
98
104
 
@@ -113,7 +119,7 @@ class Future(concurrent.futures.Future):
113
119
  with self._condition:
114
120
  return self._timed_out
115
121
 
116
- def cancel(self):
122
+ def cancel(self) -> bool:
117
123
  """Cancel the future if possible.
118
124
 
119
125
  Returns True if the future was cancelled, False otherwise. A future cannot be
@@ -147,7 +153,12 @@ class Future(concurrent.futures.Future):
147
153
  self._invoke_callbacks()
148
154
  return True
149
155
 
150
- def result(self, timeout=None):
156
+ if TYPE_CHECKING:
157
+
158
+ def __get_result(self) -> T:
159
+ ...
160
+
161
+ def result(self, timeout: Optional[float] = None) -> T:
151
162
  """Return the result of the call that the future represents.
152
163
 
153
164
  Args:
@@ -186,7 +197,9 @@ class Future(concurrent.futures.Future):
186
197
  # Break a reference cycle with the exception in self._exception
187
198
  self = None
188
199
 
189
- def _invoke_callbacks(self):
200
+ _done_callbacks: list[Callable[[Self], object]]
201
+
202
+ def _invoke_callbacks(self) -> None:
190
203
  """
191
204
  Invoke our done callbacks and clean up cancel scopes and cancel
192
205
  callbacks. Fixes a memory leak that hung on to Call objects,
@@ -206,7 +219,7 @@ class Future(concurrent.futures.Future):
206
219
 
207
220
  self._cancel_callbacks = []
208
221
  if self._cancel_scope:
209
- self._cancel_scope._callbacks = []
222
+ setattr(self._cancel_scope, "_callbacks", [])
210
223
  self._cancel_scope = None
211
224
 
212
225
 
@@ -216,16 +229,21 @@ class Call(Generic[T]):
216
229
  A deferred function call.
217
230
  """
218
231
 
219
- future: Future
220
- fn: Callable[..., T]
221
- args: Tuple
222
- kwargs: Dict[str, Any]
232
+ future: Future[T]
233
+ fn: "_SyncOrAsyncCallable[..., T]"
234
+ args: tuple[Any, ...]
235
+ kwargs: dict[str, Any]
223
236
  context: contextvars.Context
224
- timeout: float
237
+ timeout: Optional[float]
225
238
  runner: Optional["Portal"] = None
226
239
 
227
240
  @classmethod
228
- def new(cls, __fn: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> "Call[T]":
241
+ def new(
242
+ cls,
243
+ __fn: _SyncOrAsyncCallable[P, T],
244
+ *args: P.args,
245
+ **kwargs: P.kwargs,
246
+ ) -> Self:
229
247
  return cls(
230
248
  future=Future(name=getattr(__fn, "__name__", str(__fn))),
231
249
  fn=__fn,
@@ -255,7 +273,7 @@ class Call(Generic[T]):
255
273
 
256
274
  self.runner = portal
257
275
 
258
- def run(self) -> Optional[Awaitable[T]]:
276
+ def run(self) -> Optional[Awaitable[None]]:
259
277
  """
260
278
  Execute the call and place the result on the future.
261
279
 
@@ -337,7 +355,7 @@ class Call(Generic[T]):
337
355
  def cancel(self) -> bool:
338
356
  return self.future.cancel()
339
357
 
340
- def _run_sync(self):
358
+ def _run_sync(self) -> Optional[Awaitable[T]]:
341
359
  cancel_scope = None
342
360
  try:
343
361
  with set_current_call(self):
@@ -348,8 +366,8 @@ class Call(Generic[T]):
348
366
  # Forget this call's arguments in order to free up any memory
349
367
  # that may be referenced by them; after a call has happened,
350
368
  # there's no need to keep a reference to them
351
- self.args = None
352
- self.kwargs = None
369
+ with contextlib.suppress(AttributeError):
370
+ del self.args, self.kwargs
353
371
 
354
372
  # Return the coroutine for async execution
355
373
  if inspect.isawaitable(result):
@@ -357,8 +375,10 @@ class Call(Generic[T]):
357
375
 
358
376
  except CancelledError:
359
377
  # Report cancellation
378
+ if TYPE_CHECKING:
379
+ assert cancel_scope is not None
360
380
  if cancel_scope.timedout():
361
- self.future._timed_out = True
381
+ setattr(self.future, "_timed_out", True)
362
382
  self.future.cancel()
363
383
  elif cancel_scope.cancelled():
364
384
  self.future.cancel()
@@ -374,8 +394,8 @@ class Call(Generic[T]):
374
394
  self.future.set_result(result) # noqa: F821
375
395
  logger.debug("Finished call %r", self) # noqa: F821
376
396
 
377
- async def _run_async(self, coro):
378
- cancel_scope = None
397
+ async def _run_async(self, coro: Awaitable[T]) -> None:
398
+ cancel_scope = result = None
379
399
  try:
380
400
  with set_current_call(self):
381
401
  with self.future.enforce_async_deadline() as cancel_scope:
@@ -385,12 +405,14 @@ class Call(Generic[T]):
385
405
  # Forget this call's arguments in order to free up any memory
386
406
  # that may be referenced by them; after a call has happened,
387
407
  # there's no need to keep a reference to them
388
- self.args = None
389
- self.kwargs = None
408
+ with contextlib.suppress(AttributeError):
409
+ del self.args, self.kwargs
390
410
  except CancelledError:
391
411
  # Report cancellation
412
+ if TYPE_CHECKING:
413
+ assert cancel_scope is not None
392
414
  if cancel_scope.timedout():
393
- self.future._timed_out = True
415
+ setattr(self.future, "_timed_out", True)
394
416
  self.future.cancel()
395
417
  elif cancel_scope.cancelled():
396
418
  self.future.cancel()
@@ -403,10 +425,11 @@ class Call(Generic[T]):
403
425
  # Prevent reference cycle in `exc`
404
426
  del self
405
427
  else:
428
+ # F821 ignored because Ruff gets confused about the del self above.
406
429
  self.future.set_result(result) # noqa: F821
407
430
  logger.debug("Finished async call %r", self) # noqa: F821
408
431
 
409
- def __call__(self) -> T:
432
+ def __call__(self) -> Union[T, Awaitable[T]]:
410
433
  """
411
434
  Execute the call and return its result.
412
435
 
@@ -417,7 +440,7 @@ class Call(Generic[T]):
417
440
  # Return an awaitable if in an async context
418
441
  if coro is not None:
419
442
 
420
- async def run_and_return_result():
443
+ async def run_and_return_result() -> T:
421
444
  await coro
422
445
  return self.result()
423
446
 
@@ -428,8 +451,9 @@ class Call(Generic[T]):
428
451
  def __repr__(self) -> str:
429
452
  name = getattr(self.fn, "__name__", str(self.fn))
430
453
 
431
- args, kwargs = self.args, self.kwargs
432
- if args is None or kwargs is None:
454
+ try:
455
+ args, kwargs = self.args, self.kwargs
456
+ except AttributeError:
433
457
  call_args = "<dropped>"
434
458
  else:
435
459
  call_args = ", ".join(
@@ -450,7 +474,7 @@ class Portal(abc.ABC):
450
474
  """
451
475
 
452
476
  @abc.abstractmethod
453
- def submit(self, call: "Call") -> "Call":
477
+ def submit(self, call: "Call[T]") -> "Call[T]":
454
478
  """
455
479
  Submit a call to execute elsewhere.
456
480
 
@@ -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