From b1865a8ca5bd713992c8663e96ffa6c96ca4a01c Mon Sep 17 00:00:00 2001 From: A5rocks Date: Fri, 21 Mar 2025 05:40:21 +0900 Subject: [PATCH 1/7] Stop raising in `_unix_pipes` --- src/trio/_tests/test_unix_pipes.py | 3 --- src/trio/_unix_pipes.py | 5 ----- src/trio/lowlevel.py | 4 +++- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/trio/_tests/test_unix_pipes.py b/src/trio/_tests/test_unix_pipes.py index e0c2d4e4f2..4d1b08c06e 100644 --- a/src/trio/_tests/test_unix_pipes.py +++ b/src/trio/_tests/test_unix_pipes.py @@ -22,9 +22,6 @@ if posix: from .._unix_pipes import FdStream -else: - with pytest.raises(ImportError): - from .._unix_pipes import FdStream async def make_pipe() -> tuple[FdStream, FdStream]: diff --git a/src/trio/_unix_pipes.py b/src/trio/_unix_pipes.py index a95f761bcc..65a4fa889b 100644 --- a/src/trio/_unix_pipes.py +++ b/src/trio/_unix_pipes.py @@ -15,11 +15,6 @@ assert not TYPE_CHECKING or sys.platform != "win32" -if os.name != "posix": - # We raise an error here rather than gating the import in lowlevel.py - # in order to keep jedi static analysis happy. - raise ImportError - # XX TODO: is this a good number? who knows... it does match the default Linux # pipe capacity though. DEFAULT_RECEIVE_SIZE: FinalType = 65536 diff --git a/src/trio/lowlevel.py b/src/trio/lowlevel.py index bbeab6af17..42641b1e3c 100644 --- a/src/trio/lowlevel.py +++ b/src/trio/lowlevel.py @@ -5,6 +5,7 @@ # imports are renamed with leading underscores to indicate they are not part of the public API +import os as _os import select as _select # static checkers don't understand if importing this as _sys, so it's deleted later @@ -74,7 +75,8 @@ from ._wait_for_object import WaitForSingleObject as WaitForSingleObject else: # Unix symbols - from ._unix_pipes import FdStream as FdStream + if _os.name == "posix": + from ._unix_pipes import FdStream as FdStream # Kqueue-specific symbols if sys.platform != "linux" and (_t.TYPE_CHECKING or not hasattr(_select, "epoll")): From 9b7a15df8c79ad10faa2a8e082c7d1916ba09d17 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Fri, 21 Mar 2025 06:40:20 +0900 Subject: [PATCH 2/7] Successfully build docs on Windows on CPython 3.13 --- src/trio/_core/__init__.py | 6 ++++-- src/trio/_core/_run.py | 8 +++++++- src/trio/_highlevel_socket.py | 13 +++++++++++-- src/trio/_path.py | 15 ++++++++++++++- src/trio/lowlevel.py | 12 +++++++----- 5 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/trio/_core/__init__.py b/src/trio/_core/__init__.py index d21aefb3e2..74430d8b05 100644 --- a/src/trio/_core/__init__.py +++ b/src/trio/_core/__init__.py @@ -73,7 +73,7 @@ from ._unbounded_queue import UnboundedQueue, UnboundedQueueStatistics # Windows imports -if sys.platform == "win32": +if sys.platform == "win32" or "sphinx.ext.autodoc" in sys.modules: from ._run import ( current_iocp, monitor_completion_key, @@ -83,7 +83,9 @@ write_overlapped, ) # Kqueue imports -elif sys.platform != "linux" and sys.platform != "win32": +if ( + sys.platform != "linux" and sys.platform != "win32" +) or "sphinx.ext.autodoc" in sys.modules: from ._run import current_kqueue, monitor_kevent, wait_kevent del sys # It would be better to import sys as _sys, but mypy does not understand it diff --git a/src/trio/_core/_run.py b/src/trio/_core/_run.py index 1552870ece..c86aee39a8 100644 --- a/src/trio/_core/_run.py +++ b/src/trio/_core/_run.py @@ -2960,6 +2960,12 @@ def in_trio_task() -> bool: return hasattr(GLOBAL_RUN_CONTEXT, "task") +# export everything for the documentation +if "sphinx.ext.autodoc" in sys.modules: + from ._generated_io_epoll import * + from ._generated_io_kqueue import * + from ._generated_io_windows import * + if sys.platform == "win32": from ._generated_io_windows import * from ._io_windows import ( @@ -2985,7 +2991,7 @@ def in_trio_task() -> bool: _patchers = sorted({"eventlet", "gevent"}.intersection(sys.modules)) if _patchers: raise NotImplementedError( - "unsupported platform or primitives trio depends on are monkey-patched out by " + "unsupported platform or primitives Trio depends on are monkey-patched out by " + ", ".join(_patchers), ) diff --git a/src/trio/_highlevel_socket.py b/src/trio/_highlevel_socket.py index c04e66e1bf..142ab11e07 100644 --- a/src/trio/_highlevel_socket.py +++ b/src/trio/_highlevel_socket.py @@ -14,10 +14,18 @@ if TYPE_CHECKING: from collections.abc import Generator - from typing_extensions import Buffer - from ._socket import SocketType +import sys + +if sys.version_info >= (3, 12): + # NOTE: this isn't in the `TYPE_CHECKING` since for some reason + # sphinx doesn't autoreload this module for SocketStream + # (hypothesis: it's our module renaming magic) + from collections.abc import Buffer +elif TYPE_CHECKING: + from typing_extensions import Buffer + # XX TODO: this number was picked arbitrarily. We should do experiments to # tune it. (Or make it dynamic -- one idea is to start small and increase it # if we observe single reads filling up the whole buffer, at least within some @@ -152,6 +160,7 @@ def setsockopt(self, level: int, option: int, value: int | Buffer) -> None: ... @overload def setsockopt(self, level: int, option: int, value: None, length: int) -> None: ... + # TODO: rename `length` to `optlen` def setsockopt( self, level: int, diff --git a/src/trio/_path.py b/src/trio/_path.py index 53bd0a700f..59249aad8e 100644 --- a/src/trio/_path.py +++ b/src/trio/_path.py @@ -39,8 +39,18 @@ async def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: update_wrapper(wrapper, wrapped) if wrapped.__doc__: + module = wrapped.__module__ + # these are exported specially from CPython's intersphinx inventory + module = module.replace("pathlib._local", "pathlib") + module = module.replace("pathlib._abc", "pathlib") + + name = wrapped.__qualname__ + name = name.replace( + "PathBase", "Path" + ) # I'm not sure why this is necessary + wrapper.__doc__ = ( - f"Like :meth:`~{wrapped.__module__}.{wrapped.__qualname__}`, but async.\n" + f"Like :meth:`~{module}.{name}`, but async.\n" f"\n" f"{cleandoc(wrapped.__doc__)}\n" ) @@ -245,6 +255,9 @@ def __repr__(self) -> str: full_match = _wrap_method(pathlib.Path.full_match) +Path.relative_to.__doc__ = Path.relative_to.__doc__.replace(" `..` ", " ``..`` ") + + @final class PosixPath(Path, pathlib.PurePosixPath): """An async :class:`pathlib.PosixPath` that executes blocking methods in :meth:`trio.to_thread.run_sync`.""" diff --git a/src/trio/lowlevel.py b/src/trio/lowlevel.py index 42641b1e3c..14411f01ad 100644 --- a/src/trio/lowlevel.py +++ b/src/trio/lowlevel.py @@ -62,7 +62,7 @@ # Uses `from x import y as y` for compatibility with `pyright --verifytypes` (#2625) -if sys.platform == "win32": +if sys.platform == "win32" or "sphinx.ext.autodoc" in sys.modules: # Windows symbols from ._core import ( current_iocp as current_iocp, @@ -73,13 +73,15 @@ write_overlapped as write_overlapped, ) from ._wait_for_object import WaitForSingleObject as WaitForSingleObject -else: + +if sys.platform != "win32" or "sphinx.ext.autodoc" in sys.modules: # Unix symbols - if _os.name == "posix": - from ._unix_pipes import FdStream as FdStream + from ._unix_pipes import FdStream as FdStream # Kqueue-specific symbols - if sys.platform != "linux" and (_t.TYPE_CHECKING or not hasattr(_select, "epoll")): + if ( + sys.platform != "linux" and (_t.TYPE_CHECKING or not hasattr(_select, "epoll")) + ) or "sphinx.ext.autodoc" in sys.modules: from ._core import ( current_kqueue as current_kqueue, monitor_kevent as monitor_kevent, From 5f9d04d87e1c607dc72b912aec39e4fd3698cea8 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Fri, 21 Mar 2025 06:46:35 +0900 Subject: [PATCH 3/7] Be a bit stricter about what is allowed for Unix-seeing-Windows --- src/trio/lowlevel.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/trio/lowlevel.py b/src/trio/lowlevel.py index 14411f01ad..d44dd5d069 100644 --- a/src/trio/lowlevel.py +++ b/src/trio/lowlevel.py @@ -72,7 +72,10 @@ wait_overlapped as wait_overlapped, write_overlapped as write_overlapped, ) - from ._wait_for_object import WaitForSingleObject as WaitForSingleObject + + # don't let documentation import the actual implementation + if sys.platform == "win32": + from ._wait_for_object import WaitForSingleObject as WaitForSingleObject if sys.platform != "win32" or "sphinx.ext.autodoc" in sys.modules: # Unix symbols From 6bb1411ccf5f081b143a1724ce3e537cd5c9c116 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Fri, 21 Mar 2025 06:48:31 +0900 Subject: [PATCH 4/7] Make use of these new capabilities --- docs/source/reference-lowlevel.rst | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/source/reference-lowlevel.rst b/docs/source/reference-lowlevel.rst index 82bd8537d9..7a39180e02 100644 --- a/docs/source/reference-lowlevel.rst +++ b/docs/source/reference-lowlevel.rst @@ -274,18 +274,21 @@ TODO: these are implemented, but are currently more of a sketch than anything real. See `#26 `__. -.. function:: current_kqueue() +.. autofunction:: current_kqueue() -.. function:: wait_kevent(ident, filter, abort_func) +.. autofunction:: wait_kevent(ident, filter, abort_func) :async: -.. function:: monitor_kevent(ident, filter) +.. autofunction:: monitor_kevent(ident, filter) :with: queue Windows-specific API -------------------- +.. note: this is a function and not `autofunction` since it relies on cffi + compiling some things. + .. function:: WaitForSingleObject(handle) :async: @@ -304,20 +307,20 @@ anything real. See `#26 `__ and `#52 `__. -.. function:: register_with_iocp(handle) +.. autofunction:: register_with_iocp(handle) -.. function:: wait_overlapped(handle, lpOverlapped) +.. autofunction:: wait_overlapped(handle, lpOverlapped) :async: -.. function:: write_overlapped(handle, data) +.. autofunction:: write_overlapped(handle, data) :async: -.. function:: readinto_overlapped(handle, data) +.. autofunction:: readinto_overlapped(handle, data) :async: -.. function:: current_iocp() +.. autofunction:: current_iocp() -.. function:: monitor_completion_key() +.. autofunction:: monitor_completion_key() :with: queue From 24f85a316673d16b99337f6cf3e1db3c6d67f175 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Fri, 21 Mar 2025 06:58:59 +0900 Subject: [PATCH 5/7] Appease type checker --- src/trio/_core/__init__.py | 11 +++++++---- src/trio/_path.py | 3 ++- src/trio/lowlevel.py | 13 +++++++------ 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/trio/_core/__init__.py b/src/trio/_core/__init__.py index 74430d8b05..f9d8068f0c 100644 --- a/src/trio/_core/__init__.py +++ b/src/trio/_core/__init__.py @@ -5,6 +5,7 @@ """ import sys +import typing as _t from ._entry_queue import TrioToken from ._exceptions import ( @@ -73,7 +74,9 @@ from ._unbounded_queue import UnboundedQueue, UnboundedQueueStatistics # Windows imports -if sys.platform == "win32" or "sphinx.ext.autodoc" in sys.modules: +if sys.platform == "win32" or ( + not _t.TYPE_CHECKING and "sphinx.ext.autodoc" in sys.modules +): from ._run import ( current_iocp, monitor_completion_key, @@ -83,9 +86,9 @@ write_overlapped, ) # Kqueue imports -if ( - sys.platform != "linux" and sys.platform != "win32" -) or "sphinx.ext.autodoc" in sys.modules: +if (sys.platform != "linux" and sys.platform != "win32") or ( + not _t.TYPE_CHECKING and "sphinx.ext.autodoc" in sys.modules +): from ._run import current_kqueue, monitor_kevent, wait_kevent del sys # It would be better to import sys as _sys, but mypy does not understand it diff --git a/src/trio/_path.py b/src/trio/_path.py index 59249aad8e..63ecccd609 100644 --- a/src/trio/_path.py +++ b/src/trio/_path.py @@ -255,7 +255,8 @@ def __repr__(self) -> str: full_match = _wrap_method(pathlib.Path.full_match) -Path.relative_to.__doc__ = Path.relative_to.__doc__.replace(" `..` ", " ``..`` ") +if Path.relative_to.__doc__: + Path.relative_to.__doc__ = Path.relative_to.__doc__.replace(" `..` ", " ``..`` ") @final diff --git a/src/trio/lowlevel.py b/src/trio/lowlevel.py index d44dd5d069..3ceda052e6 100644 --- a/src/trio/lowlevel.py +++ b/src/trio/lowlevel.py @@ -4,8 +4,6 @@ """ # imports are renamed with leading underscores to indicate they are not part of the public API - -import os as _os import select as _select # static checkers don't understand if importing this as _sys, so it's deleted later @@ -61,8 +59,9 @@ # Uses `from x import y as y` for compatibility with `pyright --verifytypes` (#2625) - -if sys.platform == "win32" or "sphinx.ext.autodoc" in sys.modules: +if sys.platform == "win32" or ( + not _t.TYPE_CHECKING and "sphinx.ext.autodoc" in sys.modules +): # Windows symbols from ._core import ( current_iocp as current_iocp, @@ -77,14 +76,16 @@ if sys.platform == "win32": from ._wait_for_object import WaitForSingleObject as WaitForSingleObject -if sys.platform != "win32" or "sphinx.ext.autodoc" in sys.modules: +if sys.platform != "win32" or ( + not _t.TYPE_CHECKING and "sphinx.ext.autodoc" in sys.modules +): # Unix symbols from ._unix_pipes import FdStream as FdStream # Kqueue-specific symbols if ( sys.platform != "linux" and (_t.TYPE_CHECKING or not hasattr(_select, "epoll")) - ) or "sphinx.ext.autodoc" in sys.modules: + ) or (not _t.TYPE_CHECKING and "sphinx.ext.autodoc" in sys.modules): from ._core import ( current_kqueue as current_kqueue, monitor_kevent as monitor_kevent, From 8e56b45a36ef6aa76011ac6f91adb64f662abd17 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Fri, 21 Mar 2025 07:10:01 +0900 Subject: [PATCH 6/7] Appease codecov --- pyproject.toml | 3 ++- src/trio/lowlevel.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6e6a946845..1041959479 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -330,7 +330,8 @@ exclude_also = [ "@overload", 'class .*\bProtocol\b.*\):', "raise NotImplementedError", - 'TODO: test this line' + 'TODO: test this line', + 'if "sphinx.ext.autodoc" in sys.modules:', ] partial_branches = [ "pragma: no branch", diff --git a/src/trio/lowlevel.py b/src/trio/lowlevel.py index 3ceda052e6..b6621d47ed 100644 --- a/src/trio/lowlevel.py +++ b/src/trio/lowlevel.py @@ -73,7 +73,7 @@ ) # don't let documentation import the actual implementation - if sys.platform == "win32": + if sys.platform == "win32": # pragma: no branch from ._wait_for_object import WaitForSingleObject as WaitForSingleObject if sys.platform != "win32" or ( From 6b92ded85a1dc28dfcf11e5275b698023d9fda67 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Fri, 21 Mar 2025 07:36:06 +0900 Subject: [PATCH 7/7] More codecov appeasement --- src/trio/_path.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/trio/_path.py b/src/trio/_path.py index 63ecccd609..a9b2c2e52a 100644 --- a/src/trio/_path.py +++ b/src/trio/_path.py @@ -255,7 +255,7 @@ def __repr__(self) -> str: full_match = _wrap_method(pathlib.Path.full_match) -if Path.relative_to.__doc__: +if Path.relative_to.__doc__: # pragma: no branch Path.relative_to.__doc__ = Path.relative_to.__doc__.replace(" `..` ", " ``..`` ")