From 46f4c264804350dcf35a41b20883acdd7ab459a8 Mon Sep 17 00:00:00 2001 From: Holger Frey Date: Thu, 17 Apr 2025 09:38:27 +0200 Subject: [PATCH] linting with ruff --- pyproject.toml | 55 +++++++++++++++++++++++++++++++- work_helpers/__init__.py | 2 +- work_helpers/_natural_sort.py | 5 +-- work_helpers/excel2changelog.py | 14 +++++--- work_helpers/excel2pylist.py | 11 +++++-- work_helpers/nice_path.py | 3 +- work_helpers/password.py | 3 +- work_helpers/random_int.py | 4 +-- work_helpers/sensospot_rename.py | 36 +++++++++++++-------- work_helpers/sg_mbp_build.py | 14 +++++--- work_helpers/sg_mbp_issue.py | 2 +- 11 files changed, 115 insertions(+), 34 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index dc458a7..82507a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,12 +14,18 @@ license = "Beerware" requires = [ "pyperclip >=1.8.0", "click >= 7.1.2", - "black", + "pandas", + "openpyxl", + "ruff", "py-gitea", + "PyPDFForm", ] [tool.flit.scripts] +form_inspect = "work_helpers.fill_forms:inspect" +form_prepare_payments = "work_helpers.fill_forms:prepare_payments" +form_fill_payments = "work_helpers.fill_forms:payments" nice_path = "work_helpers.nice_path:make_nice_path" random_password = "work_helpers.password:get_random_password" random_ints = "work_helpers.random_int:generate_random_number_list" @@ -30,3 +36,50 @@ sg_mbp_issue_ref = "work_helpers.sg_mbp_issue:sg_mbp_issue_ref" xls2changelog = "work_helpers.excel2changelog:cli" xls2markdown = "work_helpers.excel2markdown:cli" xls2pylist = "work_helpers.excel2pylist:cli" + + + +[tool.ruff] +# see https://docs.astral.sh/ruff/configuration/ + +line-length = 80 +indent-width = 4 + +fix = true + +[tool.ruff.lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or +# McCabe complexity (`C901`) by default. +fixable = ["ALL"] +select = ["ALL"] +ignore = [ + # ignored for now, should be activated in the future + # docstrings + "D", + + # don't complain about not annotating self or cls + "ANN101", + "ANN102", + + # ignored, "ruff format" will handle this + "COM812", + "ISC001", + + # ignored, due to Windows / WSL2 setup + # flake8-executable + "EXE", +] + +[tool.ruff.lint.pydocstyle] +convention = "pep257" # Accepts: "google", "numpy", or "pep257". + +[tool.ruff.lint.per-file-ignores] +# see https://github.com/charliermarsh/ruff +"tests/*" = ["FBT003", "INP001", "PLR2004", "S101", "SLF001", "ANN"] +"noxfile.py" = ["ANN"] + + +[tool.ruff.format] +indent-style = "space" + diff --git a/work_helpers/__init__.py b/work_helpers/__init__.py index 5d4657f..13cc2eb 100644 --- a/work_helpers/__init__.py +++ b/work_helpers/__init__.py @@ -1,3 +1,3 @@ -""" Some helper scripts for the day to day work with Ubuntu in WSL2 """ +"""Some helper scripts for the day to day work with Ubuntu in WSL2""" __version__ = "0.0.2" diff --git a/work_helpers/_natural_sort.py b/work_helpers/_natural_sort.py index 84cc098..1c06d05 100644 --- a/work_helpers/_natural_sort.py +++ b/work_helpers/_natural_sort.py @@ -1,4 +1,4 @@ -""" Sort the content of an list in a natural way +"""Sort the content of an list in a natural way > l = ["A2", "A10", "A1", "A3"] > sorted(l) @@ -21,7 +21,8 @@ def _nartural_sort_convert(text): def _nartural_sort_alphanum_key(text): return tuple( - _nartural_sort_convert(part) for part in _NATURAL_SORT_REGEX_DIGITS.split(text) + _nartural_sort_convert(part) + for part in _NATURAL_SORT_REGEX_DIGITS.split(text) ) diff --git a/work_helpers/excel2changelog.py b/work_helpers/excel2changelog.py index 92fc86b..1f63a81 100644 --- a/work_helpers/excel2changelog.py +++ b/work_helpers/excel2changelog.py @@ -13,13 +13,13 @@ sheet = "Input_Data_*" col_start = "A" row_start = 8 + def xls_formula_spacing(formula): for c in "();=": formula = formula.replace(c, f"{c} ") return formula - def text_to_changelog( raw_text, sheet, start_column, start_row, compact=False, keep=False ): @@ -55,9 +55,13 @@ def text_to_changelog( prev = current -def clipboard_to_changelog(sheet, start_column, start_row, compact=False, keep=False): +def clipboard_to_changelog( + sheet, start_column, start_row, compact=False, keep=False +): xls = pyperclip.paste() - result = list(text_to_changelog(xls, sheet, start_column, start_row, compact, keep)) + result = list( + text_to_changelog(xls, sheet, start_column, start_row, compact, keep) + ) line_count = len(result) if line_count == 1: print(f"Copied one line to the clipboard") @@ -74,7 +78,9 @@ def clipboard_to_changelog(sheet, start_column, start_row, compact=False, keep=F @click.command() -@click.option("-s", "--sheet", prompt=True, required=True, default="Input_Data_*") +@click.option( + "-s", "--sheet", prompt=True, required=True, default="Input_Data_*" +) @click.option("-w", "--well", prompt=True, required=True, default="A1") @click.option("-c", "--compact", is_flag=True) @click.option("-k", "--keep", is_flag=True) diff --git a/work_helpers/excel2pylist.py b/work_helpers/excel2pylist.py index 486dab2..9a74d65 100644 --- a/work_helpers/excel2pylist.py +++ b/work_helpers/excel2pylist.py @@ -1,12 +1,15 @@ import click import pyperclip + def _strip_parts(iterable): return [item.strip() for item in iterable] + def _replace_empty_strings(iterable, replacement="None"): return [i or replacement for i in iterable] + def prepare(text): lines = text.splitlines() @@ -31,13 +34,17 @@ def pad_field(index, t): else: return value.ljust(length) + def pad_fields(iterable, lengths): return [pad_field(i, t) for i, t in enumerate(zip(iterable, lengths))] + def build_list(table, lengths): padded = (pad_fields(l, lengths) for l in table) padded_lines = (", ".join(l) for l in padded) - lines_as_list = (f" [{l}], # noqa: E201, E202, E203, E501," for l in padded_lines) + lines_as_list = ( + f" [{l}], # noqa: E201, E202, E203, E501," for l in padded_lines + ) list_content = "\n".join(lines_as_list) return f"[\n{list_content}\n]\n" @@ -49,4 +56,4 @@ def cli(): lengths = get_cell_lengths(table) result = build_list(table, lengths) print("copied to clipboard") - pyperclip.copy(result) \ No newline at end of file + pyperclip.copy(result) diff --git a/work_helpers/nice_path.py b/work_helpers/nice_path.py index 6d2f269..1a092e0 100644 --- a/work_helpers/nice_path.py +++ b/work_helpers/nice_path.py @@ -1,4 +1,4 @@ -""" create a nice path representation from a copied windows path """ +"""create a nice path representation from a copied windows path""" import click import pyperclip @@ -7,6 +7,7 @@ REPLACEMENTS = { "G:": "Google Drive", } + def replace(segment, replacements=REPLACEMENTS): return replacements.get(segment, segment) diff --git a/work_helpers/password.py b/work_helpers/password.py index d8f07ca..ee2fa9b 100644 --- a/work_helpers/password.py +++ b/work_helpers/password.py @@ -15,7 +15,7 @@ characters = list(lowers + uppers + decimals) @click.command() @click.option("--length", default=16, type=int) def get_random_password(length=16): - """ generates a random password and copies it to the clipboard """ + """generates a random password and copies it to the clipboard""" choice = [random.choice(characters) for i in range(length)] groups = [iter(choice)] * 4 grouped = ("".join(g) for g in itertools.zip_longest(*groups, fillvalue="")) @@ -23,4 +23,3 @@ def get_random_password(length=16): pyperclip.copy(password) click.echo("Copied to clipboard:", err=True) click.echo(password) - \ No newline at end of file diff --git a/work_helpers/random_int.py b/work_helpers/random_int.py index 30f7024..9f18dc9 100644 --- a/work_helpers/random_int.py +++ b/work_helpers/random_int.py @@ -2,14 +2,14 @@ import click import random import pyperclip + @click.command() @click.argument("length", default=100, type=int) def generate_random_number_list(length=100): - """ generates a new line separated list of integers and copies it to the clipboard """ + """generates a new line separated list of integers and copies it to the clipboard""" numbers = list(range(1, length + 1)) random.shuffle(numbers) integer_list = "\n".join(str(i) for i in numbers) pyperclip.copy(integer_list) click.echo("Copied to clipboard:", err=True) click.echo(integer_list) - \ No newline at end of file diff --git a/work_helpers/sensospot_rename.py b/work_helpers/sensospot_rename.py index e8018ac..6400dd0 100644 --- a/work_helpers/sensospot_rename.py +++ b/work_helpers/sensospot_rename.py @@ -1,4 +1,4 @@ -""" rename sensospot images """ +"""rename sensospot images""" import click import pathlib @@ -9,41 +9,43 @@ RENAME_MAP_NAME = "rename_map.txt" IMAGE_SUFFIXES = {".tif", ".jpg"} -def get_map_file_path(directory:str) -> pathlib.Path: +def get_map_file_path(directory: str) -> pathlib.Path: return pathlib.Path(directory) / RENAME_MAP_NAME -def list_images(directory:str) -> list[pathlib.Path]: +def list_images(directory: str) -> list[pathlib.Path]: parent = pathlib.Path(directory) non_hidden = (i for i in parent.iterdir() if not i.name.startswith(".")) return [i for i in non_hidden if i.suffix.lower() in IMAGE_SUFFIXES] -def get_image_parts(image_path:pathlib.Path) -> tuple[str, str, str]: +def get_image_parts(image_path: pathlib.Path) -> tuple[str, str, str]: return image_path.stem.rsplit("_", maxsplit=2) -def get_unique_parts(images:list[pathlib.Path]) -> list[list[str], list[str], list[str]]: +def get_unique_parts( + images: list[pathlib.Path], +) -> list[list[str], list[str], list[str]]: parts = [get_image_parts(p) for p in images] return [sorted(set(items)) for items in zip(*parts)] -def write_rename_map(map_file:pathlib.Path) -> None: +def write_rename_map(map_file: pathlib.Path) -> None: images = list_images(map_file.parent) parts = get_unique_parts(images) headers = ["stems", "wells", "exposures"] - + lines = [] for header, items in zip(headers, parts): lines.append(f"# {header}") lines.extend([f"{i}\t" for i in items]) lines.append("") lines.append("") - + map_file.write_text("\n".join(lines)) -def _parse_rename_map(content:str) -> tuple[str, str]: +def _parse_rename_map(content: str) -> tuple[str, str]: lines = [line.strip() for line in content.splitlines()] for i, line in enumerate(lines, start=1): if not line or line.startswith("#"): @@ -53,19 +55,23 @@ def _parse_rename_map(content:str) -> tuple[str, str]: yield line.split("\t", maxsplit=1) -def read_rename_map(map_file:pathlib.Path) -> dict[str, str]: +def read_rename_map(map_file: pathlib.Path) -> dict[str, str]: content = map_file.read_text() return {k: v for k, v in _parse_rename_map(content)} -def prepare_rename(images:list[pathlib.Path], rename_map:dict[str, str], sep="_") -> list[tuple[pathlib.Path, pathlib.Path]]: +def prepare_rename( + images: list[pathlib.Path], rename_map: dict[str, str], sep="_" +) -> list[tuple[pathlib.Path, pathlib.Path]]: for path in images: renamed_parts = [rename_map[p] for p in get_image_parts(path)] yield path, path.with_stem(sep.join(renamed_parts)) @click.command() -@click.argument("directory", type=click.Path(exists=True, file_okay=False, dir_okay=True)) +@click.argument( + "directory", type=click.Path(exists=True, file_okay=False, dir_okay=True) +) def sensospot_rename(directory): images = list_images(directory) if not images: @@ -85,8 +91,10 @@ def sensospot_rename(directory): try: prepared = list(prepare_rename(images, rename_map)) except Exception: - raise click.UsageError("Could not rename images. Please check the image directory and rename map file.") + raise click.UsageError( + "Could not rename images. Please check the image directory and rename map file." + ) for src, dst in prepared: click.echo(f"renaming: {src} -> {dst}") - shutil.move(src, dst) \ No newline at end of file + shutil.move(src, dst) diff --git a/work_helpers/sg_mbp_build.py b/work_helpers/sg_mbp_build.py index 5aced92..2bbf416 100644 --- a/work_helpers/sg_mbp_build.py +++ b/work_helpers/sg_mbp_build.py @@ -103,7 +103,7 @@ def _get_issue_numbers(group: str, build_version: str): # print(list(_list_current_frms(group, build_version))) for path in _list_current_frms(group, build_version): rest, issue_info = path.name.lower().split("issue") - issue_info = issue_info.removeprefix("s") # might be "issues" + issue_info = issue_info.removeprefix("s") # might be "issues" issue, *rest = issue_info.strip().split() yield issue.strip(" ,") @@ -122,7 +122,9 @@ def _extract_changes_from_log( def _get_group_from_folder(folder: Pathlike) -> str: name = pathlib.Path(folder).name - middle = name.removeprefix(GROUP_FOLDER_PREFIX).removesuffix(GROUP_FOLDER_SUFFIX) + middle = name.removeprefix(GROUP_FOLDER_PREFIX).removesuffix( + GROUP_FOLDER_SUFFIX + ) if middle in GROUPS: return middle msg = f"Folder '{name}' is not an MBP group folder" @@ -166,7 +168,9 @@ class Version: ## functions for `sg_mbp_build` -def copy_workbooks(group: str, destination: Pathlike, build_version: str) -> None: +def copy_workbooks( + group: str, destination: Pathlike, build_version: str +) -> None: source = _get_workbook_folder(group) all_xls_files = _files_in_folder(source, ".xlsx") mbp_files = (f for f in all_xls_files if f.name.lower().startswith("mbp")) @@ -198,7 +202,9 @@ def copy_workbook_changelogs( shutil.copyfile(log_file, new_path) -def copy_changelog(cwd: Pathlike, destination: Pathlike, build_version: str) -> None: +def copy_changelog( + cwd: Pathlike, destination: Pathlike, build_version: str +) -> None: changelog = _get_changelog_path(cwd) new_path = pathlib.Path(destination) / f"CHANGELOG {build_version}.txt" print(changelog.name, "->", new_path) diff --git a/work_helpers/sg_mbp_issue.py b/work_helpers/sg_mbp_issue.py index e03ab26..beebad2 100644 --- a/work_helpers/sg_mbp_issue.py +++ b/work_helpers/sg_mbp_issue.py @@ -9,4 +9,4 @@ MBP_ISSUE_BASE = "Safeguard/MBP-issues" def sg_mbp_issue_ref(issue): reference = f"{MBP_ISSUE_BASE}#{issue}" pyperclip.copy(f"{MBP_ISSUE_BASE}#{issue}") - click.echo(reference) \ No newline at end of file + click.echo(reference)