Skip to content

Commit

Permalink
Add cli doctor command (#274)
Browse files Browse the repository at this point in the history
* Add cli doctor command

* Format code

* Add check for environment variables

* Get agni directory from coupler.py

* Add documentation
  • Loading branch information
stefsmeets authored Nov 21, 2024
1 parent 6962c08 commit 58ab8ae
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 20 deletions.
20 changes: 20 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,23 @@ safely.

Proteus has a command-line interface that can be accessed by running `proteus` on the command line.
Try `proteus --help` to see the available commands!

### `proteus doctor`

The `proteus doctor` commnd helps you to diagnose issues with your proteus installation.
It tells you about outdated or missing packages, and whether all environment variables have been set.

```console
$ proteus doctor
Dependencies
fwl-proteus: ok
fwl-mors: Update available 24.10.27 -> 24.11.18
fwl-calliope: ok
fwl-zephyrus: ok
aragog: Update available 0.1.0a0 -> 0.1.5a0
AGNI: No package metadata was found for AGNI is not installed.

Environment variables
FWL_DATA: Variable not set.
RAD_DIR: Variable not set.
```
11 changes: 11 additions & 0 deletions src/proteus/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ def spider():
from .utils.data import get_spider
get_spider()


cli.add_command(get)
get.add_command(spectral)
get.add_command(surfaces)
Expand All @@ -147,5 +148,15 @@ def spider():
get.add_command(petsc)
get.add_command(spider)


@click.command()
def doctor():
"""Diagnose your PROTEUS installation"""
from .doctor import doctor_entry
doctor_entry()


cli.add_command(doctor)

if __name__ == '__main__':
cli()
115 changes: 115 additions & 0 deletions src/proteus/doctor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
from __future__ import annotations

import importlib.metadata
import os
from functools import partial
from importlib.metadata import PackageNotFoundError
from typing import Callable

import click
import requests
from attr import dataclass

from proteus.utils.coupler import _get_agni_version, get_proteus_directories

DIRS = get_proteus_directories()

HEADER_STYLE = {'fg': 'yellow', 'underline': True, 'bold': True}
OK_STYLE = {'fg': 'green'}
ERROR_STYLE = {'fg': 'red'}
DEFAULT_STYLE = {'fg': 'yellow'}


@dataclass
class BasePackage:
name: str

def current_version(self) -> str: ...

def latest_version(self) -> str: ...

def get_status_message(self) -> str:
try:
current_version = self.current_version()
latest_version = self.latest_version()
except BaseException as exc:
message = click.style(str(exc), **ERROR_STYLE)
else:
if current_version != latest_version:
message = click.style(
f'Update available {current_version} -> {latest_version}', fg='yellow'
)
else:
message = click.style('ok', **OK_STYLE)

name = click.style(self.name, **OK_STYLE)
return f'{name}: {message}'


class PythonPackage(BasePackage):
def current_version(self) -> str:
return importlib.metadata.version(self.name)

def latest_version(self) -> str:
response = requests.get(f'https://pypi.org/pypi/{self.name}/json')
if not response.ok:
response.raise_for_status()

return response.json()['info']['version']


@dataclass
class GitPackage(BasePackage):
owner: str
version_getter: Callable

def current_version(self) -> str:
try:
return self.version_getter()
except FileNotFoundError as exc:
raise PackageNotFoundError(f'{self.name} is not installed.') from exc

def latest_version(self) -> str:
response = requests.get(
f'https://api.github.com/repos/{self.owner}/{self.name}/releases/latest'
)
return response.json()['name']


PACKAGES = (
PythonPackage(name='aragog'),
PythonPackage(name='fwl-calliope'),
PythonPackage(name='fwl-janus'),
PythonPackage(name='fwl-proteus'),
PythonPackage(name='fwl-mors'),
PythonPackage(name='fwl-zephyrus'),
GitPackage(name='AGNI', owner='nichollsh', version_getter=partial(_get_agni_version, DIRS)),
)


def get_env_var_status_message(var: str) -> str:
if os.environ.get(var):
message = click.style('ok', **OK_STYLE)
else:
message = click.style('Variable not set.', **ERROR_STYLE)

name = click.style(var, **OK_STYLE)
return f'{name}: {message}'


VARIABLES = (
'FWL_DATA',
'RAD_DIR',
)


def doctor_entry():
click.secho('Packages', **HEADER_STYLE)
for package in PACKAGES:
message = package.get_status_message()
click.echo(message)

click.secho('\nEnvironment variables', **HEADER_STYLE)
for var in VARIABLES:
message = get_env_var_status_message(var)
click.echo(message)
56 changes: 36 additions & 20 deletions src/proteus/utils/coupler.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,37 +498,53 @@ def UpdatePlots( hf_all:pd.DataFrame, dirs:dict, config:Config, end=False, num_s
# Close all figures
plt.close()

def SetDirectories(config: Config):

def get_proteus_directories(*, out_dir: str = 'proteus_out') -> dict[str, str]:
"""Create dict of proteus directories from root dir.
Parameters
----------
root_dir : str
Proteus root directory
out_dir : str, optional
Name out output directory
Returns
-------
dirs : dict[str, str]
Proteus directories dict
"""
root_dir = get_proteus_dir()

return {
"agni": os.path.join(root_dir, "AGNI"),
"input": os.path.join(root_dir, "input"),
"output": os.path.join(root_dir, "output", out_dir),
"proteus": root_dir,
"spider": os.path.join(root_dir, "SPIDER"),
"tools": os.path.join(root_dir, "tools"),
"vulcan": os.path.join(root_dir, "VULCAN"),
"utils": os.path.join(root_dir, "src", "proteus", "utils")
}


def SetDirectories(config: Config) -> dict[str, str]:
"""Set directories dictionary
Sets paths to the required directories, based on the configuration provided
by the options dictionary.
Parameters
----------
config : Config
PROTEUS options dictionary
config : Config
PROTEUS options dictionary
Returns
----------
dirs : dict
Dictionary of paths to important directories
dirs : dict
Dictionary of paths to important directories
"""

proteus_dir = get_proteus_dir()
proteus_src = os.path.join(proteus_dir,"src","proteus")

# PROTEUS folders
dirs = {
"output": os.path.join(proteus_dir,"output",config.params.out.path),
"input": os.path.join(proteus_dir,"input"),
"proteus": proteus_dir,
"agni": os.path.join(proteus_dir,"AGNI"),
"vulcan": os.path.join(proteus_dir,"VULCAN"),
"spider": os.path.join(proteus_dir,"SPIDER"),
"utils": os.path.join(proteus_src,"utils"),
"tools": os.path.join(proteus_dir,"tools"),
}
dirs = get_proteus_directories(out_dir=config.params.out.path)

# FWL data folder
if os.environ.get('FWL_DATA') is None:
Expand Down

0 comments on commit 58ab8ae

Please sign in to comment.