Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement usethis tool pytest #37

Merged
merged 1 commit into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,20 @@ dev-dependencies = [
"pre-commit>=4.0.1",
]

[tool.coverage.run]
source = ["src"]
omit = ["*/pytest-of-*/*"]


[tool.ruff]
src = ["src"]
line-length = 88

[tool.ruff.lint]
select = ["C4", "E4", "E7", "E9", "F", "FURB", "I", "PLE", "PLR", "RUF", "SIM", "UP"]


[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = [
"--import-mode=importlib",
]

[tool.coverage.run]
source = ["src"]
omit = ["*/pytest-of-*/*"]
16 changes: 16 additions & 0 deletions src/usethis/_deptry/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from usethis import console
from usethis._pre_commit.hooks import get_hook_entry
from usethis._tool import PreCommitTool


def add_deptry_root_dir() -> None:
if not PreCommitTool().is_used():
return

entry = get_hook_entry()
if not entry.startswith("uv run --frozen deptry"):
console.print(
"☐ Reconfigure deptry in '.pre-commit-config.yaml' to run on the '/tests' directory.",
style="blue",
)
return
Empty file.
112 changes: 4 additions & 108 deletions src/usethis/_pre_commit/core.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import subprocess
from collections import Counter
from pathlib import Path

import ruamel.yaml
from ruamel.yaml.util import load_yaml_guess_indent

from usethis import console
from usethis._github import GitHubTagError, get_github_latest_tag
from usethis._pre_commit.config import PreCommitRepoConfig

_YAML_CONTENTS_TEMPLATE = """
repos:
Expand All @@ -20,20 +15,13 @@
# Manually bump this version when necessary
_VALIDATEPYPROJECT_VERSION = "v0.21"

_HOOK_ORDER = [
"validate-pyproject",
"ruff-format",
"ruff-check",
"deptry",
]


def ensure_pre_commit_config() -> None:
def add_pre_commit_config() -> None:
if (Path.cwd() / ".pre-commit-config.yaml").exists():
# Early exit; the file already exists
return

console.print("✔ Creating .pre-commit-config.yaml file", style="green")
console.print("✔ Writing '.pre-commit-config.yaml'.", style="green")
try:
pkg_version = get_github_latest_tag("abravalheri", "validate-pyproject")
except GitHubTagError:
Expand All @@ -53,100 +41,8 @@ def remove_pre_commit_config() -> None:
(Path.cwd() / ".pre-commit-config.yaml").unlink()


def add_hook(config: PreCommitRepoConfig) -> None:
path = Path.cwd() / ".pre-commit-config.yaml"

with path.open(mode="r") as f:
content, sequence_ind, offset_ind = load_yaml_guess_indent(f)

yaml = ruamel.yaml.YAML(typ="rt")
yaml.indent(mapping=sequence_ind, sequence=sequence_ind, offset=offset_ind)

(hook_config,) = config.hooks
hook_name = hook_config.id

# Get an ordered list of the hooks already in the file
existing_hooks = get_hook_names(path.parent)

if not existing_hooks:
raise NotImplementedError

# Get the precendents, i.e. hooks occuring before the new hook
try:
hook_idx = _HOOK_ORDER.index(hook_name)
except ValueError:
raise NotImplementedError(f"Hook '{hook_name}' not recognized")
precedents = _HOOK_ORDER[:hook_idx]

# Find the last of the precedents in the existing hooks
existings_precedents = [hook for hook in existing_hooks if hook in precedents]
if existings_precedents:
last_precedent = existings_precedents[-1]
else:
# Use the last existing hook
last_precedent = existing_hooks[-1]

# Insert the new hook after the last precedent repo
# Do this by iterating over the repos and hooks, and inserting the new hook after
# the last precedent
new_repos = []
for repo in content["repos"]:
new_repos.append(repo)
for hook in repo["hooks"]:
if hook["id"] == last_precedent:
new_repos.append(config.model_dump(exclude_none=True))
content["repos"] = new_repos

# Dump the new content
yaml.dump(content, path)


def remove_hook(name: str) -> None:
path = Path.cwd() / ".pre-commit-config.yaml"

with path.open(mode="r") as f:
content, sequence_ind, offset_ind = load_yaml_guess_indent(f)

yaml = ruamel.yaml.YAML(typ="rt")
yaml.indent(mapping=sequence_ind, sequence=sequence_ind, offset=offset_ind)

# search across the repos for any hooks with ID equal to name
for repo in content["repos"]:
for hook in repo["hooks"]:
if hook["id"] == name:
repo["hooks"].remove(hook)

# if repo has no hooks, remove it
if not repo["hooks"]:
content["repos"].remove(repo)

yaml.dump(content, path)


def get_hook_names(path: Path) -> list[str]:
yaml = ruamel.yaml.YAML()
with (path / ".pre-commit-config.yaml").open(mode="r") as f:
content = yaml.load(f)

hook_names = []
for repo in content["repos"]:
for hook in repo["hooks"]:
hook_names.append(hook["id"])

# Need to validate there are no duplciates
for name, count in Counter(hook_names).items():
if count > 1:
raise DuplicatedHookNameError(f"Hook name '{name}' is duplicated")

return hook_names


class DuplicatedHookNameError(ValueError):
"""Raised when a hook name is duplicated in a pre-commit configuration file."""


def install_pre_commit() -> None:
console.print("✔ Installing pre-commit hooks", style="green")
console.print("✔ Ensuring pre-commit hooks are installed.", style="green")
subprocess.run(
["uv", "run", "pre-commit", "install"],
check=True,
Expand All @@ -155,7 +51,7 @@ def install_pre_commit() -> None:


def uninstall_pre_commit() -> None:
console.print("✔ Uninstalling pre-commit hooks", style="green")
console.print("✔ Ensuring pre-commit hooks are uninstalled.", style="green")
subprocess.run(
["uv", "run", "pre-commit", "uninstall"],
check=True,
Expand Down
106 changes: 106 additions & 0 deletions src/usethis/_pre_commit/hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
from collections import Counter
from pathlib import Path

import ruamel.yaml
from ruamel.yaml.util import load_yaml_guess_indent

from usethis._pre_commit.config import PreCommitRepoConfig

_HOOK_ORDER = [
"validate-pyproject",
"ruff-format",
"ruff-check",
"deptry",
]


class DuplicatedHookNameError(ValueError):
"""Raised when a hook name is duplicated in a pre-commit configuration file."""


def add_hook(config: PreCommitRepoConfig) -> None:
path = Path.cwd() / ".pre-commit-config.yaml"

with path.open(mode="r") as f:
content, sequence_ind, offset_ind = load_yaml_guess_indent(f)

yaml = ruamel.yaml.YAML(typ="rt")
yaml.indent(mapping=sequence_ind, sequence=sequence_ind, offset=offset_ind)

(hook_config,) = config.hooks
hook_name = hook_config.id

# Get an ordered list of the hooks already in the file
existing_hooks = get_hook_names(path.parent)

if not existing_hooks:
raise NotImplementedError

# Get the precendents, i.e. hooks occuring before the new hook
try:
hook_idx = _HOOK_ORDER.index(hook_name)
except ValueError:
raise NotImplementedError(f"Hook '{hook_name}' not recognized")
precedents = _HOOK_ORDER[:hook_idx]

# Find the last of the precedents in the existing hooks
existings_precedents = [hook for hook in existing_hooks if hook in precedents]
if existings_precedents:
last_precedent = existings_precedents[-1]
else:
# Use the last existing hook
last_precedent = existing_hooks[-1]

# Insert the new hook after the last precedent repo
# Do this by iterating over the repos and hooks, and inserting the new hook after
# the last precedent
new_repos = []
for repo in content["repos"]:
new_repos.append(repo)
for hook in repo["hooks"]:
if hook["id"] == last_precedent:
new_repos.append(config.model_dump(exclude_none=True))
content["repos"] = new_repos

# Dump the new content
yaml.dump(content, path)


def remove_hook(name: str) -> None:
path = Path.cwd() / ".pre-commit-config.yaml"

with path.open(mode="r") as f:
content, sequence_ind, offset_ind = load_yaml_guess_indent(f)

yaml = ruamel.yaml.YAML(typ="rt")
yaml.indent(mapping=sequence_ind, sequence=sequence_ind, offset=offset_ind)

# search across the repos for any hooks with ID equal to name
for repo in content["repos"]:
for hook in repo["hooks"]:
if hook["id"] == name:
repo["hooks"].remove(hook)

# if repo has no hooks, remove it
if not repo["hooks"]:
content["repos"].remove(repo)

yaml.dump(content, path)


def get_hook_names(path: Path) -> list[str]:
yaml = ruamel.yaml.YAML()
with (path / ".pre-commit-config.yaml").open(mode="r") as f:
content = yaml.load(f)

hook_names = []
for repo in content["repos"]:
for hook in repo["hooks"]:
hook_names.append(hook["id"])

# Need to validate there are no duplciates
for name, count in Counter(hook_names).items():
if count > 1:
raise DuplicatedHookNameError(f"Hook name '{name}' is duplicated")

return hook_names
Empty file.
7 changes: 0 additions & 7 deletions src/usethis/_pyproject/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,3 @@
class PyProjectConfig(BaseModel):
id_keys: list[str]
main_contents: dict[str, Any]

@property
def contents(self) -> dict[str, Any]:
c = self.main_contents
for key in reversed(self.id_keys):
c = {key: c}
return c
Loading