Skip to content

Commit

Permalink
ENH: switch to pre-commit-uv (#411)
Browse files Browse the repository at this point in the history
* ENH: extract `split_dependency_definition()`
  • Loading branch information
redeboer authored Oct 9, 2024
1 parent 0875ad0 commit c81ef54
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 12 deletions.
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()

0 comments on commit c81ef54

Please sign in to comment.