From 511f11b21a5b7d4c9e4569252f20c87d22c7f047 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 3 Jan 2026 06:13:50 -0600 Subject: [PATCH 01/12] internal/subprocess(refactor[typing]): Tighten subprocess param types why: Reduce use of overly broad types for SubprocessCommand parameters. what: - Narrow _FILE to IO[str] | IO[bytes] - Specify preexec_fn returns None - Constrain pass_fds to sequence of ints --- src/libvcs/_internal/subprocess.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libvcs/_internal/subprocess.py b/src/libvcs/_internal/subprocess.py index 3e73f646..b8ccc206 100644 --- a/src/libvcs/_internal/subprocess.py +++ b/src/libvcs/_internal/subprocess.py @@ -62,7 +62,7 @@ def __init__(self, output: str, *args: object) -> None: _ENV: t.TypeAlias = Mapping[str, str] else: _ENV: t.TypeAlias = Mapping[bytes, StrOrBytesPath] | Mapping[str, StrOrBytesPath] -_FILE: t.TypeAlias = None | int | t.IO[t.Any] +_FILE: t.TypeAlias = None | int | t.IO[str] | t.IO[bytes] _TXT: t.TypeAlias = bytes | str #: Command _CMD: t.TypeAlias = StrOrBytesPath | Sequence[StrOrBytesPath] @@ -95,7 +95,7 @@ class SubprocessCommand(SkipDefaultFieldsReprMixin): stdin: _FILE = None stdout: _FILE = None stderr: _FILE = None - preexec_fn: t.Callable[[], t.Any] | None = None + preexec_fn: t.Callable[[], None] | None = None close_fds: bool = True shell: bool = False cwd: StrOrBytesPath | None = None @@ -108,7 +108,7 @@ class SubprocessCommand(SkipDefaultFieldsReprMixin): # POSIX-only restore_signals: bool = True start_new_session: bool = False - pass_fds: t.Any = () + pass_fds: Sequence[int] = () umask: int = -1 pipesize: int = -1 user: str | None = None From e3e43e2cd494b6e219499af198926e4e6c3c8bdf Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 3 Jan 2026 06:15:40 -0600 Subject: [PATCH 02/12] internal/run(refactor[typing]): Tighten run() parameter types why: Reduce overly broad typing on subprocess helper parameters. what: - Narrow _FILE to IO[str] | IO[bytes] - Specify preexec_fn returns None - Constrain pass_fds to sequence of ints --- src/libvcs/_internal/run.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libvcs/_internal/run.py b/src/libvcs/_internal/run.py index e37e5273..21a5fcf3 100644 --- a/src/libvcs/_internal/run.py +++ b/src/libvcs/_internal/run.py @@ -104,7 +104,7 @@ def __call__(self, output: str, timestamp: datetime.datetime) -> None: _ENV: t.TypeAlias = Mapping[bytes, StrOrBytesPath] | Mapping[str, StrOrBytesPath] _CMD: t.TypeAlias = StrOrBytesPath | Sequence[StrOrBytesPath] -_FILE: t.TypeAlias = int | t.IO[t.Any] | None +_FILE: t.TypeAlias = int | t.IO[str] | t.IO[bytes] | None def _normalize_command_args(args: _CMD) -> list[StrOrBytesPath]: @@ -130,7 +130,7 @@ def run( stdin: _FILE | None = None, stdout: _FILE | None = None, stderr: _FILE | None = None, - preexec_fn: t.Callable[[], t.Any] | None = None, + preexec_fn: t.Callable[[], None] | None = None, close_fds: bool = True, shell: bool = False, cwd: StrOrBytesPath | None = None, @@ -139,7 +139,7 @@ def run( creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: t.Any = (), + pass_fds: Sequence[int] = (), *, encoding: str | None = None, errors: str | None = None, From f3291f88d8ac204abf65c4ed44a6a508d8dd2fcd Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 3 Jan 2026 06:21:31 -0600 Subject: [PATCH 03/12] tests/_internal/subprocess(test[typing]): Refine SubprocessCommand test types why: Reduce broad typing in test fixtures without changing behavior. what: - Replace varargs fixture shape with explicit cmd_args - Type expected_result as SubprocessCommand - Add CmdArgs alias for clearer argument intent --- .../subprocess/test_SubprocessCommand.py | 107 ++++++++++-------- 1 file changed, 60 insertions(+), 47 deletions(-) diff --git a/tests/_internal/subprocess/test_SubprocessCommand.py b/tests/_internal/subprocess/test_SubprocessCommand.py index 345d2912..7061febf 100644 --- a/tests/_internal/subprocess/test_SubprocessCommand.py +++ b/tests/_internal/subprocess/test_SubprocessCommand.py @@ -8,12 +8,17 @@ import pytest from libvcs._internal.subprocess import SubprocessCommand +from libvcs._internal.types import StrOrBytesPath if t.TYPE_CHECKING: import pathlib -def idfn(val: t.Any) -> str: +CmdArgs = StrOrBytesPath | t.Sequence[StrOrBytesPath] +Kwargs = dict[str, t.Any] + + +def idfn(val: CmdArgs | Kwargs | SubprocessCommand | None) -> str: """Test ID naming function for SubprocessCommand py.test parametrize.""" if isinstance(val, list): if len(val): @@ -24,15 +29,19 @@ def idfn(val: t.Any) -> str: @pytest.mark.parametrize( - ("args", "kwargs", "expected_result"), + ("cmd_args", "kwargs", "expected_result"), [ - (["ls"], {}, SubprocessCommand("ls")), - ([["ls", "-l"]], {}, SubprocessCommand(["ls", "-l"])), - ([], {"args": ["ls", "-l"]}, SubprocessCommand(["ls", "-l"])), - (["ls -l"], {"shell": True}, SubprocessCommand("ls -l", shell=True)), - ([], {"args": "ls -l", "shell": True}, SubprocessCommand("ls -l", shell=True)), + ("ls", {}, SubprocessCommand("ls")), + (["ls", "-l"], {}, SubprocessCommand(["ls", "-l"])), + (None, {"args": ["ls", "-l"]}, SubprocessCommand(["ls", "-l"])), + ("ls -l", {"shell": True}, SubprocessCommand("ls -l", shell=True)), + ( + None, + {"args": "ls -l", "shell": True}, + SubprocessCommand("ls -l", shell=True), + ), ( - [], + None, {"args": ["ls", "-l"], "shell": True}, SubprocessCommand(["ls", "-l"], shell=True), ), @@ -40,12 +49,16 @@ def idfn(val: t.Any) -> str: ids=idfn, ) def test_init( - args: list[t.Any], - kwargs: dict[str, t.Any], - expected_result: t.Any, + cmd_args: CmdArgs | None, + kwargs: Kwargs, + expected_result: SubprocessCommand, ) -> None: """Test SubprocessCommand via list + kwargs, assert attributes.""" - cmd = SubprocessCommand(*args, **kwargs) + cmd = ( + SubprocessCommand(cmd_args, **kwargs) + if cmd_args is not None + else SubprocessCommand(**kwargs) + ) assert cmd == expected_result # Attributes in cmd should match what's passed in @@ -57,116 +70,116 @@ def test_init( assert proc.returncode == 0 -FIXTURES = [ - [["ls"], {}, SubprocessCommand("ls")], - [[["ls", "-l"]], {}, SubprocessCommand(["ls", "-l"])], +FIXTURES: list[tuple[CmdArgs, Kwargs, SubprocessCommand]] = [ + ("ls", {}, SubprocessCommand("ls")), + (["ls", "-l"], {}, SubprocessCommand(["ls", "-l"])), ] @pytest.mark.parametrize( - ("args", "kwargs", "expected_result"), + ("cmd_args", "kwargs", "expected_result"), FIXTURES, ids=idfn, ) def test_init_and_Popen( - args: list[t.Any], - kwargs: dict[str, t.Any], - expected_result: t.Any, + cmd_args: CmdArgs, + kwargs: Kwargs, + expected_result: SubprocessCommand, ) -> None: """Test SubprocessCommand with Popen.""" - cmd = SubprocessCommand(*args, **kwargs) + cmd = SubprocessCommand(cmd_args, **kwargs) assert cmd == expected_result cmd_proc = cmd.Popen() cmd_proc.communicate() assert cmd_proc.returncode == 0 - proc = subprocess.Popen(*args, **kwargs) + proc = subprocess.Popen(cmd_args, **kwargs) proc.communicate() assert proc.returncode == 0 @pytest.mark.parametrize( - ("args", "kwargs", "expected_result"), + ("cmd_args", "kwargs", "expected_result"), FIXTURES, ids=idfn, ) def test_init_and_Popen_run( - args: list[t.Any], - kwargs: dict[str, t.Any], - expected_result: t.Any, + cmd_args: CmdArgs, + kwargs: Kwargs, + expected_result: SubprocessCommand, ) -> None: """Test SubprocessCommand with run.""" - cmd = SubprocessCommand(*args, **kwargs) + cmd = SubprocessCommand(cmd_args, **kwargs) assert cmd == expected_result cmd_proc = cmd.Popen() cmd_proc.communicate() assert cmd_proc.returncode == 0 - proc = subprocess.run(*args, **kwargs, check=False) + proc = subprocess.run(cmd_args, **kwargs, check=False) assert proc.returncode == 0 @pytest.mark.parametrize( - ("args", "kwargs", "expected_result"), + ("cmd_args", "kwargs", "expected_result"), FIXTURES, ids=idfn, ) def test_init_and_check_call( - args: list[t.Any], - kwargs: dict[str, t.Any], - expected_result: t.Any, + cmd_args: CmdArgs, + kwargs: Kwargs, + expected_result: SubprocessCommand, ) -> None: """Test SubprocessCommand with Popen.check_call.""" - cmd = SubprocessCommand(*args, **kwargs) + cmd = SubprocessCommand(cmd_args, **kwargs) assert cmd == expected_result return_code = cmd.check_call() assert return_code == 0 - proc = subprocess.check_call(*args, **kwargs) + proc = subprocess.check_call(cmd_args, **kwargs) assert proc == return_code @pytest.mark.parametrize( - ("args", "kwargs", "expected_result"), + ("cmd_args", "kwargs", "expected_result"), FIXTURES, ) def test_init_and_check_output( - args: list[t.Any], - kwargs: dict[str, t.Any], - expected_result: t.Any, + cmd_args: CmdArgs, + kwargs: Kwargs, + expected_result: SubprocessCommand, ) -> None: """Test SubprocessCommand with Popen.check_output.""" - cmd = SubprocessCommand(*args, **kwargs) + cmd = SubprocessCommand(cmd_args, **kwargs) assert cmd == expected_result return_output = cmd.check_output() assert isinstance(return_output, bytes) - proc = subprocess.check_output(*args, **kwargs) + proc = subprocess.check_output(cmd_args, **kwargs) assert proc == return_output @pytest.mark.parametrize( - ("args", "kwargs", "run_kwargs"), + ("cmd_args", "kwargs", "run_kwargs"), [ - (["ls"], {}, {}), - ([["ls", "-l"]], {}, {}), - ([["ls", "-al"]], {}, {"stdout": subprocess.DEVNULL}), + ("ls", {}, {}), + (["ls", "-l"], {}, {}), + (["ls", "-al"], {}, {"stdout": subprocess.DEVNULL}), ], ids=idfn, ) def test_run( tmp_path: pathlib.Path, - args: list[t.Any], - kwargs: dict[str, t.Any], - run_kwargs: dict[str, t.Any], + cmd_args: CmdArgs, + kwargs: Kwargs, + run_kwargs: Kwargs, ) -> None: """Test SubprocessCommand.run().""" kwargs["cwd"] = tmp_path - cmd = SubprocessCommand(*args, **kwargs) + cmd = SubprocessCommand(cmd_args, **kwargs) response = cmd.run(**run_kwargs) assert response.returncode == 0 From 5e8162a515e2f473e702d9e76f58046d8ac0a7b6 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 3 Jan 2026 06:22:28 -0600 Subject: [PATCH 04/12] tests/cmd/test_git(test[typing]): Narrow constructor param type why: Keep the test fixture typing aligned with actual callable returns. what: - Replace t.Any return with str | pathlib.Path for path_type --- tests/cmd/test_git.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cmd/test_git.py b/tests/cmd/test_git.py index 9434ed7d..7177a7c2 100644 --- a/tests/cmd/test_git.py +++ b/tests/cmd/test_git.py @@ -23,7 +23,7 @@ @pytest.mark.parametrize("path_type", [str, pathlib.Path]) def test_git_constructor( - path_type: t.Callable[[str | pathlib.Path], t.Any], + path_type: t.Callable[[str | pathlib.Path], str | pathlib.Path], tmp_path: pathlib.Path, ) -> None: """Test Git constructor.""" From 15244d34737b060ceea4835f7cf7381d2df4c761 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 3 Jan 2026 06:24:36 -0600 Subject: [PATCH 05/12] internal/shortcuts(refactor[typing]): Narrow VCS TypeGuard input why: Avoid unbounded Any in local VCS guard. what: - Type is_vcs input as str --- src/libvcs/_internal/shortcuts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libvcs/_internal/shortcuts.py b/src/libvcs/_internal/shortcuts.py index a3e401d3..53507e9a 100644 --- a/src/libvcs/_internal/shortcuts.py +++ b/src/libvcs/_internal/shortcuts.py @@ -127,7 +127,7 @@ def create_project( assert vcs_matches[0].vcs is not None - def is_vcs(val: t.Any) -> t.TypeGuard[VCSLiteral]: + def is_vcs(val: str) -> t.TypeGuard[VCSLiteral]: return isinstance(val, str) and val in {"git", "hg", "svn"} if is_vcs(vcs_matches[0].vcs): From 45a609a136c63e35405bb7255321e224ec551fde Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 3 Jan 2026 06:29:33 -0600 Subject: [PATCH 06/12] cmd/git(refactor[typing]): Type reftag as str why: reftag is passed to git CLI as a ref name or URL. what: - Narrow fetch() and pull() reftag to str | None --- src/libvcs/cmd/git.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libvcs/cmd/git.py b/src/libvcs/cmd/git.py index 1bdb0890..160a8e31 100644 --- a/src/libvcs/cmd/git.py +++ b/src/libvcs/cmd/git.py @@ -429,7 +429,7 @@ def clone( def fetch( self, *, - reftag: t.Any | None = None, + reftag: str | None = None, deepen: str | None = None, depth: str | None = None, upload_pack: str | None = None, @@ -797,7 +797,7 @@ def rebase( def pull( self, *, - reftag: t.Any | None = None, + reftag: str | None = None, repository: str | None = None, deepen: str | None = None, depth: str | None = None, From 1cbe96331a367cde53d9fb595bf6729d765e866f Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 3 Jan 2026 06:31:16 -0600 Subject: [PATCH 07/12] cmd/git(refactor[typing]): Narrow config value types why: Document the supported shapes for git --config values. what: - Add GitConfigValue alias and use it for config parameters - Type the config stringify helper to match --- src/libvcs/cmd/git.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libvcs/cmd/git.py b/src/libvcs/cmd/git.py index 160a8e31..1ea2f8dd 100644 --- a/src/libvcs/cmd/git.py +++ b/src/libvcs/cmd/git.py @@ -17,6 +17,7 @@ from libvcs._internal.types import StrOrBytesPath, StrPath _CMD = StrOrBytesPath | Sequence[StrOrBytesPath] +GitConfigValue: t.TypeAlias = bool | int | float | StrPath class Git: @@ -142,7 +143,7 @@ def run( noglob_pathspecs: bool | None = None, icase_pathspecs: bool | None = None, no_optional_locks: bool | None = None, - config: dict[str, t.Any] | None = None, + config: dict[str, GitConfigValue] | None = None, config_env: str | None = None, # Pass-through to run() log_in_real_time: bool = False, @@ -250,7 +251,7 @@ def run( if config is not None: assert isinstance(config, dict) - def stringify(v: t.Any) -> str: + def stringify(v: GitConfigValue) -> str: if isinstance(v, bool): return "true" if v else "false" if not isinstance(v, str): @@ -325,7 +326,7 @@ def clone( verbose: bool | None = None, quiet: bool | None = None, # Pass-through to run - config: dict[str, t.Any] | None = None, + config: dict[str, GitConfigValue] | None = None, log_in_real_time: bool = False, # Special behavior check_returncode: bool | None = None, From b8b011b81397fa46cc7a9fef90a2220762f51818 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 3 Jan 2026 06:32:13 -0600 Subject: [PATCH 08/12] internal/run(refactor[typing]): Narrow log adapter return type why: process() always returns the input message string. what: - Type CmdLoggingAdapter.process return as tuple[str, ...] --- src/libvcs/_internal/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libvcs/_internal/run.py b/src/libvcs/_internal/run.py index 21a5fcf3..8a928d3b 100644 --- a/src/libvcs/_internal/run.py +++ b/src/libvcs/_internal/run.py @@ -79,7 +79,7 @@ def process( self, msg: str, kwargs: MutableMapping[str, t.Any], - ) -> tuple[t.Any, MutableMapping[str, t.Any]]: + ) -> tuple[str, MutableMapping[str, t.Any]]: """Add additional context information for loggers.""" prefixed_dict = {} prefixed_dict["bin_name"] = self.bin_name From e5012a168509fcaa1cd811de81134a1a7b875001 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 3 Jan 2026 06:43:32 -0600 Subject: [PATCH 09/12] cmd/git(refactor[typing]): Type submodule status data why: Git submodule parsing returns a fixed schema. what: - Add GitSubmoduleData TypedDict - Use it for _ls() return and list typing --- src/libvcs/cmd/git.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/libvcs/cmd/git.py b/src/libvcs/cmd/git.py index 1ea2f8dd..feef3eea 100644 --- a/src/libvcs/cmd/git.py +++ b/src/libvcs/cmd/git.py @@ -20,6 +20,18 @@ GitConfigValue: t.TypeAlias = bool | int | float | StrPath +class GitSubmoduleData(t.TypedDict): + """Structured submodule data returned by _ls().""" + + name: str + path: str + sha: str + url: str | None + branch: str | None + status_prefix: str + description: str + + class Git: """Run commands directly on a git repository.""" @@ -3396,7 +3408,7 @@ def _ls( # Pass-through to run() log_in_real_time: bool = False, check_returncode: bool | None = None, - ) -> list[dict[str, t.Any]]: + ) -> list[GitSubmoduleData]: """Parse submodule status output into structured data. Parameters @@ -3408,7 +3420,7 @@ def _ls( Returns ------- - list[dict[str, Any]] + list[GitSubmoduleData] List of parsed submodule data. Examples @@ -3431,7 +3443,7 @@ def _ls( log_in_real_time=log_in_real_time, ) - submodules: list[dict[str, t.Any]] = [] + submodules: list[GitSubmoduleData] = [] for line in result.strip().split("\n"): if not line: From abf071a2141af1ce93521d7a391f2ca35cf8a223 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 3 Jan 2026 06:46:23 -0600 Subject: [PATCH 10/12] internal/shortcuts(refactor[typing]): Tighten kwargs key type why: **kwargs keys are always strings for create_project. what: - Use dict[str, t.Any] for create_project overloads and impl --- src/libvcs/_internal/shortcuts.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libvcs/_internal/shortcuts.py b/src/libvcs/_internal/shortcuts.py index 53507e9a..57d81e56 100644 --- a/src/libvcs/_internal/shortcuts.py +++ b/src/libvcs/_internal/shortcuts.py @@ -38,7 +38,7 @@ def create_project( path: StrPath, vcs: t.Literal["git"], progress_callback: ProgressCallbackProtocol | None = None, - **kwargs: dict[t.Any, t.Any], + **kwargs: dict[str, t.Any], ) -> GitSync: ... @@ -49,7 +49,7 @@ def create_project( path: StrPath, vcs: t.Literal["svn"], progress_callback: ProgressCallbackProtocol | None = None, - **kwargs: dict[t.Any, t.Any], + **kwargs: dict[str, t.Any], ) -> SvnSync: ... @@ -60,7 +60,7 @@ def create_project( path: StrPath, vcs: t.Literal["hg"], progress_callback: ProgressCallbackProtocol | None = ..., - **kwargs: dict[t.Any, t.Any], + **kwargs: dict[str, t.Any], ) -> HgSync: ... @@ -71,7 +71,7 @@ def create_project( path: StrPath, vcs: None = None, progress_callback: ProgressCallbackProtocol | None = None, - **kwargs: dict[t.Any, t.Any], + **kwargs: dict[str, t.Any], ) -> GitSync | HgSync | SvnSync: ... @@ -81,7 +81,7 @@ def create_project( path: StrPath, vcs: VCSLiteral | None = None, progress_callback: ProgressCallbackProtocol | None = None, - **kwargs: dict[t.Any, t.Any], + **kwargs: dict[str, t.Any], ) -> GitSync | HgSync | SvnSync: r"""Return an object representation of a VCS repository. From 0cd6a734e70423155ef007e9363456953df4a541 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 3 Jan 2026 07:30:43 -0600 Subject: [PATCH 11/12] internal/subprocess(refactor[typing]): Align pass_fds with typeshed why: Use Collection[int] to match Python's official typeshed, allowing sets and frozensets which subprocess accepts at runtime. what: - Change pass_fds type from Sequence[int] to Collection[int] - Update both subprocess.py and run.py --- src/libvcs/_internal/run.py | 4 ++-- src/libvcs/_internal/subprocess.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libvcs/_internal/run.py b/src/libvcs/_internal/run.py index 8a928d3b..0c23a26a 100644 --- a/src/libvcs/_internal/run.py +++ b/src/libvcs/_internal/run.py @@ -19,7 +19,7 @@ import sys import time import typing as t -from collections.abc import Iterable, Mapping, MutableMapping, Sequence +from collections.abc import Collection, Iterable, Mapping, MutableMapping, Sequence from libvcs import exc from libvcs._internal.types import StrOrBytesPath @@ -139,7 +139,7 @@ def run( creationflags: int = 0, restore_signals: bool = True, start_new_session: bool = False, - pass_fds: Sequence[int] = (), + pass_fds: Collection[int] = (), *, encoding: str | None = None, errors: str | None = None, diff --git a/src/libvcs/_internal/subprocess.py b/src/libvcs/_internal/subprocess.py index b8ccc206..fbf261c5 100644 --- a/src/libvcs/_internal/subprocess.py +++ b/src/libvcs/_internal/subprocess.py @@ -44,7 +44,7 @@ import subprocess import sys import typing as t -from collections.abc import Mapping, Sequence +from collections.abc import Collection, Mapping, Sequence from libvcs._internal.types import StrOrBytesPath @@ -108,7 +108,7 @@ class SubprocessCommand(SkipDefaultFieldsReprMixin): # POSIX-only restore_signals: bool = True start_new_session: bool = False - pass_fds: Sequence[int] = () + pass_fds: Collection[int] = () umask: int = -1 pipesize: int = -1 user: str | None = None From 117088fba71672c8daf126171d620fcd08ff43b3 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 3 Jan 2026 07:31:41 -0600 Subject: [PATCH 12/12] internal/subprocess(refactor[typing]): Align preexec_fn with typeshed why: Use Callable[[], object] to match Python's official typeshed, accepting functions that return values (which subprocess ignores). what: - Change preexec_fn return type from None to object - Update both subprocess.py and run.py --- src/libvcs/_internal/run.py | 2 +- src/libvcs/_internal/subprocess.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libvcs/_internal/run.py b/src/libvcs/_internal/run.py index 0c23a26a..13497129 100644 --- a/src/libvcs/_internal/run.py +++ b/src/libvcs/_internal/run.py @@ -130,7 +130,7 @@ def run( stdin: _FILE | None = None, stdout: _FILE | None = None, stderr: _FILE | None = None, - preexec_fn: t.Callable[[], None] | None = None, + preexec_fn: t.Callable[[], object] | None = None, close_fds: bool = True, shell: bool = False, cwd: StrOrBytesPath | None = None, diff --git a/src/libvcs/_internal/subprocess.py b/src/libvcs/_internal/subprocess.py index fbf261c5..a110f1e7 100644 --- a/src/libvcs/_internal/subprocess.py +++ b/src/libvcs/_internal/subprocess.py @@ -95,7 +95,7 @@ class SubprocessCommand(SkipDefaultFieldsReprMixin): stdin: _FILE = None stdout: _FILE = None stderr: _FILE = None - preexec_fn: t.Callable[[], None] | None = None + preexec_fn: t.Callable[[], object] | None = None close_fds: bool = True shell: bool = False cwd: StrOrBytesPath | None = None