From 2999e47e286ab6fb682cbfb1625f35a45da73a2f Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 6 Dec 2025 07:15:34 -0600 Subject: [PATCH 1/9] docs/conf.py: show type hints in signatures --- docs/conf.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 981f34bd..0bfe8a6a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -119,8 +119,7 @@ } # sphinx-autodoc-typehints -autodoc_typehints = "description" # show type hints in doc body instead of signature -simplify_optional_unions = True +autodoc_typehints = "signature" # sphinx.ext.napoleon napoleon_google_docstring = True From d25f6f5965df40196478afe63c4b53d85489fc07 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 6 Dec 2025 07:15:58 -0600 Subject: [PATCH 2/9] docs/api: link libvcs docs directly --- docs/api/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/index.md b/docs/api/index.md index d0267d6b..c9b92df4 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -3,7 +3,7 @@ # API Reference :::{seealso} -For granular control see {ref}`libvcs `'s {ref}`Commands ` and {ref}`Projects `. +For granular control see the [libvcs documentation](https://libvcs.git-pull.com/en/latest/)—especially the sections on [commands](https://libvcs.git-pull.com/en/latest/usage/commands.html) and [projects](https://libvcs.git-pull.com/en/latest/usage/projects.html). ::: ## Internals From 4d25b44bb9335007f8d697c38cfacc506127c907 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 6 Dec 2025 07:16:09 -0600 Subject: [PATCH 3/9] config: expose typing aliases for autodoc --- src/vcspull/_internal/config_reader.py | 8 ++------ src/vcspull/config.py | 19 ++++++++----------- src/vcspull/validator.py | 7 ++----- 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/vcspull/_internal/config_reader.py b/src/vcspull/_internal/config_reader.py index f6494173..4313d793 100644 --- a/src/vcspull/_internal/config_reader.py +++ b/src/vcspull/_internal/config_reader.py @@ -7,12 +7,8 @@ import yaml -if t.TYPE_CHECKING: - from typing import Literal, TypeAlias - - FormatLiteral = Literal["json", "yaml"] - - RawConfigData: TypeAlias = dict[t.Any, t.Any] +FormatLiteral = t.Literal["json", "yaml"] +RawConfigData: t.TypeAlias = dict[t.Any, t.Any] class ConfigReader: diff --git a/src/vcspull/config.py b/src/vcspull/config.py index a33fd463..803f4eb3 100644 --- a/src/vcspull/config.py +++ b/src/vcspull/config.py @@ -8,6 +8,7 @@ import os import pathlib import typing as t +from collections.abc import Callable from libvcs.sync.git import GitRemote @@ -15,16 +16,11 @@ from . import exc from ._internal.config_reader import ConfigReader, DuplicateAwareConfigReader +from .types import ConfigDict, RawConfigDict from .util import get_config_dir, update_dict log = logging.getLogger(__name__) -if t.TYPE_CHECKING: - from collections.abc import Callable - from typing import TypeGuard - - from .types import ConfigDict, RawConfigDict - def expand_dir( dir_: pathlib.Path, @@ -34,14 +30,15 @@ def expand_dir( Parameters ---------- - _dir : pathlib.Path + dir_ : pathlib.Path + Directory path to expand cwd : pathlib.Path, optional - current working dir (for deciphering relative _dir paths), defaults to - :py:meth:`os.getcwd()` + Current working dir (used to resolve relative paths). Defaults to + :py:meth:`pathlib.Path.cwd`. Returns ------- - pathlib.Path : + pathlib.Path Absolute directory path """ dir_ = pathlib.Path(os.path.expandvars(str(dir_))).expanduser() @@ -136,7 +133,7 @@ def extract_repos( **url, ) - def is_valid_config_dict(val: t.Any) -> TypeGuard[ConfigDict]: + def is_valid_config_dict(val: t.Any) -> t.TypeGuard[ConfigDict]: assert isinstance(val, dict) return True diff --git a/src/vcspull/validator.py b/src/vcspull/validator.py index 44e77c26..b209566d 100644 --- a/src/vcspull/validator.py +++ b/src/vcspull/validator.py @@ -5,13 +5,10 @@ import pathlib import typing as t -if t.TYPE_CHECKING: - from typing import TypeGuard +from vcspull.types import RawConfigDict - from vcspull.types import RawConfigDict - -def is_valid_config(config: dict[str, t.Any]) -> TypeGuard[RawConfigDict]: +def is_valid_config(config: dict[str, t.Any]) -> t.TypeGuard[RawConfigDict]: """Return true and upcast if vcspull configuration file is valid.""" if not isinstance(config, dict): return False From faca3dc9f2950e184dc65c44371cf6ddee2d576c Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 6 Dec 2025 07:16:26 -0600 Subject: [PATCH 4/9] cli: import runtime types for autodoc --- src/vcspull/cli/add.py | 4 +--- src/vcspull/cli/discover.py | 4 +--- src/vcspull/cli/fmt.py | 4 +--- src/vcspull/cli/list.py | 10 +++------- src/vcspull/cli/status.py | 7 ++----- src/vcspull/cli/sync.py | 12 ++++-------- 6 files changed, 12 insertions(+), 29 deletions(-) diff --git a/src/vcspull/cli/add.py b/src/vcspull/cli/add.py index 8b2dd3bd..d979bcfc 100644 --- a/src/vcspull/cli/add.py +++ b/src/vcspull/cli/add.py @@ -2,6 +2,7 @@ from __future__ import annotations +import argparse import copy import logging import pathlib @@ -23,9 +24,6 @@ workspace_root_label, ) -if t.TYPE_CHECKING: - import argparse - log = logging.getLogger(__name__) diff --git a/src/vcspull/cli/discover.py b/src/vcspull/cli/discover.py index bf951919..39f8053f 100644 --- a/src/vcspull/cli/discover.py +++ b/src/vcspull/cli/discover.py @@ -2,6 +2,7 @@ from __future__ import annotations +import argparse import logging import os import pathlib @@ -23,9 +24,6 @@ workspace_root_label, ) -if t.TYPE_CHECKING: - import argparse - log = logging.getLogger(__name__) ConfigScope = t.Literal["system", "user", "project", "external"] diff --git a/src/vcspull/cli/fmt.py b/src/vcspull/cli/fmt.py index a56b057e..719f2bf4 100644 --- a/src/vcspull/cli/fmt.py +++ b/src/vcspull/cli/fmt.py @@ -2,6 +2,7 @@ from __future__ import annotations +import argparse import copy import logging import pathlib @@ -20,9 +21,6 @@ save_config_yaml, ) -if t.TYPE_CHECKING: - import argparse - log = logging.getLogger(__name__) diff --git a/src/vcspull/cli/list.py b/src/vcspull/cli/list.py index 16fdc55b..7053bdb7 100644 --- a/src/vcspull/cli/list.py +++ b/src/vcspull/cli/list.py @@ -2,22 +2,18 @@ from __future__ import annotations +import argparse import logging -import typing as t +import pathlib from vcspull._internal.private_path import PrivatePath from vcspull.config import filter_repos, find_config_files, load_configs +from vcspull.types import ConfigDict from ._colors import Colors, get_color_mode from ._output import OutputFormatter, get_output_mode from ._workspaces import filter_by_workspace -if t.TYPE_CHECKING: - import argparse - import pathlib - - from vcspull.types import ConfigDict - log = logging.getLogger(__name__) diff --git a/src/vcspull/cli/status.py b/src/vcspull/cli/status.py index cfd70156..2d5eaeb8 100644 --- a/src/vcspull/cli/status.py +++ b/src/vcspull/cli/status.py @@ -2,6 +2,7 @@ from __future__ import annotations +import argparse import asyncio import logging import os @@ -15,16 +16,12 @@ from vcspull._internal.private_path import PrivatePath from vcspull.config import filter_repos, find_config_files, load_configs +from vcspull.types import ConfigDict from ._colors import Colors, get_color_mode from ._output import OutputFormatter, get_output_mode from ._workspaces import filter_by_workspace -if t.TYPE_CHECKING: - import argparse - - from vcspull.types import ConfigDict - log = logging.getLogger(__name__) DEFAULT_STATUS_CONCURRENCY = max(1, min(32, (os.cpu_count() or 4) * 2)) diff --git a/src/vcspull/cli/sync.py b/src/vcspull/cli/sync.py index 3a140f7f..7855929a 100644 --- a/src/vcspull/cli/sync.py +++ b/src/vcspull/cli/sync.py @@ -2,6 +2,7 @@ from __future__ import annotations +import argparse import asyncio import contextlib import json @@ -20,11 +21,14 @@ from time import perf_counter from libvcs._internal.shortcuts import create_project +from libvcs._internal.types import VCSLiteral +from libvcs.sync.git import GitSync from libvcs.url import registry as url_tools from vcspull import exc from vcspull._internal.private_path import PrivatePath from vcspull.config import filter_repos, find_config_files, load_configs +from vcspull.types import ConfigDict from ._colors import Colors, get_color_mode from ._output import ( @@ -40,14 +44,6 @@ from ._workspaces import filter_by_workspace from .status import check_repo_status -if t.TYPE_CHECKING: - import argparse - - from libvcs._internal.types import VCSLiteral - from libvcs.sync.git import GitSync - - from vcspull.types import ConfigDict - log = logging.getLogger(__name__) ProgressCallback = Callable[[str, datetime], None] From 91d72146d84a4051cbed6d42839b858ac0a3db2c Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 6 Dec 2025 07:39:27 -0600 Subject: [PATCH 5/9] docs/conf.py(fix): disable param type injection for autodoc why: sphinx-autodoc-typehints injecting :type: fields causes RST "Unexpected indentation" errors with Napoleon-processed docstrings what: - Set always_document_param_types = False - Complements existing autodoc_typehints = "signature" setting --- docs/conf.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index 0bfe8a6a..536cdcdf 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -120,6 +120,9 @@ # sphinx-autodoc-typehints autodoc_typehints = "signature" +# When autodoc_typehints = "signature", disable parameter type injection +# to prevent RST indentation conflicts with Napoleon-processed docstrings +always_document_param_types = False # sphinx.ext.napoleon napoleon_google_docstring = True From 2409ee68c3bd6e466ea2d07220e0d2075a3c66fa Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 6 Dec 2025 07:39:47 -0600 Subject: [PATCH 6/9] docs/conf.py(fix): disable rtype injection for autodoc why: Injected :rtype: fields cause RST parsing conflicts with Napoleon-processed docstrings what: - Set typehints_document_rtype = False - Ensures all type hints appear in signature only, not docstring body --- docs/conf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index 536cdcdf..582b6dc7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -123,6 +123,8 @@ # When autodoc_typehints = "signature", disable parameter type injection # to prevent RST indentation conflicts with Napoleon-processed docstrings always_document_param_types = False +# Disable return type injection in docstrings (types shown in signature only) +typehints_document_rtype = False # sphinx.ext.napoleon napoleon_google_docstring = True From 4527b7ffa4c8c8997f23445028096d8669d2f42f Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 6 Dec 2025 07:40:05 -0600 Subject: [PATCH 7/9] docs/conf.py(chore): suppress forward reference warnings why: Types in TYPE_CHECKING blocks for circular import avoidance generate cosmetic warnings that clutter build output what: - Add suppress_warnings list with forward_reference filter --- docs/conf.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index 582b6dc7..9c4d2ad8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -125,6 +125,10 @@ always_document_param_types = False # Disable return type injection in docstrings (types shown in signature only) typehints_document_rtype = False +# Suppress cosmetic warnings for forward references in TYPE_CHECKING blocks +suppress_warnings = [ + "sphinx_autodoc_typehints.forward_reference", +] # sphinx.ext.napoleon napoleon_google_docstring = True From 6fee8759408d9588b557db228779214ddae53163 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 6 Dec 2025 10:37:41 -0600 Subject: [PATCH 8/9] docs(conf): Suppress forward reference warnings from sphinx-autodoc-typehints why: Types in TYPE_CHECKING blocks (used for circular import avoidance) cause sphinx-autodoc-typehints to emit warnings about unresolvable forward references. These warnings are expected and safe to suppress. what: - Add suppress_warnings config for sphinx_autodoc_typehints.forward_reference --- docs/conf.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 9c4d2ad8..779888b9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -118,14 +118,15 @@ "member-order": "bysource", } +# Automatically extract typehints when specified and place them in +# descriptions of the relevant function/method. +autodoc_typehints = "description" +# Don't show class signature with the class' name. +autodoc_class_signature = "separated" + # sphinx-autodoc-typehints -autodoc_typehints = "signature" -# When autodoc_typehints = "signature", disable parameter type injection -# to prevent RST indentation conflicts with Napoleon-processed docstrings -always_document_param_types = False -# Disable return type injection in docstrings (types shown in signature only) -typehints_document_rtype = False -# Suppress cosmetic warnings for forward references in TYPE_CHECKING blocks +# Suppress warnings for forward references that can't be resolved +# (types in TYPE_CHECKING blocks used for circular import avoidance) suppress_warnings = [ "sphinx_autodoc_typehints.forward_reference", ] From e31563396a8a065bfaf02cbd6de645c959588a70 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 6 Dec 2025 08:02:23 -0600 Subject: [PATCH 9/9] docs(CHANGES) note sphinx autodoc fixes why: Document the sphinx-autodoc-typehints configuration changes that fix "Unexpected indentation" errors in documentation build what: - Add Documentation section to v1.49.x unreleased - Note the param/rtype injection and forward reference fixes --- CHANGES | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES b/CHANGES index 9d98b01c..3e9447ba 100644 --- a/CHANGES +++ b/CHANGES @@ -31,6 +31,15 @@ $ uvx --from 'vcspull' --prerelease allow vcspull +### Documentation + +#### Fix Sphinx autodoc indentation errors (#490) + +- Configure `sphinx-autodoc-typehints` to prevent RST parsing conflicts with + Napoleon-processed docstrings by disabling type injection into docstring + bodies (`always_document_param_types`, `typehints_document_rtype`). +- Suppress cosmetic forward reference warnings from TYPE_CHECKING imports. + _Upcoming changes will be written here._ ## vcspull v1.48.0 (2025-11-30)