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

ENH: switch to pre-commit-uv #411

Merged
merged 2 commits into from
Oct 9, 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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ doc = [
sty = [
"compwa-policy[types]",
"mypy",
"pre-commit >=1.4.0",
"pre-commit-uv",
"ruff",
]
test = [
Expand Down
9 changes: 3 additions & 6 deletions src/compwa_policy/check_dev_files/pixi.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
Pyproject,
complies_with_subset,
)
from compwa_policy.utilities.python import split_dependency_definition
from compwa_policy.utilities.toml import to_toml_array

if TYPE_CHECKING:
Expand Down Expand Up @@ -165,16 +166,12 @@ def ___to_pixi_dependency(conda_dependency: str) -> tuple[str, str]:
>>> ___to_pixi_dependency("my_package~=1.2")
('my_package', '~=1.2')
"""
matches = re.match(r"^([a-zA-Z0-9_-]+)([\!<=>~\s]*)([^ ^#]*)", conda_dependency)
if not matches:
msg = f"Could not extract package name and version from {conda_dependency}"
raise ValueError(msg)
package, operator, version = matches.groups()
package, operator, version = split_dependency_definition(conda_dependency)
if not version:
version = "*"
if operator in {"=", "=="}:
operator = ""
return package.strip(), f"{operator.strip()}{version.strip()}"
return package, f"{operator}{version}"


def __import_conda_environment(pyproject: ModifiablePyproject) -> None:
Expand Down
89 changes: 85 additions & 4 deletions src/compwa_policy/check_dev_files/precommit.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@

import re
from pathlib import Path
from typing import TYPE_CHECKING, cast
from typing import TYPE_CHECKING, Any, MutableMapping, cast

from ruamel.yaml.comments import CommentedMap
from ruamel.yaml.comments import CommentedMap, CommentedSeq

from compwa_policy.errors import PrecommitError
from compwa_policy.utilities import CONFIG_PATH
from compwa_policy.utilities.executor import Executor
from compwa_policy.utilities.precommit.getters import find_repo
from compwa_policy.utilities.precommit.struct import Hook
from compwa_policy.utilities.python import has_constraint_files
from compwa_policy.utilities.pyproject import ModifiablePyproject
from compwa_policy.utilities.python import (
has_constraint_files,
split_dependency_definition,
)
from compwa_policy.utilities.yaml import create_prettier_round_trip_yaml

if TYPE_CHECKING:
Expand All @@ -25,13 +30,14 @@


def main(precommit: ModifiablePrecommit, has_notebooks: bool) -> None:
with Executor() as do:
with Executor() as do, ModifiablePyproject.load() as pyproject:
do(_sort_hooks, precommit)
do(_update_conda_environment, precommit)
do(_update_precommit_ci_commit_msg, precommit)
do(_update_precommit_ci_skip, precommit)
do(_update_policy_hook, precommit, has_notebooks)
do(_update_repo_urls, precommit)
do(_switch_to_precommit_uv, pyproject)


def _sort_hooks(precommit: ModifiablePrecommit) -> None:
Expand Down Expand Up @@ -199,3 +205,78 @@ def _update_repo_urls(precommit: ModifiablePrecommit) -> None:
for url, new_url in updated_repos:
msg += f"\n {url} -> {new_url}"
precommit.changelog.append(msg)


def _switch_to_precommit_uv(pyproject: ModifiablePyproject) -> None:
__replace_precommit_in_conda()
__replace_precommit_in_pyproject(pyproject)


def __replace_precommit_in_conda() -> None:
if not CONFIG_PATH.conda.exists():
return
yaml = create_prettier_round_trip_yaml()
conda_env: CommentedMap = yaml.load(CONFIG_PATH.conda)
dependencies: CommentedSeq = conda_env.get("dependencies")
if dependencies is None:
return
precommit_idx = ___get_precommit_idx(dependencies)
if precommit_idx is None:
return
dependencies.pop(precommit_idx)
if "pip" not in dependencies:
dependencies.append("pip")
pip_dependencies = ___find_conda_pip_dict(dependencies)
if pip_dependencies is None:
pip_dependencies = CommentedMap({"pip": ["pre-commit-uv"]})
dependencies.append(pip_dependencies)
else:
pip_dependencies["pip"].append("pre-commit-uv")
pip_dependencies["pip"] = sorted(pip_dependencies["pip"])
yaml.dump(conda_env, CONFIG_PATH.conda)
msg = f"Switched to pre-commit-uv in {CONFIG_PATH.conda}"
raise PrecommitError(msg)


def ___get_precommit_idx(dependencies: list[str]) -> int | None:
for idx, dep in enumerate(dependencies):
if not isinstance(dep, str):
continue
name, *_ = split_dependency_definition(dep)
if name.lower() == "pre-commit":
return idx
return None


def ___find_conda_pip_dict(
dependencies: CommentedSeq,
) -> CommentedMap | None:
for dep in dependencies:
if isinstance(dep, CommentedMap) and "pip" in dep:
return dep
return None


def __replace_precommit_in_pyproject(pyproject: ModifiablePyproject) -> None:
table_key = "project.optional-dependencies"
if not pyproject.has_table(table_key):
return
optional_dependencies = pyproject.get_table(table_key)
updated = ___update_dependency_group(optional_dependencies, key="dev")
updated |= ___update_dependency_group(optional_dependencies, key="sty")
if updated:
msg = "Switched to pre-commit-uv in pyproject.toml"
pyproject.changelog.append(msg)


def ___update_dependency_group(
optional_dependencies: MutableMapping[str, Any], key: str
) -> bool:
dependencies: list[str] | None = optional_dependencies.get(key)
if dependencies is None:
return False
precommit_idx = ___get_precommit_idx(dependencies)
if precommit_idx is None:
return False
dependencies[precommit_idx] = "pre-commit-uv"
return True
32 changes: 31 additions & 1 deletion src/compwa_policy/utilities/python.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from pathlib import Path # noqa: D100
# noqa: D100
from __future__ import annotations

import re
from pathlib import Path

from compwa_policy.utilities import CONFIG_PATH
from compwa_policy.utilities.pyproject import Pyproject, get_constraints_file
Expand All @@ -11,3 +15,29 @@ def has_constraint_files() -> bool:
constraint_files = [get_constraints_file(v) for v in python_versions]
constraint_paths = [Path(path) for path in constraint_files if path is not None]
return any(path.exists() for path in constraint_paths)


def split_dependency_definition(definition: str) -> tuple[str, str, str]:
"""Get the package name, operator, and version from a PyPI dependency definition.

>>> split_dependency_definition("julia")
('julia', '', '')
>>> split_dependency_definition("python==3.9.*")
('python', '==', '3.9.*')
>>> split_dependency_definition("graphviz # for binder")
('graphviz', '', '')
>>> split_dependency_definition("pip > 19 # needed")
('pip', '>', '19')
>>> split_dependency_definition("compwa-policy!= 3.14")
('compwa-policy', '!=', '3.14')
>>> split_dependency_definition("my_package~=1.2")
('my_package', '~=', '1.2')
>>> split_dependency_definition("any_version_package==*")
('any_version_package', '==', '*')
"""
matches = re.match(r"^([a-zA-Z0-9_-]+)([\!<=>~\s]*)([^ ^#]*)", definition)
if not matches:
msg = f"Could not extract package name and version from {definition}"
raise ValueError(msg)
package, operator, version = matches.groups()
return package.strip(), operator.strip(), version.strip()
Loading