From 56621731074e2a91a53297c72df3cf91fe35e4ff Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 25 Jul 2026 12:27:49 -0500 Subject: [PATCH 1/4] _internal(docs[SubprocessCommand]): Add Attributes why: Autodoc renders every dataclass field, so the undocumented Popen mirror fields shipped bare in the API reference with no hint of their accepted values, platform scope, or what each default means. what: - Document every SubprocessCommand field in an Attributes section - Note which fields are POSIX-only or Windows-only - Spell out the sentinel defaults (-1, (), None) and the text-mode interaction between text, universal_newlines, encoding, errors --- src/libvcs/_internal/subprocess.py | 85 ++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/src/libvcs/_internal/subprocess.py b/src/libvcs/_internal/subprocess.py index 48fa33c9..228598d7 100644 --- a/src/libvcs/_internal/subprocess.py +++ b/src/libvcs/_internal/subprocess.py @@ -72,6 +72,91 @@ def __init__(self, output: str, *args: object) -> None: class SubprocessCommand(SkipDefaultFieldsReprMixin): """Wraps a :mod:`subprocess` request. Inspect, mutate, control before invocation. + Fields mirror the parameters of :class:`subprocess.Popen`. Each is passed + through as-is when :meth:`Popen`, :meth:`run`, :meth:`check_call`, or + :meth:`check_output` fires, and the defaults match the ones + :mod:`subprocess` uses. + + Attributes + ---------- + args : _CMD + Program and its arguments, as a sequence like ``['echo', 'hi']`` or + as a single string when ``shell`` is set. + bufsize : int + Buffering policy for the pipe file objects: ``-1`` for + :data:`io.DEFAULT_BUFFER_SIZE`, ``0`` for unbuffered, ``1`` for line + buffered in text mode. + executable : StrOrBytesPath | None + Program to execute in place of ``args[0]``, or the shell to use when + ``shell`` is set. ``None`` runs ``args[0]`` itself. + stdin : _FILE + Child's standard input: a file descriptor, a file object, + :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, or ``None`` to + inherit the parent's. + stdout : _FILE + Child's standard output, taking the same values as ``stdin``. + stderr : _FILE + Child's standard error, taking the same values as ``stdin``, plus + :data:`subprocess.STDOUT` to fold it into ``stdout``. + preexec_fn : t.Callable[[], t.Any] | None + POSIX-only callable run in the child between fork and exec, or + ``None`` to run nothing. + close_fds : bool + Close inherited file descriptors above stderr in the child before + exec. + shell : bool + Run ``args`` through the system shell instead of exec'ing it + directly. + cwd : StrOrBytesPath | None + Directory to change into before running, or ``None`` to inherit the + parent's working directory. + env : _ENV | None + Environment for the child, replacing the parent's, or ``None`` to + inherit it. + creationflags : int + Windows-only process creation flags, e.g. + :data:`subprocess.CREATE_NEW_CONSOLE`. ``0`` applies none. + startupinfo : t.Any | None + Windows-only :class:`subprocess.STARTUPINFO` controlling how the + child's window appears, or ``None`` for the defaults. + restore_signals : bool + POSIX-only: reset signals Python set to ``SIG_IGN`` back to + ``SIG_DFL`` in the child before exec. + start_new_session : bool + POSIX-only: run :func:`os.setsid` in the child, detaching it from the + parent's process group and controlling terminal. + pass_fds : t.Any + POSIX-only file descriptors to keep open in the child regardless of + ``close_fds``. The empty ``()`` passes none. + umask : int + POSIX-only umask to apply in the child before exec. ``-1`` leaves the + inherited umask alone. + pipesize : int + Capacity of the pipes opened for ``stdin``, ``stdout``, and + ``stderr``. ``-1`` keeps the operating system default. + user : str | None + POSIX-only user to switch the child to, or ``None`` to stay as the + calling user. + group : str | None + POSIX-only group to switch the child to, or ``None`` to keep the + calling group. + extra_groups : list[str] | None + POSIX-only supplementary groups for the child, or ``None`` to leave + them untouched. + universal_newlines : bool | None + Alias of ``text``, kept for backwards compatibility. ``None`` leaves + the mode to the other text options. + text : t.Literal[True] | None + Open the pipe file objects in text mode. ``None`` leaves them binary + unless ``encoding``, ``errors``, or ``universal_newlines`` selects + text. + encoding : str | None + Codec for the text-mode pipe file objects. Setting it turns text mode + on; ``None`` leaves the choice to the other text options. + errors : str | None + Decoding error handler for the text-mode pipe file objects, e.g. + ``"replace"``. Setting it turns text mode on. + Examples -------- >>> cmd = SubprocessCommand("ls") From ea86957431caeea3f15eea99f2f04aa44ac9fcbe Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 25 Jul 2026 12:27:53 -0500 Subject: [PATCH 2/4] sync(docs): Add Attributes to sync result types why: Autodoc renders every field whether or not it is described, so these shipped to the API reference bare, and the NamedTuple fields shipped as "Alias for field number 0". what: - Document SyncError, SyncResult, and VCSLocation fields - Document GitRemote fetch/push URL fields - Describe GitStatus fields as the "git status --porcelain=2" branch headers they hold, including when each is None --- src/libvcs/sync/base.py | 29 ++++++++++++++++++++++++++++- src/libvcs/sync/git.py | 39 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/src/libvcs/sync/base.py b/src/libvcs/sync/base.py index 6bf94786..eea11517 100644 --- a/src/libvcs/sync/base.py +++ b/src/libvcs/sync/base.py @@ -19,6 +19,16 @@ class SyncError: """An error encountered during a sync step. + Attributes + ---------- + step : str + Name of the sync step that failed, e.g. ``"fetch"`` or ``"checkout"``. + message : str + Human-readable description of what went wrong. + exception : Exception | None + Underlying exception, or ``None`` when the step reported a failure + without raising. + Examples -------- >>> error = SyncError(step="fetch", message="remote not found") @@ -39,6 +49,15 @@ class SyncError: class SyncResult: """Result of a repository synchronization. + Attributes + ---------- + ok : bool + Whether every sync step succeeded. :meth:`SyncResult.add_error` flips + this to ``False``. + errors : list[SyncError] + Errors recorded during the sync, in the order they happened. Empty + while the sync is still clean. + Examples -------- >>> result = SyncResult() @@ -92,7 +111,15 @@ def add_error( class VCSLocation(t.NamedTuple): - """Generic VCS Location (URL and optional revision).""" + """Generic VCS Location (URL and optional revision). + + Attributes + ---------- + url : str + Repository URL, with any revision suffix stripped. + rev : str | None + Revision to check out, or ``None`` when unspecified. + """ url: str rev: str | None diff --git a/src/libvcs/sync/git.py b/src/libvcs/sync/git.py index 2c291a48..71527109 100644 --- a/src/libvcs/sync/git.py +++ b/src/libvcs/sync/git.py @@ -86,7 +86,18 @@ def __str__(self) -> str: @dataclasses.dataclass class GitRemote: - """Structure containing git working copy information.""" + """Structure containing git working copy information. + + Attributes + ---------- + name : str + Remote name as git records it, e.g. ``origin``. + fetch_url : str + URL git fetches from for this remote. + push_url : str + URL git pushes to for this remote. Same as ``fetch_url`` unless a + separate push URL is configured. + """ name: str fetch_url: str @@ -99,7 +110,31 @@ class GitRemote: @dataclasses.dataclass class GitStatus: - """Git status information.""" + """Git status information. + + Fields hold the ``# branch.*`` headers of ``git status -sb + --porcelain=2`` as strings, unconverted. Each is ``None`` when the header + is absent from the output :meth:`GitStatus.from_stdout` parsed. + + Attributes + ---------- + branch_oid : str | None + Commit SHA of ``HEAD``. ``None`` on an unborn branch, where git + reports ``(initial)`` instead of a SHA. + branch_head : str | None + Checked-out branch name, or ``(detached)`` when ``HEAD`` points at a + commit rather than a branch. + branch_upstream : str | None + Upstream branch ``HEAD`` tracks, e.g. ``origin/master``. ``None`` + when the branch has no upstream configured. + branch_ab : str | None + Ahead/behind counts as git prints them, e.g. ``+0 -0``. ``None`` + without an upstream to compare against. + branch_ahead : str | None + Commit count ahead of the upstream, taken from ``branch_ab``. + branch_behind : str | None + Commit count behind the upstream, taken from ``branch_ab``. + """ branch_oid: str | None = None branch_head: str | None = None From 4a165b2ba9604784a0494482701fbc2fd03b59eb Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 25 Jul 2026 12:27:59 -0500 Subject: [PATCH 3/4] url(docs): Add Attributes to Rule and URL parsers why: Autodoc renders every dataclass field, so the parsed URL fields shipped bare in the API reference. Rule's per-field docstrings also sat one field below what they described, so readers matched "Weight: Higher is more likely to win" to is_explicit. what: - Move Rule's field docstrings into an Attributes section, paired with the field each describes, and cover defaults and weight - Document the parsed fields of GitBaseURL, HgBaseURL, SvnBaseURL, including which sentinel each carries when a rule captures nothing - Document region and rev on GitAWSCodeCommitURL, and rev on the pip URL parsers --- src/libvcs/url/base.py | 26 +++++++++++++++----- src/libvcs/url/git.py | 54 ++++++++++++++++++++++++++++++++++++++++-- src/libvcs/url/hg.py | 43 ++++++++++++++++++++++++++++++++- src/libvcs/url/svn.py | 41 +++++++++++++++++++++++++++++++- 4 files changed, 154 insertions(+), 10 deletions(-) diff --git a/src/libvcs/url/base.py b/src/libvcs/url/base.py index c3214013..d1cfa07e 100644 --- a/src/libvcs/url/base.py +++ b/src/libvcs/url/base.py @@ -28,18 +28,32 @@ def is_valid(cls, url: str, is_explicit: bool | None = None) -> bool: @dataclasses.dataclass(repr=False) class Rule(SkipDefaultFieldsReprMixin): - """A Rule represents an eligible pattern mapping to URL.""" + """A Rule represents an eligible pattern mapping to URL. + + Attributes + ---------- + label : str + Computer readable name / ID. Keys the rule inside a + :class:`RuleMap` and is recorded on a matched URL's ``rule``. + description : str + Human readable description of the URL shape the rule covers. + pattern : Pattern[str] + Regex pattern. Its named groups are assigned onto the URL object's + fields of the same name. + defaults : dict[str, str] + Values to apply to fields the pattern left unset, e.g. the + ``hostname`` and ``scheme`` a bare prefix such as ``github:`` implies. + is_explicit : bool + Is the match unambiguous with other VCS systems? e.g. git+ prefix + weight : int + Weight: Higher is more likely to win + """ label: str - """Computer readable name / ID""" description: str - """Human readable description""" pattern: Pattern[str] - """Regex pattern""" defaults: dict[str, str] = dataclasses.field(default_factory=dict) - """Is the match unambiguous with other VCS systems? e.g. git+ prefix""" is_explicit: bool = False - """Weight: Higher is more likely to win""" weight: int = 0 diff --git a/src/libvcs/url/git.py b/src/libvcs/url/git.py index ac2cb038..07ed8e4d 100644 --- a/src/libvcs/url/git.py +++ b/src/libvcs/url/git.py @@ -294,6 +294,32 @@ class GitBaseURL( ): """Git repository location. Parses URLs on initialization. + Attributes + ---------- + url : str + Location as given, kept verbatim. Every other field is filled from it + by the first :class:`~libvcs.url.base.Rule` that matches. + scheme : str | None + Transport scheme, e.g. ``https`` or ``ssh``. ``None`` for scp-style + locations such as ``git@github.com:vcs-python/libvcs.git``, which + carry no scheme. + user : str | None + User in front of the hostname, e.g. ``git``. ``None`` when the URL + omits one; :meth:`to_url` falls back to ``git`` for scp-style output. + hostname : str | None + Server hosting the repository, e.g. ``github.com``. + port : int | None + Port the URL specifies, or ``None`` to use the transport's default. + path : str | None + Server-side path to the repository with the suffix split off, e.g. + ``vcs-python/libvcs``. + suffix : str | None + Trailing ``.git`` split off the path, or ``None`` when the URL has + none. :meth:`to_url` re-appends it. + rule : str | None + :attr:`~libvcs.url.base.Rule.label` of the rule that matched, or + ``None`` when no rule did and the remaining fields stayed unset. + Examples -------- >>> GitBaseURL(url='https://github.com/vcs-python/libvcs.git') @@ -454,7 +480,21 @@ class GitAWSCodeCommitURL( URLProtocol, SkipDefaultFieldsReprMixin, ): - """Supports AWS CodeCommit git URLs.""" + """Supports AWS CodeCommit git URLs. + + Parses the fields of :class:`GitBaseURL` plus the two below. + + Attributes + ---------- + region : str | None + AWS region from a ``codecommit::://`` URL, e.g. + ``us-east-1``. ``None`` for region-less GRC URLs and for the HTTPS + and SSH forms, which carry the region inside the hostname. + rev : str | None + Commit-ish (tag, branch, ref) trailing the URL as ``@rev``, or + ``None`` when the URL names no revision. :meth:`to_url` re-appends + it. + """ # AWS CodeCommit Region region: str | None = None @@ -589,7 +629,17 @@ class GitPipURL( URLProtocol, SkipDefaultFieldsReprMixin, ): - """Supports pip git URLs.""" + """Supports pip git URLs. + + Parses the fields of :class:`GitBaseURL` plus the one below. + + Attributes + ---------- + rev : str | None + Commit-ish (tag, branch, ref) from a pip-style ``@rev``, e.g. + ``v0.10.0``. ``None`` when the URL names no revision. + :meth:`to_url` re-appends it. + """ # commit-ish (rev): tag, branch, ref rev: str | None = None diff --git a/src/libvcs/url/hg.py b/src/libvcs/url/hg.py index d20fd478..3e9b9d54 100644 --- a/src/libvcs/url/hg.py +++ b/src/libvcs/url/hg.py @@ -154,6 +154,38 @@ class HgBaseURL( ): """Mercurial repository location. Parses URLs on initialization. + Attributes + ---------- + url : str + Location as given, kept verbatim. Every other field is filled from it + by the first :class:`~libvcs.url.base.Rule` that matches. + scheme : str | None + Transport scheme, e.g. ``https``, ``ssh``, or ``hg+file``. ``None`` + when the matched rule captured none. + user : str | None + User in front of the hostname, e.g. ``hg``. ``None`` when the URL + omits one; :meth:`to_url` falls back to ``hg`` for scp-style output. + hostname : str + Server hosting the repository, e.g. ``hg.mozilla.org``. Empty when + the matched rule captures no host, as with ``hg+file://`` URLs. + port : int | None + Port the URL specifies, or ``None`` to use the transport's default. + separator : str + Character sitting between the host (and port) and the path, ``/`` + unless the URL used ``:`` or ``,``. :meth:`to_url` re-emits it. + path : str + Server-side path to the repository, e.g. ``mozilla-central/``. Empty + when the URL carries no path. + suffix : str | None + Trailing ``.git``-style decoration split off the path, or ``None`` + when the URL has none. + ref : str | None + Commit-ish (tag, branch, ref, revision) for callers to set; the + bundled rules capture no ref, so parsing leaves it ``None``. + rule : str | None + :attr:`~libvcs.url.base.Rule.label` of the rule that matched, or + ``None`` when no rule did and the remaining fields stayed unset. + Examples -------- >>> HgBaseURL(url='https://hg.mozilla.org/mozilla-central/') @@ -341,7 +373,16 @@ class HgPipURL( URLProtocol, SkipDefaultFieldsReprMixin, ): - """Supports pip hg URLs.""" + """Supports pip hg URLs. + + Parses the fields of :class:`HgBaseURL` plus the one below. + + Attributes + ---------- + rev : str | None + Commit-ish (tag, branch, ref) from a pip-style ``@rev``, e.g. + ``v1.0``. ``None`` when the URL names no revision. + """ # commit-ish (rev): tag, branch, ref rev: str | None = None diff --git a/src/libvcs/url/svn.py b/src/libvcs/url/svn.py index eb28b517..886bb82e 100644 --- a/src/libvcs/url/svn.py +++ b/src/libvcs/url/svn.py @@ -150,6 +150,36 @@ class SvnBaseURL( ): """SVN repository location. Parses URLs on initialization. + Attributes + ---------- + url : str + Location as given, kept verbatim. Every other field is filled from it + by the first :class:`~libvcs.url.base.Rule` that matches. + scheme : str | None + Transport scheme, e.g. ``https``, ``svn+ssh``, or ``svn+file``. + ``None`` for scp-style locations, which carry no scheme. + user : str | None + User in front of the hostname, e.g. ``svn``. ``None`` when the URL + omits one. + hostname : str + Server hosting the repository, e.g. ``svn.debian.org``. Empty when + the matched rule captures no host, as with ``svn+file://`` URLs. + port : int | None + Port the URL specifies, or ``None`` to use the transport's default. + separator : str + Character sitting between the host (and port) and the path, ``/`` + unless the URL used ``:`` or ``,``. :meth:`to_url` re-emits it. + path : str + Server-side path to the repository, e.g. + ``svn/aliothproj/path/in/project``. Empty when the URL carries no + path. + ref : str | None + Commit-ish (tag, branch, revision) for callers to set; the bundled + rules capture no ref, so parsing leaves it ``None``. + rule : str | None + :attr:`~libvcs.url.base.Rule.label` of the rule that matched, or + ``None`` when no rule did and the remaining fields stayed unset. + Examples -------- >>> SvnBaseURL( @@ -279,7 +309,16 @@ class SvnPipURL( URLProtocol, SkipDefaultFieldsReprMixin, ): - """Supports pip svn URLs.""" + """Supports pip svn URLs. + + Parses the fields of :class:`SvnBaseURL` plus the one below. + + Attributes + ---------- + rev : str | None + Commit-ish (tag, branch, revision) from a pip-style ``@rev``, e.g. + ``2019``. ``None`` when the URL names no revision. + """ # commit-ish (rev): tag, branch, ref rev: str | None = None From bcccf113808c563319e08951a7ea9b6fa0cdc659 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 25 Jul 2026 20:13:14 -0500 Subject: [PATCH 4/4] docs(CHANGES) Documented class fields why: The unreleased entry did not record that class fields now carry descriptions where the API reference renders them. what: - Note the described fields under Documentation --- CHANGES | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES b/CHANGES index e68efa95..896425e9 100644 --- a/CHANGES +++ b/CHANGES @@ -20,6 +20,15 @@ $ uv add libvcs --prerelease allow _Notes on the upcoming release will go here._ +### Documentation + +#### Class fields describe themselves in the API reference (#548) + +The URL, command, and sync types — parsed URLs, rules, remotes, status, and +the subprocess wrapper — now say what each field holds. They previously +reached the rendered API reference as "Alias for field number 0" or as a +bare name carrying only its type. + ### Development #### CI actions updated to current majors