diff --git a/stdlib/@tests/stubtest_allowlists/py314.txt b/stdlib/@tests/stubtest_allowlists/py314.txt index 959353790b49..d5ed8af95f86 100644 --- a/stdlib/@tests/stubtest_allowlists/py314.txt +++ b/stdlib/@tests/stubtest_allowlists/py314.txt @@ -2,7 +2,6 @@ # TODO: New errors in Python 3.14 that need to be fixed or moved below # ==================================================================== -concurrent.interpreters concurrent.futures.InterpreterPoolExecutor.__init__ concurrent.futures.InterpreterPoolExecutor.prepare_context concurrent.futures.interpreter.ExecutionFailed @@ -38,6 +37,15 @@ types.UnionType.__qualname__ # Assigning `__new__` causes `func` not to get recognized. functools.partialmethod.__new__ +# decorator approximated by classmethod +concurrent.interpreters._crossinterp.classonly.* + +# object() sentinels at runtime represented by NewTypes in the stubs +concurrent.interpreters._crossinterp.UNBOUND_ERROR +concurrent.interpreters._crossinterp.UNBOUND_REMOVE +concurrent.interpreters._queues.UNBOUND_ERROR +concurrent.interpreters._queues.UNBOUND_REMOVE + # ==================================== # Pre-existing errors from Python 3.13 # ==================================== diff --git a/stdlib/VERSIONS b/stdlib/VERSIONS index 8baf207ad7b8..6fcf0161790d 100644 --- a/stdlib/VERSIONS +++ b/stdlib/VERSIONS @@ -124,6 +124,7 @@ compileall: 3.0- compression: 3.14- concurrent: 3.2- concurrent.futures.interpreter: 3.14- +concurrent.interpreters: 3.14- configparser: 3.0- contextlib: 3.0- contextvars: 3.7- diff --git a/stdlib/_interpreters.pyi b/stdlib/_interpreters.pyi index ad8eccbe3328..54fc0e39d239 100644 --- a/stdlib/_interpreters.pyi +++ b/stdlib/_interpreters.pyi @@ -1,8 +1,10 @@ import types from collections.abc import Callable -from typing import Any, Final, Literal, SupportsIndex +from typing import Any, Final, Literal, SupportsIndex, TypeVar from typing_extensions import TypeAlias +_R = TypeVar("_R") + _Configs: TypeAlias = Literal["default", "isolated", "legacy", "empty", ""] _SharedDict: TypeAlias = dict[str, Any] # many objects can be shared @@ -21,7 +23,7 @@ def get_current() -> tuple[int, int]: ... def get_main() -> tuple[int, int]: ... def is_running(id: SupportsIndex, *, restrict: bool = False) -> bool: ... def get_config(id: SupportsIndex, *, restrict: bool = False) -> types.SimpleNamespace: ... -def whence(id: SupportsIndex) -> int: ... +def whence(id: SupportsIndex) -> _Whence: ... def exec( id: SupportsIndex, code: str | types.CodeType | Callable[[], object], @@ -31,12 +33,12 @@ def exec( ) -> None | types.SimpleNamespace: ... def call( id: SupportsIndex, - callable: Callable[..., object], + callable: Callable[..., _R], args: tuple[object, ...] | None = None, kwargs: dict[str, object] | None = None, *, restrict: bool = False, -) -> object: ... +) -> tuple[_R, types.SimpleNamespace]: ... def run_string( id: SupportsIndex, script: str | types.CodeType | Callable[[], object], @@ -53,6 +55,7 @@ def decref(id: SupportsIndex, *, restrict: bool = False) -> None: ... def is_shareable(obj: object) -> bool: ... def capture_exception(exc: BaseException | None = None) -> types.SimpleNamespace: ... +_Whence: TypeAlias = Literal[0, 1, 2, 3, 4, 5] WHENCE_UNKNOWN: Final = 0 WHENCE_RUNTIME: Final = 1 WHENCE_LEGACY_CAPI: Final = 2 diff --git a/stdlib/concurrent/interpreters/__init__.pyi b/stdlib/concurrent/interpreters/__init__.pyi new file mode 100644 index 000000000000..3839e6bef09b --- /dev/null +++ b/stdlib/concurrent/interpreters/__init__.pyi @@ -0,0 +1,68 @@ +import sys +import threading +import types +from collections.abc import Callable +from typing import Any, Literal, TypeVar +from typing_extensions import ParamSpec, Self + +if sys.version_info >= (3, 13): # needed to satisfy pyright checks for Python <3.13 + from _interpreters import ( + InterpreterError as InterpreterError, + InterpreterNotFoundError as InterpreterNotFoundError, + NotShareableError as NotShareableError, + _SharedDict, + _Whence, + is_shareable as is_shareable, + ) + + from ._queues import Queue as Queue, QueueEmpty as QueueEmpty, QueueFull as QueueFull, create as create_queue + + __all__ = [ + "ExecutionFailed", + "Interpreter", + "InterpreterError", + "InterpreterNotFoundError", + "NotShareableError", + "Queue", + "QueueEmpty", + "QueueFull", + "create", + "create_queue", + "get_current", + "get_main", + "is_shareable", + "list_all", + ] + + _R = TypeVar("_R") + _P = ParamSpec("_P") + + class ExecutionFailed(InterpreterError): + excinfo: types.SimpleNamespace + + def __init__(self, excinfo: types.SimpleNamespace) -> None: ... + + def create() -> Interpreter: ... + def list_all() -> list[Interpreter]: ... + def get_current() -> Interpreter: ... + def get_main() -> Interpreter: ... + + class Interpreter: + def __new__(cls, id: int, /, _whence: _Whence | None = None, _ownsref: bool | None = None) -> Self: ... + def __reduce__(self) -> tuple[type[Self], int]: ... + def __hash__(self) -> int: ... + def __del__(self) -> None: ... + @property + def id(self) -> int: ... + @property + def whence( + self, + ) -> Literal["unknown", "runtime init", "legacy C-API", "C-API", "cross-interpreter C-API", "_interpreters module"]: ... + def is_running(self) -> bool: ... + def close(self) -> None: ... + def prepare_main( + self, ns: _SharedDict | None = None, /, **kwargs: Any + ) -> None: ... # kwargs has same value restrictions as _SharedDict + def exec(self, code: str | types.CodeType | Callable[[], object], /) -> None: ... + def call(self, callable: Callable[_P, _R], /, *args: _P.args, **kwargs: _P.kwargs) -> _R: ... + def call_in_thread(self, callable: Callable[_P, object], /, *args: _P.args, **kwargs: _P.kwargs) -> threading.Thread: ... diff --git a/stdlib/concurrent/interpreters/_crossinterp.pyi b/stdlib/concurrent/interpreters/_crossinterp.pyi new file mode 100644 index 000000000000..b073aefa7ca7 --- /dev/null +++ b/stdlib/concurrent/interpreters/_crossinterp.pyi @@ -0,0 +1,29 @@ +import sys +from collections.abc import Callable +from typing import Final, NewType +from typing_extensions import Never, Self, TypeAlias + +if sys.version_info >= (3, 13): # needed to satisfy pyright checks for Python <3.13 + from _interpqueues import _UnboundOp + + class ItemInterpreterDestroyed(Exception): ... + # Actually a descriptor that behaves similarly to classmethod but prevents + # access from instances. + classonly = classmethod + + class UnboundItem: + def __new__(cls) -> Never: ... + @classonly + def singleton(cls, kind: str, module: str, name: str = "UNBOUND") -> Self: ... + + # Sentinel types and alias that don't exist at runtime. + _UnboundErrorType = NewType("_UnboundErrorType", object) + _UnboundRemoveType = NewType("_UnboundRemoveType", object) + _AnyUnbound: TypeAlias = _UnboundErrorType | _UnboundRemoveType | UnboundItem + + UNBOUND_ERROR: Final[_UnboundErrorType] + UNBOUND_REMOVE: Final[_UnboundRemoveType] + UNBOUND: Final[UnboundItem] # analogous to UNBOUND_REPLACE in C + + def serialize_unbound(unbound: _AnyUnbound) -> tuple[_UnboundOp]: ... + def resolve_unbound(flag: _UnboundOp, exctype_destroyed: Callable[[str], BaseException]) -> UnboundItem: ... diff --git a/stdlib/concurrent/interpreters/_queues.pyi b/stdlib/concurrent/interpreters/_queues.pyi new file mode 100644 index 000000000000..39a057ee9a7b --- /dev/null +++ b/stdlib/concurrent/interpreters/_queues.pyi @@ -0,0 +1,58 @@ +import queue +import sys +from typing import Final, SupportsIndex +from typing_extensions import Self + +if sys.version_info >= (3, 13): # needed to satisfy pyright checks for Python <3.13 + from _interpqueues import QueueError as QueueError, QueueNotFoundError as QueueNotFoundError + + from . import _crossinterp + from ._crossinterp import UNBOUND_ERROR as UNBOUND_ERROR, UNBOUND_REMOVE as UNBOUND_REMOVE, UnboundItem, _AnyUnbound + + __all__ = [ + "UNBOUND", + "UNBOUND_ERROR", + "UNBOUND_REMOVE", + "ItemInterpreterDestroyed", + "Queue", + "QueueEmpty", + "QueueError", + "QueueFull", + "QueueNotFoundError", + "create", + "list_all", + ] + + class QueueEmpty(QueueError, queue.Empty): ... + class QueueFull(QueueError, queue.Full): ... + class ItemInterpreterDestroyed(QueueError, _crossinterp.ItemInterpreterDestroyed): ... + UNBOUND: Final[UnboundItem] + + def create(maxsize: int = 0, *, unbounditems: _AnyUnbound = ...) -> Queue: ... + def list_all() -> list[Queue]: ... + + class Queue: + def __new__(cls, id: int, /) -> Self: ... + def __del__(self) -> None: ... + def __hash__(self) -> int: ... + def __reduce__(self) -> tuple[type[Self], int]: ... + @property + def id(self) -> int: ... + @property + def unbounditems(self) -> _AnyUnbound: ... + @property + def maxsize(self) -> int: ... + def empty(self) -> bool: ... + def full(self) -> bool: ... + def qsize(self) -> int: ... + def put( + self, + obj: object, + timeout: SupportsIndex | None = None, + *, + unbounditems: _AnyUnbound | None = None, + _delay: float = ..., + ) -> None: ... + def put_nowait(self, obj: object, *, unbounditems: _AnyUnbound | None = None) -> None: ... + def get(self, timeout: SupportsIndex | None = None, *, _delay: float = ...) -> object: ... + def get_nowait(self) -> object: ...