Skip to content

Commit ec9bee2

Browse files
authored
pytest_plugin(refactor[types]): Improve typings (#521)
Replaces private internal types in `libvcs.pytest_plugin`'s public API with stable, named exports so downstream callers can import them for type annotations, and so the Sphinx fixture summary table renders return types as clickable cross-references. - **`Env`**: public alias for the subprocess env mapping type, replacing `_ENV` in all `env:` parameters across Protocol classes and helpers - **`GitCommitEnvVars`**: alias for `dict[str, str]`, the return type of the `git_commit_envvars` fixture - **Rename**: `CreateRepoPytestFixtureFn` → `CreateRepoFn`; the "PytestFixture" infix was redundant and inconsistent with the `Fn`-suffix pattern of `CreateRepoPostInitFn` - **Docs**: `CreateRepoFn`, `CreateRepoPostInitFn`, and `GitCommitEnvVars` documented in `docs/api/pytest-plugin.md` and registered in the Sphinx Python domain; `docs/conf.py` gains a missing-reference handler so `py:data` TypeAliases resolve under `:class:` lookups emitted by `sphinx-autodoc-pytest-fixtures` Breaking change: replace `from libvcs.pytest_plugin import CreateRepoPytestFixtureFn` with `CreateRepoFn`.
2 parents ae3c53f + 4260c59 commit ec9bee2

File tree

10 files changed

+133
-57
lines changed

10 files changed

+133
-57
lines changed

CHANGES

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,27 @@ $ uv add libvcs --prerelease allow
2020
_Notes on the upcoming release will go here._
2121
<!-- END PLACEHOLDER - ADD NEW CHANGELOG ENTRIES BELOW THIS LINE -->
2222

23+
### What's new
24+
25+
#### pytest plugin: Improve typings (#521)
26+
27+
The pytest plugin now exports concise public TypeAliases in place of
28+
the private `_ENV` type that was leaking into public signatures:
29+
30+
- `Env` — public alias for the subprocess environment mapping type;
31+
replaces `_ENV` in all `env:` parameters across Protocol classes and
32+
helper functions
33+
- `GitCommitEnvVars` — alias for `dict[str, str]`, the type returned
34+
by the `git_commit_envvars` fixture
35+
36+
`CreateRepoPytestFixtureFn` is renamed to `CreateRepoFn`. Update any
37+
`TYPE_CHECKING` imports accordingly.
38+
39+
### Documentation
40+
41+
- pytest plugin: `CreateRepoFn` and `CreateRepoPostInitFn` now render
42+
as hyperlinks in the fixture summary table (#521)
43+
2344
## libvcs 0.39.0 (2026-02-07)
2445

2546
### New features

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,11 @@ Writing a tool that interacts with VCS? Use our fixtures to keep your tests clea
134134

135135
```python
136136
import pathlib
137-
from libvcs.pytest_plugin import CreateRepoPytestFixtureFn
137+
from libvcs.pytest_plugin import CreateRepoFn
138138
from libvcs.sync.git import GitSync
139139

140140
def test_my_git_tool(
141-
create_git_remote_repo: CreateRepoPytestFixtureFn,
141+
create_git_remote_repo: CreateRepoFn,
142142
tmp_path: pathlib.Path
143143
):
144144
# Spin up a real, temporary Git server

docs/api/pytest-plugin.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,17 @@ def setup(
4141
pass
4242
```
4343
:::
44+
45+
## Types
46+
47+
```{eval-rst}
48+
.. autodata:: libvcs.pytest_plugin.GitCommitEnvVars
49+
50+
.. autoclass:: libvcs.pytest_plugin.CreateRepoFn
51+
:special-members: __call__
52+
:exclude-members: __init__, _abc_impl, _is_protocol
53+
54+
.. autoclass:: libvcs.pytest_plugin.CreateRepoPostInitFn
55+
:special-members: __call__
56+
:exclude-members: __init__, _abc_impl, _is_protocol
57+
```

docs/conf.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@
44

55
import pathlib
66
import sys
7+
import typing as t
8+
9+
if t.TYPE_CHECKING:
10+
from docutils import nodes # type: ignore[import-untyped]
11+
from sphinx import addnodes
12+
from sphinx.application import Sphinx
13+
from sphinx.domains.python import PythonDomain
14+
from sphinx.environment import BuildEnvironment
715

816
from gp_sphinx.config import make_linkcode_resolve, merge_sphinx_config
917

@@ -54,3 +62,33 @@
5462
rediraffe_redirects="redirects.txt",
5563
)
5664
globals().update(conf)
65+
66+
67+
def _on_missing_class_reference(
68+
app: Sphinx,
69+
env: BuildEnvironment,
70+
node: addnodes.pending_xref,
71+
contnode: nodes.TextElement,
72+
) -> nodes.reference | None:
73+
if node.get("refdomain") != "py" or node.get("reftype") != "class":
74+
return None
75+
from sphinx.util.nodes import make_refnode
76+
77+
py_domain: PythonDomain = env.get_domain("py") # type: ignore[assignment]
78+
target = node.get("reftarget", "")
79+
matches = py_domain.find_obj(env, "", "", target, None, 1) # type: ignore[attr-defined,unused-ignore]
80+
if not matches:
81+
return None
82+
_name, obj_entry = matches[0]
83+
return make_refnode(
84+
app.builder,
85+
node.get("refdoc", ""),
86+
obj_entry.docname,
87+
obj_entry.node_id,
88+
contnode,
89+
)
90+
91+
92+
def setup(app: Sphinx) -> None:
93+
"""Connect missing-reference handler to resolve py:data as :class: links."""
94+
app.connect("missing-reference", _on_missing_class_reference)

src/libvcs/pytest_plugin.py

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def vcs_user(vcs_name: str, vcs_email: str) -> str:
6666

6767

6868
@pytest.fixture(scope="session")
69-
def git_commit_envvars(vcs_name: str, vcs_email: str) -> _ENV:
69+
def git_commit_envvars(vcs_name: str, vcs_email: str) -> GitCommitEnvVars:
7070
"""Return environment variables for `git commit`.
7171
7272
For some reason, `GIT_CONFIG` via {func}`set_gitconfig` doesn't work for `git
@@ -266,6 +266,9 @@ def unique_repo_name(remote_repos_path: pathlib.Path, max_retries: int = 15) ->
266266

267267

268268
InitCmdArgs: t.TypeAlias = list[str] | None
269+
GitCommitEnvVars: t.TypeAlias = dict[str, str]
270+
"""Environment variable mapping passed to ``git commit`` subprocess calls."""
271+
Env: t.TypeAlias = _ENV
269272

270273

271274
class CreateRepoPostInitFn(t.Protocol):
@@ -274,13 +277,13 @@ class CreateRepoPostInitFn(t.Protocol):
274277
def __call__(
275278
self,
276279
remote_repo_path: pathlib.Path,
277-
env: _ENV | None = None,
280+
env: Env | None = None,
278281
) -> None:
279282
"""Ran after creating a repo from pytest fixture."""
280283
...
281284

282285

283-
class CreateRepoPytestFixtureFn(t.Protocol):
286+
class CreateRepoFn(t.Protocol):
284287
"""Typing for VCS pytest fixture callback."""
285288

286289
def __call__(
@@ -301,7 +304,7 @@ def _create_git_remote_repo(
301304
remote_repo_path: pathlib.Path,
302305
remote_repo_post_init: CreateRepoPostInitFn | None = None,
303306
init_cmd_args: InitCmdArgs = DEFAULT_GIT_REMOTE_REPO_CMD_ARGS,
304-
env: _ENV | None = None,
307+
env: Env | None = None,
305308
) -> pathlib.Path:
306309
if init_cmd_args is None:
307310
init_cmd_args = []
@@ -374,7 +377,7 @@ def empty_git_repo(
374377
def create_git_remote_bare_repo(
375378
remote_repos_path: pathlib.Path,
376379
empty_git_bare_repo: pathlib.Path,
377-
) -> CreateRepoPytestFixtureFn:
380+
) -> CreateRepoFn:
378381
"""Return factory to create git remote repo to for clone / push purposes."""
379382

380383
def fn(
@@ -403,7 +406,7 @@ def fn(
403406
def create_git_remote_repo(
404407
remote_repos_path: pathlib.Path,
405408
empty_git_repo: pathlib.Path,
406-
) -> CreateRepoPytestFixtureFn:
409+
) -> CreateRepoFn:
407410
"""Return factory to create git remote repo to for clone / push purposes."""
408411

409412
def fn(
@@ -434,7 +437,7 @@ def fn(
434437

435438
def git_remote_repo_single_commit_post_init(
436439
remote_repo_path: pathlib.Path,
437-
env: _ENV | None = None,
440+
env: Env | None = None,
438441
) -> None:
439442
"""Post-initialization: Create a test git repo with a single commit."""
440443
testfile_filename = "testfile.test"
@@ -454,9 +457,9 @@ def git_remote_repo_single_commit_post_init(
454457
@pytest.fixture(scope="session")
455458
@skip_if_git_missing
456459
def git_remote_repo(
457-
create_git_remote_repo: CreateRepoPytestFixtureFn,
460+
create_git_remote_repo: CreateRepoFn,
458461
gitconfig: pathlib.Path,
459-
git_commit_envvars: _ENV,
462+
git_commit_envvars: GitCommitEnvVars,
460463
) -> pathlib.Path:
461464
"""Copy the session-scoped Git repository to a temporary directory."""
462465
# TODO: Cache the effect of of this in a session-based repo
@@ -490,7 +493,7 @@ def _create_svn_remote_repo(
490493

491494
def svn_remote_repo_single_commit_post_init(
492495
remote_repo_path: pathlib.Path,
493-
env: _ENV | None = None,
496+
env: Env | None = None,
494497
) -> None:
495498
"""Post-initialization: Create a test SVN repo with a single commit."""
496499
assert remote_repo_path.exists()
@@ -541,7 +544,7 @@ def empty_svn_repo(
541544
def create_svn_remote_repo(
542545
remote_repos_path: pathlib.Path,
543546
empty_svn_repo: pathlib.Path,
544-
) -> CreateRepoPytestFixtureFn:
547+
) -> CreateRepoFn:
545548
"""Pre-made svn repo, bare, used as a file:// remote to checkout and commit to."""
546549

547550
def fn(
@@ -571,7 +574,7 @@ def fn(
571574
@pytest.fixture(scope="session")
572575
@skip_if_svn_missing
573576
def svn_remote_repo(
574-
create_svn_remote_repo: CreateRepoPytestFixtureFn,
577+
create_svn_remote_repo: CreateRepoFn,
575578
) -> pathlib.Path:
576579
"""Pre-made. Local file:// based SVN server."""
577580
return create_svn_remote_repo()
@@ -580,7 +583,7 @@ def svn_remote_repo(
580583
@pytest.fixture(scope="session")
581584
@skip_if_svn_missing
582585
def svn_remote_repo_with_files(
583-
create_svn_remote_repo: CreateRepoPytestFixtureFn,
586+
create_svn_remote_repo: CreateRepoFn,
584587
) -> pathlib.Path:
585588
"""Pre-made. Local file:// based SVN server."""
586589
repo_path = create_svn_remote_repo()
@@ -610,7 +613,7 @@ def _create_hg_remote_repo(
610613

611614
def hg_remote_repo_single_commit_post_init(
612615
remote_repo_path: pathlib.Path,
613-
env: _ENV | None = None,
616+
env: Env | None = None,
614617
) -> None:
615618
"""Post-initialization: Create a test mercurial repo with a single commit."""
616619
testfile_filename = "testfile.test"
@@ -647,7 +650,7 @@ def create_hg_remote_repo(
647650
remote_repos_path: pathlib.Path,
648651
empty_hg_repo: pathlib.Path,
649652
hgconfig: pathlib.Path,
650-
) -> CreateRepoPytestFixtureFn:
653+
) -> CreateRepoFn:
651654
"""Pre-made hg repo, bare, used as a file:// remote to checkout and commit to."""
652655

653656
def fn(
@@ -681,7 +684,7 @@ def fn(
681684
@skip_if_hg_missing
682685
def hg_remote_repo(
683686
remote_repos_path: pathlib.Path,
684-
create_hg_remote_repo: CreateRepoPytestFixtureFn,
687+
create_hg_remote_repo: CreateRepoFn,
685688
hgconfig: pathlib.Path,
686689
) -> pathlib.Path:
687690
"""Pre-made, file-based repo for push and pull."""
@@ -787,11 +790,11 @@ def add_doctest_fixtures(
787790
doctest_namespace: dict[str, t.Any],
788791
tmp_path: pathlib.Path,
789792
set_home: pathlib.Path,
790-
git_commit_envvars: _ENV,
793+
git_commit_envvars: GitCommitEnvVars,
791794
hgconfig: pathlib.Path,
792-
create_git_remote_repo: CreateRepoPytestFixtureFn,
793-
create_svn_remote_repo: CreateRepoPytestFixtureFn,
794-
create_hg_remote_repo: CreateRepoPytestFixtureFn,
795+
create_git_remote_repo: CreateRepoFn,
796+
create_svn_remote_repo: CreateRepoFn,
797+
create_hg_remote_repo: CreateRepoFn,
795798
git_repo: pathlib.Path,
796799
) -> None:
797800
"""Harness pytest fixtures to pytest's doctest namespace."""

tests/cmd/test_git.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from libvcs.cmd import git
1313

1414
if t.TYPE_CHECKING:
15-
from libvcs.pytest_plugin import CreateRepoPytestFixtureFn
15+
from libvcs.pytest_plugin import CreateRepoFn, GitCommitEnvVars
1616
from libvcs.sync.git import GitSync
1717

1818

@@ -956,7 +956,7 @@ class RemoteAddParamFixture(t.NamedTuple):
956956
)
957957
def test_remote_manager_add_params(
958958
git_repo: GitSync,
959-
create_git_remote_repo: CreateRepoPytestFixtureFn,
959+
create_git_remote_repo: CreateRepoFn,
960960
test_id: str,
961961
fetch: bool | None,
962962
track: str | None,
@@ -2287,7 +2287,7 @@ def test_reflog_entry_delete(git_repo: GitSync) -> None:
22872287
@pytest.fixture
22882288
def submodule_repo(
22892289
tmp_path: pathlib.Path,
2290-
git_commit_envvars: dict[str, str],
2290+
git_commit_envvars: GitCommitEnvVars,
22912291
set_gitconfig: pathlib.Path,
22922292
) -> git.Git:
22932293
"""Create a git repository to use as a submodule source."""

0 commit comments

Comments
 (0)