diff --git a/CHANGES b/CHANGES index ef3c08ef..4afa2576 100644 --- a/CHANGES +++ b/CHANGES @@ -20,6 +20,10 @@ $ uv add libvcs --prerelease allow _Upcoming changes will be written here._ +### Documentation (#498) + +Fix doctree warnings and broken references + ## libvcs 0.38.0 (2025-11-30) ### New features diff --git a/MIGRATION b/MIGRATION index 99a841ba..219c8472 100644 --- a/MIGRATION +++ b/MIGRATION @@ -24,11 +24,11 @@ _Notes on the upcoming release will be added here_ -#### pytest fixtures: `git_local_clone` renamed to `example_git_repo` (#468) +### pytest fixtures: `git_local_clone` renamed to `example_git_repo` (#468) - pytest: `git_local_clone` renamed to `example_git_repo` -#### Commands: Listing method renamed (#466) +### Commands: Listing method renamed (#466) - `libvcs.cmd.git.GitCmd._list()` -> `libvcs.cmd.git.Git.ls()` - `libvcs.cmd.svn.Svn._list()` -> `libvcs.cmd.svn.Svn.ls()` diff --git a/docs/conf.py b/docs/conf.py index 19b5dc08..06892795 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -110,6 +110,8 @@ # sphinx.ext.autodoc autoclass_content = "both" autodoc_member_order = "bysource" +# Don't show class signature with the class' name. +autodoc_class_signature = "separated" toc_object_entries_show_parents = "hide" autodoc_default_options = { "undoc-members": True, @@ -120,7 +122,9 @@ } # sphinx-autodoc-typehints -autodoc_typehints = "description" # show type hints in doc body instead of signature +# Automatically extract typehints when specified and place them in +# descriptions of the relevant function/method. +autodoc_typehints = "description" simplify_optional_unions = True # sphinx.ext.napoleon @@ -151,6 +155,18 @@ "gp-libs": ("https://gp-libs.git-pull.com/", None), } +# Keep network lookups fast and quiet when inventories are slow or unreachable. +intersphinx_timeout = 1 + +# sphinx-autodoc-typehints +# Suppress warnings for forward references that can't be resolved +# (types in TYPE_CHECKING blocks used for circular import avoidance) +suppress_warnings = [ + "intersphinx", + "intersphinx.inventory", + "sphinx_autodoc_typehints.forward_reference", +] + def linkcode_resolve(domain: str, info: dict[str, str]) -> None | str: """ diff --git a/docs/pytest-plugin.md b/docs/pytest-plugin.md index 22aeb435..0a69035e 100644 --- a/docs/pytest-plugin.md +++ b/docs/pytest-plugin.md @@ -11,10 +11,6 @@ Looking for more flexibility, correctness, or power? Need different defaults? [C [connect with us]: https://github.com/vcs-python/libvcs/discussions ``` -```{module} libvcs.pytest_plugin - -``` - [pytest]: https://docs.pytest.org/ ## Usage @@ -29,7 +25,7 @@ Pytest will automatically detect the plugin, and its fixtures will be available. ## Fixtures -This pytest plugin works by providing {ref}`pytest fixtures `. The plugin's fixtures ensure that a fresh Git, Subversion, or Mercurial repository is available for each test. It utilizes [session-scoped fixtures] to cache initial repositories, improving performance across tests. +This pytest plugin works by providing [pytest fixtures](https://docs.pytest.org/en/stable/how-to/fixtures.html). The plugin's fixtures ensure that a fresh Git, Subversion, or Mercurial repository is available for each test. It utilizes [session-scoped fixtures] to cache initial repositories, improving performance across tests. [session-scoped fixtures]: https://docs.pytest.org/en/8.3.x/how-to/fixtures.html#fixture-scopes diff --git a/docs/url/index.md b/docs/url/index.md index a5edcbe8..e7fdd291 100644 --- a/docs/url/index.md +++ b/docs/url/index.md @@ -243,8 +243,9 @@ in {mod}`urlparse` (undocumented). From there, `GitURL` can be used downstream directly by other projects. -In our case, `libvcs`s' own {ref}`cmd` and {ref}`projects`, as well as a {ref}`vcspull:index` -configuration, will be able to detect and accept various URL patterns. +In our case, `libvcs`s' own {ref}`cmd` and {ref}`projects`, as well as a +[vcspull configuration](https://vcspull.git-pull.com/), will be able to detect and accept various +URL patterns. ### Matchers: Defaults @@ -255,10 +256,6 @@ When a match occurs, its `defaults` will fill in non-matched groups. When registering new matchers, higher `weight`s are checked first. If it's a valid regex grouping, it will be picked. -[^api-unstable]: Provisional API only - - It's not determined if Location will be mutable or if modifications will return a new object. - ## Explore ```{toctree} diff --git a/src/libvcs/_internal/dataclasses.py b/src/libvcs/_internal/dataclasses.py index 19b4a2d5..01526d32 100644 --- a/src/libvcs/_internal/dataclasses.py +++ b/src/libvcs/_internal/dataclasses.py @@ -13,6 +13,8 @@ if t.TYPE_CHECKING: from _typeshed import DataclassInstance +else: + DataclassInstance = object class SkipDefaultFieldsReprMixin: diff --git a/src/libvcs/_internal/run.py b/src/libvcs/_internal/run.py index 71fa99a6..8b586ba8 100644 --- a/src/libvcs/_internal/run.py +++ b/src/libvcs/_internal/run.py @@ -37,7 +37,6 @@ def console_to_str(s: bytes) -> str: if t.TYPE_CHECKING: _LoggerAdapter = logging.LoggerAdapter[logging.Logger] - from typing import TypeAlias else: _LoggerAdapter = logging.LoggerAdapter @@ -96,12 +95,12 @@ def __call__(self, output: str, timestamp: datetime.datetime) -> None: if sys.platform == "win32": - _ENV: TypeAlias = Mapping[str, str] + _ENV: t.TypeAlias = Mapping[str, str] else: - _ENV: TypeAlias = Mapping[bytes, StrPath] | Mapping[str, StrPath] + _ENV: t.TypeAlias = Mapping[bytes, StrPath] | Mapping[str, StrPath] _CMD = StrPath | Sequence[StrPath] -_FILE: TypeAlias = int | t.IO[t.Any] | None +_FILE: t.TypeAlias = int | t.IO[t.Any] | None def run( @@ -158,12 +157,12 @@ def run( subprocess in real time instead of when the process finishes. check_returncode : bool - Indicate whether a `libvcs.exc.CommandError` should be raised if return code is - different from 0. + Indicate whether a ``libvcs.exc.CommandError`` should be raised if return + code is different from 0. callback : ProgressCallbackProtocol callback to return output as a command executes, accepts a function signature - of `(output, timestamp)`. Example usage:: + of ``(output, timestamp)``. Example usage:: def progress_cb(output, timestamp): sys.stdout.write(output) @@ -172,7 +171,7 @@ def progress_cb(output, timestamp): Upcoming changes ---------------- - When minimum python >= 3.10, `pipesize: int = -1` will be added after `umask`. + When minimum python >= 3.10, pipesize: int = -1 will be added after umask. """ proc = subprocess.Popen( args, diff --git a/src/libvcs/_internal/shortcuts.py b/src/libvcs/_internal/shortcuts.py index d6699929..a3e401d3 100644 --- a/src/libvcs/_internal/shortcuts.py +++ b/src/libvcs/_internal/shortcuts.py @@ -10,15 +10,11 @@ import typing as t from libvcs import GitSync, HgSync, SvnSync, exc +from libvcs._internal.run import ProgressCallbackProtocol +from libvcs._internal.types import StrPath, VCSLiteral from libvcs.exc import InvalidVCS from libvcs.url import registry as url_tools -if t.TYPE_CHECKING: - from typing import TypeGuard - - from libvcs._internal.run import ProgressCallbackProtocol - from libvcs._internal.types import StrPath, VCSLiteral - class VCSNoMatchFoundForUrl(exc.LibVCSException): def __init__(self, url: str, *args: object) -> None: @@ -131,7 +127,7 @@ def create_project( assert vcs_matches[0].vcs is not None - def is_vcs(val: t.Any) -> TypeGuard[VCSLiteral]: + def is_vcs(val: t.Any) -> t.TypeGuard[VCSLiteral]: return isinstance(val, str) and val in {"git", "hg", "svn"} if is_vcs(vcs_matches[0].vcs): diff --git a/src/libvcs/_internal/subprocess.py b/src/libvcs/_internal/subprocess.py index 5135ecbf..003de28f 100644 --- a/src/libvcs/_internal/subprocess.py +++ b/src/libvcs/_internal/subprocess.py @@ -51,10 +51,6 @@ from .dataclasses import SkipDefaultFieldsReprMixin -if t.TYPE_CHECKING: - from typing import TypeAlias - - F = t.TypeVar("F", bound=t.Callable[..., t.Any]) @@ -64,92 +60,19 @@ def __init__(self, output: str, *args: object) -> None: if sys.platform == "win32": - _ENV: TypeAlias = Mapping[str, str] + _ENV: t.TypeAlias = Mapping[str, str] else: - _ENV: TypeAlias = Mapping[bytes, StrOrBytesPath] | Mapping[str, StrOrBytesPath] -_FILE: TypeAlias = None | int | t.IO[t.Any] -_TXT: TypeAlias = bytes | str + _ENV: t.TypeAlias = Mapping[bytes, StrOrBytesPath] | Mapping[str, StrOrBytesPath] +_FILE: t.TypeAlias = None | int | t.IO[t.Any] +_TXT: t.TypeAlias = bytes | str #: Command -_CMD: TypeAlias = StrOrBytesPath | Sequence[StrOrBytesPath] +_CMD: t.TypeAlias = StrOrBytesPath | Sequence[StrOrBytesPath] @dataclasses.dataclass(repr=False) class SubprocessCommand(SkipDefaultFieldsReprMixin): """Wraps a :mod:`subprocess` request. Inspect, mutate, control before invocation. - Attributes - ---------- - args : _CMD - A string, or a sequence of program arguments. - - bufsize : int - supplied as the buffering argument to the open() function when creating the - stdin/stdout/stderr pipe file objects - - executable : Optional[StrOrBytesPath] - A replacement program to execute. - - stdin : _FILE - standard output for executed program - - stdout : - standard output for executed program - - stderr : - standard output for executed program - - close_fds : Controls closing or inheriting of file descriptors. - - shell : If true, the command will be executed through the shell. - - cwd : Sets the current directory before the child is executed. - - env : Defines the environment variables for the new process. - - text : - If ``True``, decode stdin, stdout and stderr using the given encoding (if set) - or the system default otherwise. - - universal_newlines : - Alias of text, provided for backwards compatibility. - - startupinfo : - Windows only - - creationflags : - Windows only - - preexec_fn : - (POSIX only) An object to be called in the child process just before the child - is executed. - - restore_signals : - POSIX only - - start_new_session : - POSIX only - - group : - POSIX only - - extra_groups : - POSIX only - - user : - POSIX only - - umask : - POSIX only - - pass_fds : - POSIX only - - encoding : - Text mode encoding to use for file objects stdin, stdout and stderr. - - errors : - Text mode error handling to use for file objects stdin, stdout and stderr. - Examples -------- >>> cmd = SubprocessCommand("ls") diff --git a/src/libvcs/_internal/types.py b/src/libvcs/_internal/types.py index f8ffb870..bb8909f7 100644 --- a/src/libvcs/_internal/types.py +++ b/src/libvcs/_internal/types.py @@ -1,4 +1,4 @@ -"""Internal :term:`type annotations `. +"""Internal type annotations. Notes ----- @@ -12,14 +12,11 @@ import typing as t from os import PathLike -if t.TYPE_CHECKING: - from typing import TypeAlias - -StrPath: TypeAlias = str | PathLike[str] # stable +StrPath: t.TypeAlias = str | PathLike[str] # stable """:class:`os.PathLike` or :class:`str`""" -StrOrBytesPath: TypeAlias = str | bytes | PathLike[str] | PathLike[bytes] -""":class:`os.PathLike`, :class:`str` or :term:`bytes-like object`""" +StrOrBytesPath: t.TypeAlias = str | bytes | PathLike[str] | PathLike[bytes] +""":class:`os.PathLike`, :class:`str` or bytes-like object""" VCSLiteral = t.Literal["git", "svn", "hg"] diff --git a/src/libvcs/cmd/hg.py b/src/libvcs/cmd/hg.py index bae90fc6..4e0f7c89 100644 --- a/src/libvcs/cmd/hg.py +++ b/src/libvcs/cmd/hg.py @@ -308,7 +308,7 @@ def pull( ) -> str: """Update working directory. - Wraps `hg update `_. + Wraps `hg pull `_. Examples -------- diff --git a/src/libvcs/pytest_plugin.py b/src/libvcs/pytest_plugin.py index 3ade539c..cbaeeb51 100644 --- a/src/libvcs/pytest_plugin.py +++ b/src/libvcs/pytest_plugin.py @@ -13,16 +13,11 @@ import pytest from libvcs import exc -from libvcs._internal.run import run +from libvcs._internal.run import _ENV, run from libvcs.sync.git import GitRemote, GitSync from libvcs.sync.hg import HgSync from libvcs.sync.svn import SvnSync -if t.TYPE_CHECKING: - from typing import TypeAlias - - from libvcs._internal.run import _ENV - class MaxUniqueRepoAttemptsExceeded(exc.LibVCSException): """Raised when exceeded threshold of attempts to find a unique repo destination.""" @@ -270,7 +265,7 @@ def unique_repo_name(remote_repos_path: pathlib.Path, max_retries: int = 15) -> return remote_repo_name -InitCmdArgs: TypeAlias = list[str] | None +InitCmdArgs: t.TypeAlias = list[str] | None class CreateRepoPostInitFn(t.Protocol): diff --git a/src/libvcs/sync/base.py b/src/libvcs/sync/base.py index 73991822..c026ad5d 100644 --- a/src/libvcs/sync/base.py +++ b/src/libvcs/sync/base.py @@ -9,9 +9,7 @@ from urllib import parse as urlparse from libvcs._internal.run import _CMD, CmdLoggingAdapter, ProgressCallbackProtocol, run - -if t.TYPE_CHECKING: - from libvcs._internal.types import StrPath +from libvcs._internal.types import StrPath logger = logging.getLogger(__name__) diff --git a/src/libvcs/sync/git.py b/src/libvcs/sync/git.py index 3ad33ffe..4916c6bf 100644 --- a/src/libvcs/sync/git.py +++ b/src/libvcs/sync/git.py @@ -25,6 +25,7 @@ from urllib import parse as urlparse from libvcs import exc +from libvcs._internal.types import StrPath from libvcs.cmd.git import Git from libvcs.sync.base import ( BaseSync, @@ -32,9 +33,6 @@ convert_pip_url as base_convert_pip_url, ) -if t.TYPE_CHECKING: - from libvcs._internal.types import StrPath - logger = logging.getLogger(__name__) diff --git a/src/libvcs/sync/hg.py b/src/libvcs/sync/hg.py index 27587bd2..0085fb48 100644 --- a/src/libvcs/sync/hg.py +++ b/src/libvcs/sync/hg.py @@ -15,13 +15,11 @@ import pathlib import typing as t +from libvcs._internal.types import StrPath from libvcs.cmd.hg import Hg from .base import BaseSync -if t.TYPE_CHECKING: - from libvcs._internal.types import StrPath - logger = logging.getLogger(__name__) diff --git a/src/libvcs/sync/svn.py b/src/libvcs/sync/svn.py index f380bce2..12c39371 100644 --- a/src/libvcs/sync/svn.py +++ b/src/libvcs/sync/svn.py @@ -21,13 +21,11 @@ import re import typing as t +from libvcs._internal.types import StrPath from libvcs.cmd.svn import Svn from .base import BaseSync -if t.TYPE_CHECKING: - from libvcs._internal.types import StrPath - logger = logging.getLogger(__name__) diff --git a/src/libvcs/url/base.py b/src/libvcs/url/base.py index 7d4508eb..e3a68c0b 100644 --- a/src/libvcs/url/base.py +++ b/src/libvcs/url/base.py @@ -4,14 +4,12 @@ import dataclasses import typing as t +from _collections_abc import dict_values +from collections.abc import Iterator +from re import Pattern from libvcs._internal.dataclasses import SkipDefaultFieldsReprMixin -if t.TYPE_CHECKING: - from _collections_abc import dict_values - from collections.abc import Iterator - from re import Pattern - class URLProtocol(t.Protocol): """Common interface for VCS URL Parsers.""" diff --git a/src/libvcs/url/git.py b/src/libvcs/url/git.py index 89a9efea..ac2cb038 100644 --- a/src/libvcs/url/git.py +++ b/src/libvcs/url/git.py @@ -322,11 +322,6 @@ class GitBaseURL( - Compatibility checking: :meth:`GitBaseURL.is_valid()` - URLs compatible with ``git(1)``: :meth:`GitBaseURL.to_url()` - - Attributes - ---------- - rule : str - name of the :class:`~libvcs.url.base.Rule` """ url: str diff --git a/src/libvcs/url/hg.py b/src/libvcs/url/hg.py index 51a96525..d65262bc 100644 --- a/src/libvcs/url/hg.py +++ b/src/libvcs/url/hg.py @@ -155,11 +155,6 @@ class HgBaseURL( ): """Mercurial repository location. Parses URLs on initialization. - Attributes - ---------- - rule : str - name of the :class:`~libvcs.url.base.Rule` - Examples -------- >>> HgBaseURL(url='https://hg.mozilla.org/mozilla-central/') diff --git a/src/libvcs/url/registry.py b/src/libvcs/url/registry.py index a9910602..0438fcc3 100644 --- a/src/libvcs/url/registry.py +++ b/src/libvcs/url/registry.py @@ -6,13 +6,10 @@ from libvcs._internal.module_loading import import_string -if t.TYPE_CHECKING: - from typing import TypeAlias +from .base import URLProtocol - from .base import URLProtocol - - ParserLazyMap: TypeAlias = dict[str, type[URLProtocol] | str] - ParserMap: TypeAlias = dict[str, type[URLProtocol]] +ParserLazyMap: t.TypeAlias = dict[str, type[URLProtocol] | str] +ParserMap: t.TypeAlias = dict[str, type[URLProtocol]] DEFAULT_PARSERS: ParserLazyMap = { "git": "libvcs.url.git.GitURL", diff --git a/src/libvcs/url/svn.py b/src/libvcs/url/svn.py index f85b7f21..7e1edbed 100644 --- a/src/libvcs/url/svn.py +++ b/src/libvcs/url/svn.py @@ -174,11 +174,6 @@ class SvnBaseURL( - Compatibility checking: :meth:`SvnBaseURL.is_valid()` - URLs compatible with ``svn(1)``: :meth:`SvnBaseURL.to_url()` - - Attributes - ---------- - rule : str - name of the :class:`~libvcs.url.base.Rule` """ url: str diff --git a/tests/url/test_registry.py b/tests/url/test_registry.py index 0c83d32f..fda8c2a5 100644 --- a/tests/url/test_registry.py +++ b/tests/url/test_registry.py @@ -13,10 +13,9 @@ if t.TYPE_CHECKING: from collections.abc import Callable - from typing import TypeAlias - ParserMatchLazy: TypeAlias = Callable[[str], registry.ParserMatch] - DetectVCSFixtureExpectedMatch: TypeAlias = registry.ParserMatch | ParserMatchLazy + ParserMatchLazy: t.TypeAlias = Callable[[str], registry.ParserMatch] + DetectVCSFixtureExpectedMatch: t.TypeAlias = registry.ParserMatch | ParserMatchLazy class DetectVCSFixture(t.NamedTuple):