Skip to content

Commit

Permalink
Enable all ruff lints
Browse files Browse the repository at this point in the history
  • Loading branch information
nx10 committed Jan 5, 2024
1 parent b0ccc3c commit 3fc76f2
Show file tree
Hide file tree
Showing 9 changed files with 70 additions and 54 deletions.
7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,13 @@ src = ["src"]
target-version = "py311"

[tool.ruff.lint]
select = ["ANN", "E", "F", "I"]
select = ["ALL"]
ignore = [
"ANN101", # self should not be annotated.
"ANN102" # cls should not be annotated.
"ANN102", # cls should not be annotated.
"COM812", # missing trailing comma in Python 3.5+
"ISC001", # implicitly concatenated strings on a single line.
"T201" # print
]
fixable = ["ALL"]
unfixable = []
Expand Down
7 changes: 4 additions & 3 deletions src/ecpac/cli.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Cli utilities."""

import pathlib as pl
from typing import Optional

import click

Expand All @@ -20,14 +21,14 @@ def check_exist_dir(path: pl.Path, label: str = "") -> bool:
return False


def option_or_prompt(opt: Optional[str], prompt: str, default: Optional[str] = None) -> str:
def option_or_prompt(opt: str | None, prompt: str, *, default: str | None = None) -> str:
"""Prompt the user for input if the option is not provided."""
if opt is not None:
return opt
return click.prompt(prompt, default=default, type=str)


def option_or_confirm(opt: Optional[bool], prompt: str, default: Optional[bool] = False) -> bool:
def option_or_confirm(opt: bool | None, prompt: str, *, default: bool | None = False) -> bool:
"""Prompt the user for input (confirmation boolean) if the option is not provided."""
if opt is not None:
return opt
Expand Down
2 changes: 2 additions & 0 deletions src/ecpac/consts.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Constants."""

import os
import pathlib as pl

Expand Down
2 changes: 2 additions & 0 deletions src/ecpac/icons.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Unicode emojis used as icons."""

ICON_CHECK = "\U00002705" # Check Mark
ICON_CROSS = "\U0000274C" # Cross Mark
ICON_FOLDER = "\U0001F4C1" # File Folder
Expand Down
80 changes: 40 additions & 40 deletions src/ecpac/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Main entry point for ecpac."""

import dataclasses
import itertools
import math
Expand All @@ -7,7 +9,6 @@
import stat
import subprocess
from datetime import datetime, timedelta
from typing import List, Optional

import click

Expand All @@ -16,18 +17,19 @@

@dataclasses.dataclass
class FsPlan:
"""Planned file system change"""
"""Planned file system change."""

path: pl.Path
is_file: bool = False
contents_text: Optional[str] = None
contents_text: str | None = None
make_executable: bool = False

def apply(self) -> None:
"""Apply the file system change."""
if self.is_file:
self.path.parent.mkdir(parents=True, exist_ok=True)
txt = "" if self.contents_text is None else self.contents_text
with open(self.path, "w", encoding="utf-8") as handle:
with self.path.open("w", encoding="utf-8") as handle:
handle.write(txt)

if self.make_executable:
Expand Down Expand Up @@ -66,7 +68,7 @@ def apply(self) -> None:
"--analysis_level",
"arg_analysis_level",
type=str,
help='Analysis level ("participant", "group", ' '"test_config").',
help='Analysis level ("participant", "group", "test_config").',
)
@click.option("-c", "--cpac", "arg_cpac", type=str, help="C-PAC folder for patching image.")
@click.option("-m", "--memory_gb", "arg_memory_gb", type=str, help="Memory (GB) for each job.")
Expand Down Expand Up @@ -98,21 +100,22 @@ def apply(self) -> None:
type=str,
help="Additional arguments that will be passed to C-PAC.",
)
def main(
arg_input: Optional[str] = None,
arg_output: Optional[str] = None,
arg_run: Optional[str] = None,
arg_image: Optional[str] = None,
arg_subject: Optional[str] = None,
arg_pipeline: Optional[str] = None,
arg_analysis_level: Optional[str] = None,
arg_cpac: Optional[str] = None,
arg_memory_gb: Optional[str] = None,
arg_threads: Optional[str] = None,
arg_duration_h: Optional[str] = None,
arg_save_working_dir: Optional[str] = None,
arg_extra_cpac_args: Optional[str] = None,
def main( # noqa: C901, PLR0912, PLR0913, PLR0915
arg_input: str | None = None,
arg_output: str | None = None,
arg_run: str | None = None,
arg_image: str | None = None,
arg_subject: str | None = None,
arg_pipeline: str | None = None,
arg_analysis_level: str | None = None,
arg_cpac: str | None = None,
arg_memory_gb: str | None = None,
arg_threads: str | None = None,
arg_duration_h: str | None = None,
arg_save_working_dir: str | None = None,
arg_extra_cpac_args: str | None = None,
) -> None:
"""CLI entry point."""
if not consts.PSC_PROJECT_USER.exists():
click.secho(
f'Error: User directory does not exist! "{consts.PSC_PROJECT_USER}" '
Expand All @@ -127,7 +130,7 @@ def main(
run_id = cli.option_or_prompt(
opt=arg_run,
prompt=icons.ICON_JOB + click.style("Run name", fg="blue"),
default=datetime.now().strftime("run_%y-%m-%d_%H-%M-%S"),
default=datetime.now().strftime("run_%y-%m-%d_%H-%M-%S"), # noqa: DTZ005
)

# Resources
Expand All @@ -138,15 +141,15 @@ def main(
prompt=icons.ICON_THREADS
+ click.style(" Number of threads/cores (int) (C-PAC will get 1 less)", fg="blue"),
default=str(8),
)
),
)

res_memory_gb = float(
cli.option_or_prompt(
opt=arg_memory_gb,
prompt=icons.ICON_MEMORY + click.style(" Memory (GB, float) (C-PAC will get 1GB less)", fg="blue"),
default=f"{2 * res_threads:.1f}",
)
),
)

res_duration = timedelta(
Expand All @@ -155,8 +158,8 @@ def main(
opt=arg_duration_h,
prompt=icons.ICON_DURATION + click.style(" Duration (hours, float)", fg="blue"),
default=f"{48.0:.1f}",
)
)
),
),
)

# Image
Expand All @@ -167,7 +170,7 @@ def main(
opt=arg_image,
prompt=icons.ICON_SINGULARITY + click.style(" Image file", fg="blue"),
default=str(consts.PSC_IMAGE_DEFAULT),
)
),
)

if cli.check_exist_file(path_image, label="Singularity image"):
Expand Down Expand Up @@ -195,8 +198,7 @@ def main(

if not patch_cpac or utils.cpac_dir_valid(path_cpac):
break
else:
click.secho(f'Error: Not a valid cpac dir! "{path_cpac}"', fg="red")
click.secho(f'Error: Not a valid cpac dir! "{path_cpac}"', fg="red")

# Input directory

Expand All @@ -205,7 +207,7 @@ def main(
cli.option_or_prompt(
opt=arg_input,
prompt=icons.ICON_FOLDER + click.style(" Input directory", fg="blue"),
)
),
)

if cli.check_exist_dir(path_input, label="Input"):
Expand Down Expand Up @@ -251,7 +253,7 @@ def main(
opt=arg_output,
prompt=icons.ICON_FOLDER + click.style(" Output directory", fg="blue"),
default=str(consts.PSC_OUTPUT_DEFAULT),
)
),
)

# Pipeline configs
Expand All @@ -265,8 +267,8 @@ def main(
),
)

preconfig_ids: List[str] = []
pipeline_config_files: List[str] = []
preconfig_ids: list[str] = []
pipeline_config_files: list[str] = []
for pipe in pipeline_ids:
if pipe in consts.CPAC_PRECONFIGS:
preconfig_ids.append(pipe)
Expand Down Expand Up @@ -329,8 +331,6 @@ def main(
# Reconstruct ecpac cli call

reargs: list[str] = ["ecpac"]
# Don't add run_id, if the user runs ecpac again, they should always change it
# reargs.extend(["--run", run_id])
reargs.extend(["--input", str(path_input)])
reargs.extend(["--output", str(path_output)])
reargs.extend(["--image", str(path_image)])
Expand All @@ -349,7 +349,7 @@ def main(

# Plan out dirs and job files

fs_plans: List[FsPlan] = []
fs_plans: list[FsPlan] = []
job_paths = []
example_job = None
example_path_stdout_log = None
Expand All @@ -362,7 +362,7 @@ def main(
path_job = path_out / "run_job.sh"
path_stdout_log = path_out / "out.log"

extra_args: List[str] = [extra_cpac_args]
extra_args: list[str] = [extra_cpac_args]
if save_working_dir:
extra_args.append(f"--save_working_dir {path_out_wd.absolute()}")

Expand All @@ -380,7 +380,7 @@ def main(
f"- Image: `{path_image.absolute()}`\n"
f"- Threads: {res_threads}\n"
f"- Memory: {res_memory_gb} GB\n"
f"- Analysis level: `{analysis_level}`"
f"- Analysis level: `{analysis_level}`",
)
+ "\n\n"
)
Expand All @@ -390,7 +390,7 @@ def main(
f"- Pipeline: `{pipe_id}`\n"
f"- Subject: `{sub}`\n"
f"- Analysis level: `{analysis_level}`\n"
f"- Output: `{path_out.absolute()}`\n"
f"- Output: `{path_out.absolute()}`\n",
)

# Adjust ressources for ACCESS limits
Expand Down Expand Up @@ -464,7 +464,7 @@ def main(
is_file=True,
contents_text=executor,
make_executable=True,
)
),
)

# Add reproducible ecpac call
Expand All @@ -474,7 +474,7 @@ def main(
is_file=True,
contents_text=shlex.join(reargs),
make_executable=True,
)
),
)

# User sanity check
Expand All @@ -499,7 +499,7 @@ def main(
plan.apply()

if click.confirm(icons.ICON_LAUNCH + click.style(" Launch now?", bg="blue"), default=None):
subprocess.run([path_executor], shell=True)
subprocess.run([path_executor], shell=True, check=False) # noqa: S602

click.secho("Jobs were executed!", bg="blue")
click.secho("Some commands you might find helpful:", fg="blue")
Expand Down
4 changes: 3 additions & 1 deletion src/ecpac/slack.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Slack-related functions."""

import json
import os
import shlex
Expand All @@ -15,7 +17,7 @@ def slack_message_bash(data: dict) -> str:
if not slack_webhook_available():
return ""
return shlex.join(
["curl", "-X", "POST", "-H", "Content-type: application/json", "--data", json.dumps(data), SLACK_WEBHOOK_URL]
["curl", "-X", "POST", "-H", "Content-type: application/json", "--data", json.dumps(data), SLACK_WEBHOOK_URL],
)


Expand Down
15 changes: 8 additions & 7 deletions src/ecpac/utils.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
"""Utility functions."""

import datetime
import os
import pathlib as pl
import re
from typing import Optional, Union


def filesafe(f: str) -> str:
"""Convert a string to a file-safe string."""
return re.sub(r"[^a-zA-Z0-9_\-]", "_", f)


def option_truthy(opt: Optional[str]) -> Optional[bool]:
"""
Convert a string option to a boolean option.
def option_truthy(opt: str | None) -> bool | None:
"""Convert a string option to a boolean option.
Common truthy values like "true", "y", "yes", and "1" are converted to True.
None is converted to None.
All other values are converted to False.
"""
if opt is None:
return None
opt_lower = opt.lower()
return opt_lower == "true" or opt_lower == "y" or opt_lower == "yes" or opt_lower == "1"
return opt_lower in ("true", "y", "yes", "1")


def timedelta_to_hms(t: datetime.timedelta) -> str:
Expand All @@ -34,7 +35,7 @@ def bullet_str_list(list_: list) -> str:
return "\n".join([f" - {i}" for i in list_])


def cpac_dir_valid(path: Union[str, os.PathLike]) -> bool:
def cpac_dir_valid(path: str | os.PathLike) -> bool:
"""Check if a directory is a valid C-PAC source code directory."""
p = pl.Path(path)
return (
Expand All @@ -44,6 +45,6 @@ def cpac_dir_valid(path: Union[str, os.PathLike]) -> bool:
)


def bridges_gb_to_mb(gb: float | int) -> float | int:
def bridges_gb_to_mb(gb: float) -> float:
"""ACCESS/Bridges uses 1000 MB per GB instead of 1024 MB per GB."""
return gb * 1000
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests."""
6 changes: 5 additions & 1 deletion tests/dummy_test.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
"""Dummy test."""


def test_dummy() -> None:
assert 1 == 1
"""Dummy test."""
assert 1 == 1 # noqa: PLR0133, S101

0 comments on commit 3fc76f2

Please sign in to comment.