From c10268f76c32cc31a9758fca5f4e1fc95376b480 Mon Sep 17 00:00:00 2001 From: Marnik Bercx Date: Wed, 1 Oct 2025 08:00:20 +1000 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=93=A6=20Remove=20`py`=20dependency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `py` dependency was using in the (currently disabled) `CondaProject` implementation. Since we'll most likely not rely on this package when we reintroduce support for `conda`, we can remove it from the dependencies list. Co-authored-by: Daniel Hollas --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 63b404c..ff047bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,6 @@ classifiers = [ keywords = ["aiida", "workflows"] requires-python = ">=3.9" dependencies = [ - "py~=1.11", "pydantic~=2.7", "pydantic-settings~=2.2", "python-dotenv~=1.0", From c9920afbf37849c00d74f2cf199f5d6c33637eef Mon Sep 17 00:00:00 2001 From: Marnik Bercx Date: Wed, 1 Oct 2025 08:02:05 +1000 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=93=A6=20Add=20`dev`=20Dependency=20g?= =?UTF-8?q?roup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a `dev` dependency group, see PEP735 for more information on the concept: https://peps.python.org/pep-0735/ Besides the advantages described in PEP735, adding a `dev` dependency group makes development with `uv` easier since `uv` installs the `dev` dependency group by default. Now one can run `uv run pytest` straight after cloning a repo and it will work. In the future this will the remove the need for the `dev` extras. However, dependency groups are very new, and only supported from `pip` v25.1 via the `--group` option: pip install -e. --group dev Co-authored-by: Daniel Hollas --- pyproject.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index ff047bb..8cbc7fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,11 @@ dev = [ "types-pyyaml~=6.0", ] +[dependency-groups] +dev = [ + "aiida-project[dev]", +] + [tool.mypy] plugins = ['pydantic.mypy'] From 57c21d6b47fb6c3514b4b716d805c7b5ebf9a1eb Mon Sep 17 00:00:00 2001 From: Marnik Bercx Date: Wed, 1 Oct 2025 10:08:21 +1000 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=94=A7=20pre-commit:=20(properly)=20s?= =?UTF-8?q?witch=20to=20Ruff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove hooks in the `.pre-commit-config.yaml` that can be effectively replaced by Ruff (`pyupgrade`, `isort`, `black`). The Ruff rules `select`/`ignore` is copied from `aiida-core`. Co-authored-by: Daniel Hollas --- .github/workflows/update_changelog.py | 1 + .github/workflows/validate_release_tag.py | 7 +++--- .pre-commit-config.yaml | 24 +++++---------------- aiida_project/commands/main.py | 7 +++--- aiida_project/config.py | 5 +++-- aiida_project/project/__init__.py | 2 +- aiida_project/project/core.py | 9 ++++---- aiida_project/project/venv.py | 5 +++-- aiida_project/shell.py | 1 + pyproject.toml | 26 ++++++++++++++++++++--- 10 files changed, 49 insertions(+), 38 deletions(-) diff --git a/.github/workflows/update_changelog.py b/.github/workflows/update_changelog.py index 8f6ec91..72ffe53 100644 --- a/.github/workflows/update_changelog.py +++ b/.github/workflows/update_changelog.py @@ -1,5 +1,6 @@ #!/bin/bash """Script for updating the `CHANGELOG.md` based on the commits since the latest release tag.""" + import re import subprocess from pathlib import Path diff --git a/.github/workflows/validate_release_tag.py b/.github/workflows/validate_release_tag.py index 318d722..a35affe 100644 --- a/.github/workflows/validate_release_tag.py +++ b/.github/workflows/validate_release_tag.py @@ -1,4 +1,5 @@ """Validate that the version in the tag label matches the version of the package.""" + import argparse import ast from pathlib import Path @@ -30,9 +31,9 @@ def get_version_from_module(content: str) -> str: parser = argparse.ArgumentParser() parser.add_argument("GITHUB_REF", help="The GITHUB_REF environmental variable") args = parser.parse_args() - assert args.GITHUB_REF.startswith( - "refs/tags/v" - ), f'GITHUB_REF should start with "refs/tags/v": {args.GITHUB_REF}' + assert args.GITHUB_REF.startswith("refs/tags/v"), ( + f'GITHUB_REF should start with "refs/tags/v": {args.GITHUB_REF}' + ) tag_version = args.GITHUB_REF[11:] package_version = get_version_from_module( Path("aiida_project/__init__.py").read_text(encoding="utf-8") diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 895c682..21f53c8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,26 +18,12 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.13.1 hooks: - - id: pyupgrade - args: [--py37-plus] - - - repo: https://github.com/PyCQA/isort - rev: 5.12.0 - hooks: - - id: isort - - - repo: https://github.com/psf/black - rev: 23.1.0 - hooks: - - id: black - - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.252 - hooks: - - id: ruff + - id: ruff-format + - id: ruff-check + args: [--fix, --exit-non-zero-on-fix] # NOTE: mypy needs to be installed in the same environment as the package # and all of its 3rd party dependencies. diff --git a/aiida_project/commands/main.py b/aiida_project/commands/main.py index df946d3..02cbe63 100644 --- a/aiida_project/commands/main.py +++ b/aiida_project/commands/main.py @@ -3,11 +3,10 @@ import sys from datetime import datetime from pathlib import Path -from typing import List, Optional +from typing import Annotated, Optional import typer from rich import print, prompt -from typing_extensions import Annotated from ..project import EngineType, load_project_class from ..shell import ShellType, load_shell @@ -84,12 +83,12 @@ def init(shell: Optional[ShellType] = None): @app.command() -def create( +def create( # noqa: PLR0912 name: str, engine: EngineType = EngineType.venv, core_version: str = "latest", plugins: Annotated[ - List[str], typer.Option("--plugin", "-p", help="Extra plugins to install.") + list[str], typer.Option("--plugin", "-p", help="Extra plugins to install.") ] = [], python: Annotated[ Optional[str], diff --git a/aiida_project/config.py b/aiida_project/config.py index 9334154..6155687 100644 --- a/aiida_project/config.py +++ b/aiida_project/config.py @@ -1,5 +1,6 @@ +from __future__ import annotations + from pathlib import Path -from typing import Optional import dotenv from pydantic_settings import BaseSettings, SettingsConfigDict @@ -20,7 +21,7 @@ class ProjectConfig(BaseSettings): aiida_venv_dir: Path = Path(Path.home(), ".aiida_venvs") aiida_project_dir: Path = Path(Path.home(), "project") - aiida_default_python_path: Optional[Path] = None + aiida_default_python_path: Path | None = None aiida_project_structure: dict = DEFAULT_PROJECT_STRUCTURE aiida_project_shell: str = "bash" model_config = SettingsConfigDict( diff --git a/aiida_project/project/__init__.py b/aiida_project/project/__init__.py index f71b7c4..1a320aa 100644 --- a/aiida_project/project/__init__.py +++ b/aiida_project/project/__init__.py @@ -2,6 +2,6 @@ __all__ = [ "EngineType", - "load_project_class", "ProjectDict", + "load_project_class", ] diff --git a/aiida_project/project/core.py b/aiida_project/project/core.py index ba4cd7a..1388dcc 100644 --- a/aiida_project/project/core.py +++ b/aiida_project/project/core.py @@ -1,6 +1,7 @@ +from __future__ import annotations + from enum import Enum from pathlib import Path -from typing import Dict, Type, Union from ..config import ProjectConfig from .base import BaseProject @@ -8,7 +9,7 @@ from .venv import VenvProject -def load_project_class(engine_type: str) -> Type[BaseProject]: +def load_project_class(engine_type: str) -> type[BaseProject]: """Load the project class corresponding the engine type.""" engine_project_dict = { "venv": VenvProject, @@ -31,7 +32,7 @@ def __init__(self): self._projects_path.joinpath("conda").mkdir(parents=True, exist_ok=True) @property - def projects(self) -> Dict[str, BaseProject]: + def projects(self) -> dict[str, BaseProject]: projects = {} for project_file in self._projects_path.glob("**/*.json"): engine = load_project_class(str(project_file.parent.name)) @@ -44,7 +45,7 @@ def add_project(self, project: BaseProject) -> None: with Path(self._projects_path, project.engine, f"{project.name}.json").open("w") as handle: handle.write(project.json()) - def remove_project(self, project: Union[str, BaseProject]) -> None: + def remove_project(self, project: str | BaseProject) -> None: """Remove a project from the configuration files.""" project = self.projects[project] if isinstance(project, str) else project Path(self._projects_path, project.engine, f"{project.name}.json").unlink() diff --git a/aiida_project/project/venv.py b/aiida_project/project/venv.py index 41bc0bf..a69255f 100644 --- a/aiida_project/project/venv.py +++ b/aiida_project/project/venv.py @@ -1,6 +1,7 @@ import shutil import subprocess from pathlib import Path +from typing import ClassVar from aiida_project.config import ProjectConfig from aiida_project.project.base import BaseProject @@ -11,12 +12,12 @@ class VenvProject(BaseProject): _engine = "venv" - shell_activate_mapping: dict = { + shell_activate_mapping: ClassVar[dict[str, str]] = { "bash": "activate", "zsh": "activate", "fish": "activate.fish", } - shell_deactivate_mapping: dict = { + shell_deactivate_mapping: ClassVar[dict[str, str]] = { "bash": "deactivate () {", "zsh": "deactivate () {", "fish": 'function deactivate -d "Exit virtual environment"', diff --git a/aiida_project/shell.py b/aiida_project/shell.py index cbcfd35..de5dce9 100644 --- a/aiida_project/shell.py +++ b/aiida_project/shell.py @@ -1,4 +1,5 @@ """Functionality to support various shells with `aiida-project`.""" + from __future__ import annotations from enum import Enum diff --git a/pyproject.toml b/pyproject.toml index 8cbc7fe..346be6d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,8 +55,28 @@ dev = [ [tool.mypy] plugins = ['pydantic.mypy'] -[tool.black] -line-length = 100 - [tool.ruff] line-length = 100 + +[tool.ruff.lint] +# TODO: PLW1510 should be enabled and fixed! +# See `ruff rule PLW1510` +ignore = [ + 'PLW2901', # `for` loop variable overwritten by assignment target + 'PLW1510', # `subprocess.run` without explicit `check` argument + 'PLC0415', # `import` should be at the top-level of a file +] + +select = [ + 'E', # pydocstyle + 'W', # pydocstyle + 'F', # pyflakes + 'I', # isort + 'N', # pep8-naming + 'UP', # pyupgrade + 'PLC', # pylint-convention + 'PLE', # pylint-error + 'PLR', # pylint-refactor + 'PLW', # pylint-warning + 'RUF', # ruff-specific rules +]