From 5fb875089887ca2300517de335d2a90196d2e525 Mon Sep 17 00:00:00 2001 From: Marnik Bercx Date: Wed, 1 Oct 2025 10:38:06 +1000 Subject: [PATCH] =?UTF-8?q?=F0=9F=91=8C=20`create`:=20install=20packages?= =?UTF-8?q?=20together?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `--plugin` option of the `create` command allows the user to install additional packages next to `aiida-core`. In the current approach these are installed one by one after completing the installation of `aiida-core`. Moreover, if any of the package installations failed, this would do so silently. Here we improve the installation step by installing all packages in one go, which makes it easier for the resolver to take into account all constraints. Additionally, any errors are now caught and shown to the user. This commit also removes the broken feature to clone packages from their GitHub repository and install them locally. Note that installing packages from GitHub is still possible using the typically `pip` syntax `git+`: aiida-project create test -p git+https://github.com/aiidateam/aiida-quantumespresso The repo is simply no longer cloned to the `git` directory`. This directory is also removed from the default project layout, since it was confusing to refer to it due to the similarity with the `.git` directory. Co-authored-by: Daniel Hollas --- .github/workflows/ci.yml | 10 +++++++++- aiida_project/commands/main.py | 31 +++++++++++++++---------------- aiida_project/config.py | 1 - aiida_project/project/base.py | 16 +++------------- aiida_project/project/venv.py | 12 +++--------- 5 files changed, 30 insertions(+), 40 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 020fa0b..20587fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,9 @@ jobs: - name: Create run: aiida-project create testproject + - name: Create with additional plugins + run: 'aiida-project create testproject2 --core-version=2.7 -p aiida-cp2k -p git+https://github.com/aiidateam/aiida-quantumespresso' + # NOTE: The cda bash function does not seem to work in GitHub runners so we execute it manually - name: cda run: | @@ -49,6 +52,11 @@ jobs: source "$aiida_venv_dir/testproject/bin/activate" cd "$aiida_project_dir/testproject" && pwd ls -lrt + uv pip list - name: Destroy - run: aiida-project destroy --force testproject + run: | + aiida-project destroy --force testproject + export $(grep -v '^#' ~/.aiida_project.env | xargs) + if [[ -d "$aiida_project_dir/testproject" ]]; then echo "Project destruction incomplete!"; exit 1; fi + if [[ -d "$aiida_venv_dir/testproject" ]]; then echo "Project venv not destroyed!"; exit 1; fi diff --git a/aiida_project/commands/main.py b/aiida_project/commands/main.py index 02cbe63..70ced35 100644 --- a/aiida_project/commands/main.py +++ b/aiida_project/commands/main.py @@ -3,6 +3,7 @@ import sys from datetime import datetime from pathlib import Path +from subprocess import CalledProcessError from typing import Annotated, Optional import typer @@ -83,7 +84,7 @@ def init(shell: Optional[ShellType] = None): @app.command() -def create( # noqa: PLR0912 +def create( name: str, engine: EngineType = EngineType.venv, core_version: str = "latest", @@ -152,22 +153,20 @@ def create( # noqa: PLR0912 project_dict.add_project(project) print("✅ [bold green]Success:[/bold green] Project created.") + aiida_spec = "aiida-core" if core_version != "latest": - typer.echo(f"💾 Installing AiiDA core module v{core_version}.") - project.install(f"aiida-core=={core_version}") - else: - typer.echo("💾 Installing the latest release of the AiiDA core module.") - project.install("aiida-core") - - for plugin in plugins: - if "github.com" in plugin: - clone_path = project.project_path / Path("git") / Path(plugin.split("/")[-1]).stem - typer.echo(f"⬇️ Cloning repo `{plugin}` from GitHub to `{clone_path.resolve()}`.") - project.clone_repo(plugin, clone_path) - typer.echo(f"💾 Installing local repo `{clone_path}` as editable install.") - else: - typer.echo(f"💾 Installing `{plugin}` from the PyPI.") - project.install(plugin) + aiida_spec += f"=={core_version}" + + packages = [aiida_spec, *plugins] + typer.echo(f"💾 Installing packages `{' '.join(packages)}`") + try: + project.install(packages) + except CalledProcessError as e: + print("[bold red]Error:[/bold red] Package installation failed!") + typer.echo(e) + typer.echo(e.stdout.decode()) + typer.echo(e.stderr.decode()) + sys.exit(1) @app.command() diff --git a/aiida_project/config.py b/aiida_project/config.py index 6155687..4b41cf3 100644 --- a/aiida_project/config.py +++ b/aiida_project/config.py @@ -12,7 +12,6 @@ "computer", "code", ], - "git": [], } diff --git a/aiida_project/project/base.py b/aiida_project/project/base.py index 466a83a..25af311 100644 --- a/aiida_project/project/base.py +++ b/aiida_project/project/base.py @@ -1,7 +1,6 @@ from __future__ import annotations import shutil -import subprocess from abc import ABC, abstractmethod from pathlib import Path @@ -32,7 +31,7 @@ def engine(self): return self._engine @abstractmethod - def create(self, python_path: Path): + def create(self, python_path: Path) -> None: """Create the project.""" Path(self.project_path, ".aiida").mkdir(parents=True, exist_ok=True) recursive_mkdir(self.project_path, self.dir_structure) @@ -51,14 +50,5 @@ def append_deactivate_text(self, text: str) -> None: """Append a text to the deactivate script.""" @abstractmethod - def install(self, package): - """Install a package from PyPI.""" - - @abstractmethod - def install_local(self, path): - """Install a package from a local directory.""" - - def clone_repo(self, repo, path): - """Clone a ``git`` repository from a remote source.""" - clone_command = ["git", "clone", "--single-branch", f"{repo}", f"{path.resolve()}"] - subprocess.run(clone_command, capture_output=True) + def install(self, packages: list[str]) -> None: + """Install a list of packages from the PyPI or a GitHub repository.""" diff --git a/aiida_project/project/venv.py b/aiida_project/project/venv.py index a69255f..9a33bbf 100644 --- a/aiida_project/project/venv.py +++ b/aiida_project/project/venv.py @@ -59,12 +59,6 @@ def append_deactivate_text(self, text): ) ) - def install(self, package): - install_command = [Path(self.venv_path, "bin", "pip").as_posix(), "install", package] - subprocess.run(install_command, capture_output=True) - - def install_local(self, path): - install_command = [] - install_command.append(Path(self.venv_path, "bin", "pip")).as_posix() - install_command.extend(["install", "-e", path.as_posix()]) - subprocess.run(install_command, cwd=self.project_path) + def install(self, packages: list[str]) -> None: + install_command = [Path(self.venv_path, "bin", "pip").as_posix(), "install", *packages] + subprocess.run(install_command, capture_output=True, check=True)