Skip to content

Commit

Permalink
👌 CLI: Switch to using typer
Browse files Browse the repository at this point in the history
  • Loading branch information
mbercx committed May 11, 2023
1 parent 8aca30c commit 048deac
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 138 deletions.
1 change: 0 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ repos:
- id: mypy
args: [--config-file=pyproject.toml]
additional_dependencies:
- click
- py
files: >
(?x)^(
Expand Down
5 changes: 0 additions & 5 deletions aiida_project/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +0,0 @@
from aiida_project.commands.create import create
from aiida_project.commands.destroy import destroy
from aiida_project.commands.main import main

__all__ = ["main", "create", "destroy"]
88 changes: 0 additions & 88 deletions aiida_project/commands/create.py

This file was deleted.

30 changes: 0 additions & 30 deletions aiida_project/commands/destroy.py

This file was deleted.

119 changes: 114 additions & 5 deletions aiida_project/commands/main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,116 @@
import click
from pathlib import Path
from typing import List, Optional

import typer
from rich import print
from typing_extensions import Annotated

@click.group("aipro")
def main():
"""Manage AiiDA projects."""
pass
from ..project import EngineType, load_project_class

ACTIVATE_AIIDA_SH = """
export AIIDA_PATH={path}
if test -x "$(command -v verdi)"
then
eval "$(_VERDI_COMPLETE=source-bash verdi)"
fi
"""

DEACTIVATE_AIIDA_SH = "unset AIIDA_PATH"


app = typer.Typer(pretty_exceptions_show_locals=False)


@app.callback()
def callback():
"""
Tool for importing CIF files and converting them into a unique set of `StructureData`.
"""


@app.command()
def create(
name: str,
engine: EngineType = EngineType.virtualenv,
core_version: str = "latest",
plugins: Annotated[
List[str], typer.Option("--plugin", "-p", help="Extra plugins to install.")
] = [],
python: Annotated[
Optional[Path],
typer.Option(
"--python",
exists=True,
dir_okay=False,
file_okay=True,
help="Path to the Python interpreter to use for the environmnent.",
),
] = None,
):
"""Create a new AiiDA project named NAME."""
from ..config import ProjectConfig, ProjectDict

config = ProjectConfig()

venv_path = config.aiida_venv_dir / Path(name)
project_path = config.aiida_project_dir / Path(name)

project = load_project_class(engine.value)(
name=name,
project_path=project_path,
venv_path=venv_path,
dir_structure=config.aiida_project_structure,
)

typer.echo("✨ Creating the project environment and directory.")
project.create(python_path=python)

typer.echo("🔧 Adding the AiiDA environment variables to the activate script.")
project.append_activate_text(ACTIVATE_AIIDA_SH.format(path=project_path))
project.append_deactivate_text(DEACTIVATE_AIIDA_SH)

project_dict = ProjectDict()
project_dict.add_project(project)
print("✅ [bold green]Success:[/bold green] Project created.")

# clone_pypackage(project_path, "aiidateam/aiida_core", branch=core_version)
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:
typer.echo(f"💾 Installing {plugin}")
project.install(plugin)


@app.command()
def destroy(
name: str,
force: Annotated[
bool, typer.Option("--force", "-f", help="Do not ask for confirmation.")
] = False,
):
"""Fully remove both the virtual environment and project directory."""
from ..config import ProjectDict

config = ProjectDict()

try:
project = config.projects[name]
except KeyError:
print(f"[bold red]Error:[/bold red] No project with name {name} found!")
return

if not force:
typer.confirm(
f"❗️ Are you sure you want to delete the entire {name} project? This cannot be undone!",
abort=True,
)

project.destroy()
config.remove_project(name)
print(f"[bold green]Succes:[/bold green] Project with name {name} has been destroyed.")
11 changes: 8 additions & 3 deletions aiida_project/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import dotenv
from pydantic import BaseSettings

from .project import EngineType
from .project import load_project_class
from .project.base import BaseProject

DEFAULT_PROJECT_STRUCTURE = {
Expand All @@ -32,12 +32,17 @@ class Config:

def __init__(self, **configuration):
super().__init__(**configuration)
if dotenv.get_key(self.Config.env_file, "aiida_venv_dir") is None:
env_config = dotenv.dotenv_values(self.Config.env_file)
if env_config.get("aiida_venv_dir", None) is None:
dotenv.set_key(
self.Config.env_file,
"aiida_venv_dir",
environ.get("WORKON_HOME", self.aiida_venv_dir.as_posix()),
)
if env_config.get("aiida_project_dir", None) is None:
dotenv.set_key(
self.Config.env_file, "aiida_project_dir", self.aiida_project_dir.as_posix()
)


class ProjectDict:
Expand All @@ -52,7 +57,7 @@ def __init__(self):
def projects(self) -> Dict[str, BaseProject]:
projects = {}
for project_file in self._projects_path.glob("**/*.json"):
engine = EngineType[str(project_file.parent.name)].value
engine = load_project_class(str(project_file.parent.name))
project = engine.parse_file(project_file)
projects[project.name] = project
return projects
Expand Down
18 changes: 14 additions & 4 deletions aiida_project/project/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
from enum import Enum
from typing import Type

from .base import BaseProject
from .conda import CondaProject
from .virtualenv import VirtualenvProject

__all__ = ["BaseProject"]
__all__ = ["BaseProject", "VirtualenvProject"]


class EngineType(Enum):
virtualenv = VirtualenvProject
conda = CondaProject
def load_project_class(engine_type: str) -> Type[BaseProject]:
"""Load the project class corresponding the engine type."""
engine_project_dict = {
"virtualenv": VirtualenvProject,
"conda": CondaProject,
}
return engine_project_dict[engine_type]


class EngineType(str, Enum):
virtualenv = "virtualenv"
conda = "conda"
6 changes: 4 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,17 @@ classifiers = [
keywords = ["aiida", "workflows"]
requires-python = ">=3.8"
dependencies = [
"click~=8.1.2",
"py~=1.11.0",
"pydantic~=1.10.7",
"python-dotenv~=1.0.0",
"typer[all]~=0.9.0"
]

[project.urls]
Source = "https://github.com/aiidateam/aiida-project"

[project.scripts]
aiida-project = "aiida_project.commands:main"
aiida-project = "aiida_project.commands.main:app"

[project.optional-dependencies]
dev = [
Expand All @@ -53,5 +53,7 @@ module = [
"dotenv",
"pydantic",
"yaml",
"typer",
"rich"
]
ignore_missing_imports = true

0 comments on commit 048deac

Please sign in to comment.