Skip to content

Commit 327cc5b

Browse files
committed
refactor: Expose Git utilities, move load_git into the loader module
1 parent 8f48193 commit 327cc5b

File tree

6 files changed

+150
-120
lines changed

6 files changed

+150
-120
lines changed

CHANGELOG.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -714,9 +714,9 @@ for the initial code allowing to compare two Griffe trees.
714714

715715
### Breaking changes
716716

717-
- All parameters of the [`load_git`][griffe.git.load_git] function, except `module`, are now keyword-only.
718-
- Parameter `try_relative_path` of the [`load_git`][griffe.git.load_git] function was removed.
719-
- Parameter `commit` was renamed `ref` in the [`load_git`][griffe.git.load_git] function.
717+
- All parameters of the [`load_git`][griffe.loader.load_git] function, except `module`, are now keyword-only.
718+
- Parameter `try_relative_path` of the [`load_git`][griffe.loader.load_git] function was removed.
719+
- Parameter `commit` was renamed `ref` in the [`load_git`][griffe.loader.load_git] function.
720720
- Parameter `commit` was renamed `ref` in the `tmp_worktree` helper, which will probably become private later.
721721
- Parameters `ref` and `repo` switched positions in the `tmp_worktree` helper.
722722
- All parameters of the [`resolve_aliases`][griffe.loader.GriffeLoader.resolve_aliases] method are now keyword-only.

src/griffe/__init__.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@
1515
from griffe.docstrings.sphinx import parse as parse_sphinx
1616
from griffe.enumerations import Parser
1717
from griffe.extensions.base import Extension, load_extensions
18-
from griffe.git import load_git
1918
from griffe.importer import dynamic_import
20-
from griffe.loader import load
19+
from griffe.loader import load, load_git
2120
from griffe.logger import get_logger
2221

2322
__all__: list[str] = [

src/griffe/cli.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030
from griffe.enumerations import ExplanationStyle, Parser
3131
from griffe.exceptions import ExtensionError, GitError
3232
from griffe.extensions.base import load_extensions
33-
from griffe.git import _get_latest_tag, _get_repo_root, load_git
34-
from griffe.loader import GriffeLoader, load
33+
from griffe.git import get_latest_tag, get_repo_root
34+
from griffe.loader import GriffeLoader, load, load_git
3535
from griffe.logger import get_logger
3636
from griffe.stats import _format_stats
3737

@@ -430,12 +430,12 @@ def check(
430430
search_paths.extend(sys.path)
431431

432432
try:
433-
against = against or _get_latest_tag(package)
433+
against = against or get_latest_tag(package)
434434
except GitError as error:
435435
print(f"griffe: error: {error}", file=sys.stderr)
436436
return 2
437437
against_path = against_path or package
438-
repository = _get_repo_root(against_path)
438+
repository = get_repo_root(against_path)
439439

440440
try:
441441
loaded_extensions = load_extensions(extensions or ())

src/griffe/git.py

+60-107
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,108 @@
1-
"""This module contains the code allowing to load modules from specific git commits.
2-
3-
```python
4-
from griffe.git import load_git
5-
6-
# where `repo` is the folder *containing* `.git`
7-
old_api = load_git("my_module", commit="v0.1.0", repo="path/to/repo")
8-
```
9-
"""
1+
"""This module contains Git utilities."""
102

113
from __future__ import annotations
124

135
import os
146
import shutil
157
import subprocess
8+
import warnings
169
from contextlib import contextmanager
1710
from pathlib import Path
1811
from tempfile import TemporaryDirectory
19-
from typing import TYPE_CHECKING, Any, Iterator, Sequence
12+
from typing import Any, Iterator
2013

21-
from griffe import loader
2214
from griffe.exceptions import GitError
2315

24-
if TYPE_CHECKING:
25-
from griffe.collections import LinesCollection, ModulesCollection
26-
from griffe.dataclasses import Object
27-
from griffe.enumerations import Parser
28-
from griffe.extensions.base import Extensions
16+
WORKTREE_PREFIX = "griffe-worktree-"
2917

3018

31-
WORKTREE_PREFIX = "griffe-worktree-"
19+
# TODO: Remove at some point.
20+
def __getattr__(name: str) -> Any:
21+
if name == "load_git":
22+
warnings.warn(
23+
f"Importing {name} from griffe.git is deprecated. Import it from griffe.loader instead.",
24+
DeprecationWarning,
25+
stacklevel=2,
26+
)
27+
28+
from griffe.loader import load_git
29+
30+
return load_git
31+
raise AttributeError
32+
3233

34+
def assert_git_repo(path: str | Path) -> None:
35+
"""Assert that a directory is a Git repository.
3336
34-
def _assert_git_repo(repo: str | Path) -> None:
37+
Parameters:
38+
path: Path to a directory.
39+
40+
Raises:
41+
OSError: When the directory is not a Git repository.
42+
"""
3543
if not shutil.which("git"):
3644
raise RuntimeError("Could not find git executable. Please install git.")
3745

3846
try:
3947
subprocess.run(
40-
["git", "-C", str(repo), "rev-parse", "--is-inside-work-tree"],
48+
["git", "-C", str(path), "rev-parse", "--is-inside-work-tree"],
4149
check=True,
4250
stdout=subprocess.DEVNULL,
4351
stderr=subprocess.DEVNULL,
4452
)
4553
except subprocess.CalledProcessError as err:
46-
raise OSError(f"Not a git repository: {repo}") from err
54+
raise OSError(f"Not a git repository: {path}") from err
4755

4856

49-
def _get_latest_tag(path: str | Path) -> str:
50-
if isinstance(path, str):
51-
path = Path(path)
52-
if not path.is_dir():
53-
path = path.parent
57+
def get_latest_tag(repo: str | Path) -> str:
58+
"""Get latest tag of a Git repository.
59+
60+
Parameters:
61+
repo: The path to Git repository.
62+
63+
Returns:
64+
The latest tag.
65+
"""
66+
if isinstance(repo, str):
67+
repo = Path(repo)
68+
if not repo.is_dir():
69+
repo = repo.parent
5470
process = subprocess.run(
5571
["git", "tag", "-l", "--sort=-committerdate"],
56-
cwd=path,
72+
cwd=repo,
5773
text=True,
5874
stdout=subprocess.PIPE,
5975
stderr=subprocess.STDOUT,
6076
check=False,
6177
)
6278
output = process.stdout.strip()
6379
if process.returncode != 0 or not output:
64-
raise GitError(f"Cannot list Git tags in {path}: {output or 'no tags'}")
80+
raise GitError(f"Cannot list Git tags in {repo}: {output or 'no tags'}")
6581
return output.split("\n", 1)[0]
6682

6783

68-
def _get_repo_root(path: str | Path) -> str:
69-
if isinstance(path, str):
70-
path = Path(path)
71-
if not path.is_dir():
72-
path = path.parent
84+
def get_repo_root(repo: str | Path) -> str:
85+
"""Get the root of a Git repository.
86+
87+
Parameters:
88+
repo: The path to a Git repository.
89+
90+
Returns:
91+
The root of the repository.
92+
"""
93+
if isinstance(repo, str):
94+
repo = Path(repo)
95+
if not repo.is_dir():
96+
repo = repo.parent
7397
output = subprocess.check_output(
7498
["git", "rev-parse", "--show-toplevel"],
75-
cwd=path,
99+
cwd=repo,
76100
)
77101
return output.decode().strip()
78102

79103

80104
@contextmanager
81-
def _tmp_worktree(repo: str | Path = ".", ref: str = "HEAD") -> Iterator[Path]:
105+
def tmp_worktree(repo: str | Path = ".", ref: str = "HEAD") -> Iterator[Path]:
82106
"""Context manager that checks out the given reference in the given repository to a temporary worktree.
83107
84108
Parameters:
@@ -92,7 +116,7 @@ def _tmp_worktree(repo: str | Path = ".", ref: str = "HEAD") -> Iterator[Path]:
92116
OSError: If `repo` is not a valid `.git` repository
93117
RuntimeError: If the `git` executable is unavailable, or if it cannot create a worktree
94118
"""
95-
_assert_git_repo(repo)
119+
assert_git_repo(repo)
96120
repo_name = Path(repo).resolve().name
97121
with TemporaryDirectory(prefix=f"{WORKTREE_PREFIX}{repo_name}-{ref}-") as tmp_dir:
98122
branch = f"griffe_{ref}"
@@ -113,75 +137,4 @@ def _tmp_worktree(repo: str | Path = ".", ref: str = "HEAD") -> Iterator[Path]:
113137
subprocess.run(["git", "-C", repo, "branch", "-D", branch], stdout=subprocess.DEVNULL, check=False)
114138

115139

116-
def load_git(
117-
objspec: str | Path | None = None,
118-
/,
119-
*,
120-
ref: str = "HEAD",
121-
repo: str | Path = ".",
122-
submodules: bool = True,
123-
extensions: Extensions | None = None,
124-
search_paths: Sequence[str | Path] | None = None,
125-
docstring_parser: Parser | None = None,
126-
docstring_options: dict[str, Any] | None = None,
127-
lines_collection: LinesCollection | None = None,
128-
modules_collection: ModulesCollection | None = None,
129-
allow_inspection: bool = True,
130-
find_stubs_package: bool = False,
131-
# TODO: Remove at some point.
132-
module: str | Path | None = None,
133-
) -> Object:
134-
"""Load and return a module from a specific Git reference.
135-
136-
This function will create a temporary
137-
[git worktree](https://git-scm.com/docs/git-worktree) at the requested reference
138-
before loading `module` with [`griffe.load`][griffe.loader.load].
139-
140-
This function requires that the `git` executable is installed.
141-
142-
Parameters:
143-
objspec: The Python path of an object, or file path to a module.
144-
ref: A Git reference such as a commit, tag or branch.
145-
repo: Path to the repository (i.e. the directory *containing* the `.git` directory)
146-
submodules: Whether to recurse on the submodules.
147-
This parameter only makes sense when loading a package (top-level module).
148-
extensions: The extensions to use.
149-
search_paths: The paths to search into (relative to the repository root).
150-
docstring_parser: The docstring parser to use. By default, no parsing is done.
151-
docstring_options: Additional docstring parsing options.
152-
lines_collection: A collection of source code lines.
153-
modules_collection: A collection of modules.
154-
allow_inspection: Whether to allow inspecting modules when visiting them is not possible.
155-
find_stubs_package: Whether to search for stubs-only package.
156-
If both the package and its stubs are found, they'll be merged together.
157-
If only the stubs are found, they'll be used as the package itself.
158-
module: Deprecated. Use `objspec` positional-only parameter instead.
159-
160-
Returns:
161-
A Griffe object.
162-
"""
163-
with _tmp_worktree(repo, ref) as worktree:
164-
search_paths = [worktree / path for path in search_paths or ["."]]
165-
if isinstance(objspec, Path):
166-
objspec = worktree / objspec
167-
# TODO: Remove at some point.
168-
if isinstance(module, Path):
169-
module = worktree / module
170-
return loader.load(
171-
objspec,
172-
submodules=submodules,
173-
try_relative_path=False,
174-
extensions=extensions,
175-
search_paths=search_paths,
176-
docstring_parser=docstring_parser,
177-
docstring_options=docstring_options,
178-
lines_collection=lines_collection,
179-
modules_collection=modules_collection,
180-
allow_inspection=allow_inspection,
181-
find_stubs_package=find_stubs_package,
182-
# TODO: Remove at some point.
183-
module=module,
184-
)
185-
186-
187-
__all__ = ["load_git"]
140+
__all__ = ["assert_git_repo", "get_latest_tag", "get_repo_root", "tmp_worktree"]

src/griffe/loader.py

+81-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import warnings
1717
from contextlib import suppress
1818
from datetime import datetime, timezone
19+
from pathlib import Path
1920
from typing import TYPE_CHECKING, Any, ClassVar, Sequence, cast
2021

2122
from griffe.agents.inspector import inspect
@@ -27,13 +28,12 @@
2728
from griffe.expressions import ExprName
2829
from griffe.extensions.base import Extensions
2930
from griffe.finder import ModuleFinder, NamespacePackage, Package
31+
from griffe.git import tmp_worktree
3032
from griffe.logger import get_logger
3133
from griffe.merger import merge_stubs
3234
from griffe.stats import stats
3335

3436
if TYPE_CHECKING:
35-
from pathlib import Path
36-
3737
from griffe.enumerations import Parser
3838

3939
logger = get_logger(__name__)
@@ -759,4 +759,82 @@ def load(
759759
)
760760

761761

762-
__all__ = ["GriffeLoader", "load"]
762+
def load_git(
763+
objspec: str | Path | None = None,
764+
/,
765+
*,
766+
ref: str = "HEAD",
767+
repo: str | Path = ".",
768+
submodules: bool = True,
769+
extensions: Extensions | None = None,
770+
search_paths: Sequence[str | Path] | None = None,
771+
docstring_parser: Parser | None = None,
772+
docstring_options: dict[str, Any] | None = None,
773+
lines_collection: LinesCollection | None = None,
774+
modules_collection: ModulesCollection | None = None,
775+
allow_inspection: bool = True,
776+
find_stubs_package: bool = False,
777+
# TODO: Remove at some point.
778+
module: str | Path | None = None,
779+
) -> Object:
780+
"""Load and return a module from a specific Git reference.
781+
782+
This function will create a temporary
783+
[git worktree](https://git-scm.com/docs/git-worktree) at the requested reference
784+
before loading `module` with [`griffe.load`][griffe.loader.load].
785+
786+
This function requires that the `git` executable is installed.
787+
788+
Examples:
789+
```python
790+
from griffe.loader import load_git
791+
792+
old_api = load_git("my_module", ref="v0.1.0", repo="path/to/repo")
793+
```
794+
795+
Parameters:
796+
objspec: The Python path of an object, or file path to a module.
797+
ref: A Git reference such as a commit, tag or branch.
798+
repo: Path to the repository (i.e. the directory *containing* the `.git` directory)
799+
submodules: Whether to recurse on the submodules.
800+
This parameter only makes sense when loading a package (top-level module).
801+
extensions: The extensions to use.
802+
search_paths: The paths to search into (relative to the repository root).
803+
docstring_parser: The docstring parser to use. By default, no parsing is done.
804+
docstring_options: Additional docstring parsing options.
805+
lines_collection: A collection of source code lines.
806+
modules_collection: A collection of modules.
807+
allow_inspection: Whether to allow inspecting modules when visiting them is not possible.
808+
find_stubs_package: Whether to search for stubs-only package.
809+
If both the package and its stubs are found, they'll be merged together.
810+
If only the stubs are found, they'll be used as the package itself.
811+
module: Deprecated. Use `objspec` positional-only parameter instead.
812+
813+
Returns:
814+
A Griffe object.
815+
"""
816+
with tmp_worktree(repo, ref) as worktree:
817+
search_paths = [worktree / path for path in search_paths or ["."]]
818+
if isinstance(objspec, Path):
819+
objspec = worktree / objspec
820+
# TODO: Remove at some point.
821+
if isinstance(module, Path):
822+
module = worktree / module
823+
return load(
824+
objspec,
825+
submodules=submodules,
826+
try_relative_path=False,
827+
extensions=extensions,
828+
search_paths=search_paths,
829+
docstring_parser=docstring_parser,
830+
docstring_options=docstring_options,
831+
lines_collection=lines_collection,
832+
modules_collection=modules_collection,
833+
allow_inspection=allow_inspection,
834+
find_stubs_package=find_stubs_package,
835+
# TODO: Remove at some point.
836+
module=module,
837+
)
838+
839+
840+
__all__ = ["GriffeLoader", "load", "load_git"]

tests/test_git.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from griffe.cli import check
1212
from griffe.dataclasses import Module
13-
from griffe.git import load_git
13+
from griffe.loader import load_git
1414
from tests import FIXTURES_DIR
1515

1616
if TYPE_CHECKING:

0 commit comments

Comments
 (0)