From 034442195d56149aa449b1b41196fb318d95c286 Mon Sep 17 00:00:00 2001 From: teutoburg Date: Fri, 28 Jul 2023 15:37:16 +0200 Subject: [PATCH 01/23] Use standard date format --- MICADO/TER_full_adc.dat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MICADO/TER_full_adc.dat b/MICADO/TER_full_adc.dat index 35935b0f..03c0ef9f 100644 --- a/MICADO/TER_full_adc.dat +++ b/MICADO/TER_full_adc.dat @@ -2,7 +2,7 @@ # description : Combined throughput of 8 surfaces and 4 prisms of the ADC # author : Ric Davies # sources: materials properties from Joost van den Born with generic AR coatings -# date_created : 2016 +# date_created : 2016-01-01 # date_modified : 2023-06-14 # status : post FDR # type : adc:transmission From 732d813bbbc48053143bdcc6c9e36ae4ceb31a98 Mon Sep 17 00:00:00 2001 From: teutoburg Date: Fri, 28 Jul 2023 15:38:46 +0200 Subject: [PATCH 02/23] Prototype file versions script --- MICADO/docs/fileversions.py | 114 ++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 MICADO/docs/fileversions.py diff --git a/MICADO/docs/fileversions.py b/MICADO/docs/fileversions.py new file mode 100644 index 00000000..ac71fa71 --- /dev/null +++ b/MICADO/docs/fileversions.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +""" +Spyder Editor + +This is a temporary script file. +""" + +# import sys +import logging +import yaml +# from typing import TextIO +# from io import StringIO +import datetime as dt +from pathlib import Path +from dataclasses import dataclass + +from astropy.table import Table +from astropy.io import ascii as ioascii +import nbformat as nbf + + +@dataclass(order=True) +class FileChange(): + date: dt.date + author: str + comment: str = "" + + +@dataclass +class IRDBFile(): + name: str + date_created: dt.date + date_modified: dt.date + changes: list + + @property + def last_change(self): + return max(self.changes) + + @classmethod + def from_folder(cls, folder): + for file in folder.glob("*.dat"): + table = ioascii.read(file, format="basic", guess=False) + comments_str = "\n".join(table.meta["comments"]) + meta = yaml.full_load(comments_str) + try: + chgs = list(cls._parse_changes(meta["changes"])) + except KeyError: + chgs = None + yield cls(file.name, + meta["date_created"], + meta.get("date_modified", None), + chgs) + + @staticmethod + def _parse_changes(changes): + for change in changes: + date_str, author, *comments = change.split() + date = dt.date.fromisoformat(date_str) + author = author.strip("()") + comment = " ".join(comments) + yield FileChange(date, author, comment) + + def validate_dates(self) -> None: + date_created = self.date_created + date_modified = self.date_modified + + if self.changes is not None: + date_last_change = self.last_change.date + if date_modified is None: + raise ValueError("Changes listed but no modified date set.") + if not date_last_change == date_modified: + msg = f"{date_last_change=!s} not equal to {date_modified=!s}" + raise ValueError(msg) + + if date_modified is not None: + if self.changes is None: + raise ValueError("Modified date set but no changes listed.") + if not date_modified >= date_created: + msg = f"{date_modified=!s} earlier than {date_created=!s}" + raise ValueError(msg) + + +files = list(IRDBFile.from_folder(Path("../"))) + +db = {"Data files": {}} +for f in files: + try: + f.validate_dates() + db["Data files"][f.name] = {"no_conflicts": True} + except ValueError as err: + msg = str(err).replace(" ", "_").replace("-", "--") + db["Data files"][f.name] = {msg: "error"} + +from irdb import utils +utils.write_badge_yaml(db, "test.yaml") +utils.make_badge_report("test.yaml", "test.md") + +# colnames = ["File", "Last modification"] +# data = [[f.name for f in files], [f.date_modified for f in files]] +# tbl = Table(names=colnames, data=data, copy=False) + +# nb = nbf.v4.new_notebook() +# text = """\ +# # My first automatic Jupyter Notebook +# This is an auto-generated notebook.""" + + +# nb["cells"] = [nbf.v4.new_markdown_cell(text), +# nbf.v4.new_markdown_cell(tbl.show_in_notebook().data)] +# fname = "test.ipynb" + +# with open(fname, "w") as f: +# nbf.write(nb, f) From 65724f211d16235dd0c01d5233f8a32c443f5474 Mon Sep 17 00:00:00 2001 From: teutoburg Date: Mon, 31 Jul 2023 12:23:39 +0200 Subject: [PATCH 03/23] General refactoring, use pathlib --- irdb/utils.py | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/irdb/utils.py b/irdb/utils.py index ad8518b2..8079749d 100644 --- a/irdb/utils.py +++ b/irdb/utils.py @@ -1,10 +1,11 @@ import os from os import path as pth +from pathlib import Path import yaml from irdb.system_dict import SystemDict -PKG_DIR = pth.abspath(pth.join(pth.dirname(__file__), "../")) +PKG_DIR = Path(__file__).parent.parent def get_packages(): @@ -16,16 +17,10 @@ def get_packages(): pkgs : dict {"packge_name" : "path_to_package"} """ - dirs = os.listdir(PKG_DIR) - pkgs = {} - for pkg in dirs: - pkg_path = pth.abspath(pth.join(PKG_DIR, pkg)) - pkg_base = f"{pkg}.yaml" - if pth.exists(pth.join(pkg_path, pkg_base)): - pkgs[pkg] = pkg_path - - return pkgs - + # TODO: update docstring for generator + for pkg_path in PKG_DIR.iterdir(): + if (pkg_path / f"{pkg_path.name}.yaml").exists(): + yield pkg_path.name, pkg_path def load_badge_yaml(filename=None): """ @@ -156,13 +151,13 @@ def recursive_filename_search(entry): fnames = [] if isinstance(entry, list): for item in entry: - fnames += recursive_filename_search(item) + fnames.extend(recursive_filename_search(item)) if isinstance(entry, dict): - for key in entry: - if key.lower() == "filename" or key.lower() == "file_name": - fnames += [entry[key]] - elif isinstance(entry[key], (dict, list)): - fnames += recursive_filename_search(entry[key]) + for key, value in entry.items(): + if key.lower() in {"filename", "file_name"}: + fnames.append(value) + elif isinstance(value, (dict, list)): + fnames.extend(recursive_filename_search(value)) return fnames From 499bd1b5c70d3b9a52124e28ffe8ac080e055ff2 Mon Sep 17 00:00:00 2001 From: teutoburg Date: Mon, 31 Jul 2023 12:24:24 +0200 Subject: [PATCH 04/23] Move badge-related functions to separate module --- irdb/badges.py | 230 +++++++++++++++++++++++++++++++++++++++++++++++++ irdb/utils.py | 110 ----------------------- 2 files changed, 230 insertions(+), 110 deletions(-) create mode 100644 irdb/badges.py diff --git a/irdb/badges.py b/irdb/badges.py new file mode 100644 index 00000000..c32b6c22 --- /dev/null +++ b/irdb/badges.py @@ -0,0 +1,230 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Module/script Description. + +Created on Fri Jul 28 15:42:59 2023 + +@author: ghost +""" + +import logging +from warnings import warn +from pathlib import Path +from typing import TextIO +# from io import StringIO +from numbers import Number +from string import Template +from collections.abc import Mapping + +import yaml + +from irdb.system_dict import SystemDict + + +PKG_DIR = Path(__file__).parent.parent + + +def _fix_badge_str(badge_str: str) -> str: + """Eliminate any spaces and single dashes in badge string.""" + return badge_str.replace(" ", "_").replace("-", "--") + + +class Badge(): + pattern = Template("[![](https://img.shields.io/badge/$key-$val-$col)]()") + colour = "lightgrey" + + def __new__(cls, key: str, value): + if isinstance(value, bool): + return super().__new__(BoolBadge) + if isinstance(value, Number): + return super().__new__(NumBadge) + if isinstance(value, str): + if value.startswith("!"): + return super().__new__(MsgOnlyBadge) + return super().__new__(StrBadge) + raise TypeError(value) + + def __init__(self, key: str, value): + self.key = _fix_badge_str(key) + self.value = _fix_badge_str(value) + + def write(self, stream: TextIO) -> None: + """Write formatted pattern to I/O stream""" + stream.write("* ") + _dict = {"key": self.key, "val": self.value, "col": self.colour} + stream.write(self.pattern.substitute(_dict)) + + +class BoolBadge(Badge): + colour = "red" + def __init__(self, key: str, value: bool): + super().__init__(key, value) + if self.value: + self.colour = "green" + + +class NumBadge(Badge): + colour = "lightblue" + + +class StrBadge(Badge): + special_strings = { + "observation" : "blueviolet", + "support" : "deepskyblue", + "error" : "red", + "missing" : "red", + "warning" : "orange", + "conflict" : "orange", + "incomplete" : "orange", + "ok": "green", + "found": "green", + "not found": "red", + } + + def __init__(self, key: str, value: str): + super().__init__(key, value) + self.colour = self.special_strings.get(self.value.lower(), "lightgrey") + + +class MsgOnlyBadge(StrBadge): + pattern = Template("[![](https://img.shields.io/badge/$key-$col)]()") + + def __init__(self, key: str, value: str): + # value = value.removeprefix("!") + # TODO: for Python 3.8 support: + value = value[1:] + super().__init__(key, value) + + +class BadgeReport(SystemDict): + def __init__(self, filename=None): + print("\nREPORT INIT") + self.filename = filename or "badges.yaml" + self.path = Path(PKG_DIR, "_REPORTS", self.filename) + super().__init__() + + def __enter__(self): + print("\nREPORT ENTER") + try: + with self.path.open(encoding="utf-8") as file: + self.update(yaml.full_load(file)) + except FileNotFoundError: + logging.warning("%s not found, init empty dict", self.path) + return self + + def __exit__(self, exc_type, exc_value, exc_traceback): + print("\nREPORT EXIT") + self.write_yaml() + + def write_yaml(self): + dumpstr = yaml.dump(self.dic, sort_keys=False) + self.path.write_text(dumpstr, encoding="utf-8") + + +def load_badge_yaml(filename=None): + """ + Gets the badge yaml file - should be called at the beginning of a test file + + Parameters + ---------- + filename : str + Defaults to /_REPORTS/badges.yaml + + Returns + ------- + badges : SystemDict + + """ + warn(("Using this function directly is deprecated, use BadgeReport " + "context manager instead."), DeprecationWarning, stacklevel=2) + if filename is None: + filename = "badges.yaml" + + badges = SystemDict() + + try: + with Path(PKG_DIR, "_REPORTS", filename).open(encoding="utf-8") as file: + badges.update(yaml.full_load(file)) + except FileNotFoundError: + logging.warning("%s not found, init empty dict", filename) + + return badges + + +def write_badge_yaml(badge_yaml, filename=None): + """ + Writes the badges yaml dict out to file - should be called during teardown + + Parameters + ---------- + badge_yaml : SystemDict + The dictionary of badges. + + filename : str + Defaults to /_REPORTS/badges.yaml + + """ + warn(("Using this function directly is deprecated, use BadgeReport " + "context manager instead."), DeprecationWarning, stacklevel=2) + if filename is None: + filename = "badges.yaml" + + if isinstance(badge_yaml, SystemDict): + badge_yaml = badge_yaml.dic + + path = Path(PKG_DIR, "_REPORTS", filename) + path.write_text(yaml.dump(badge_yaml), encoding="utf-8") + + +def make_badge_report(badge_filename=None, report_filename=None): + """ + Generates the badges.md file which describes the state of the packages + """ + if badge_filename is None: + badge_filename = "badges.yaml" + if report_filename is None: + report_filename = "badges.md" + + badge_dict = load_badge_yaml(badge_filename) + + path = Path(PKG_DIR, "_REPORTS", report_filename) + with path.open("w", encoding="utf-8") as file: + make_entries(file, badge_dict.dic) + + +def make_entries(stream: TextIO, entry, level=0) -> None: + """ + Recursively write lines of text from a nested dictionary to text stream. + + Parameters + ---------- + stream : TextIO + I/O stream to write the badges to. + + entry : dict, str, bool, float, int + A level from a nested dictionary + + level : int + How far down the rabbit hole we are w.r.t the nested dictionary + + Returns + ------- + None + """ + if not isinstance(entry, Mapping): + return + + for key, value in entry.items(): + stream.write("\n") + stream.write(" " * level) + if isinstance(value, Mapping): + stream.write(f"* {key}: " if level else f"## {key}: ") + # recursive + make_entries(stream, value, level=level+1) + else: + Badge(key, value).write(stream) + + +if __name__ == "__main__": + make_badge_report() diff --git a/irdb/utils.py b/irdb/utils.py index 8079749d..1b0f4c24 100644 --- a/irdb/utils.py +++ b/irdb/utils.py @@ -22,116 +22,6 @@ def get_packages(): if (pkg_path / f"{pkg_path.name}.yaml").exists(): yield pkg_path.name, pkg_path -def load_badge_yaml(filename=None): - """ - Gets the badge yaml file - should be called at the beginning of a test file - - Parameters - ---------- - filename : str - Defaults to /_REPORTS/badges.yaml - - Returns - ------- - badges : SystemDict - - """ - if filename is None: - filename = "badges.yaml" - - badges = SystemDict() - with open(pth.join(PKG_DIR, "_REPORTS", filename)) as f: - badges.update(yaml.full_load(f)) - - return badges - - -def write_badge_yaml(badge_yaml, filename=None): - """ - Writes the badges yaml dict out to file - should be called during teardown - - Parameters - ---------- - badge_yaml : SystemDict - The dictionary of badges. - - filename : str - Defaults to /_REPORTS/badges.yaml - - """ - if filename is None: - filename = "badges.yaml" - - if isinstance(badge_yaml, SystemDict): - badge_yaml = badge_yaml.dic - - with open(pth.join(PKG_DIR, "_REPORTS", filename), "w") as f: - f.write(yaml.dump(badge_yaml)) - - -def make_badge_report(badge_filename=None, report_filename=None): - """ - Generates the badges.md file which describes the state of the packages - """ - if badge_filename is None: - badge_filename = "badges.yaml" - if report_filename is None: - report_filename = "badges.md" - - badge_dict = load_badge_yaml(badge_filename) - msg = make_entries(badge_dict.dic) - - with open(pth.join(PKG_DIR, "_REPORTS", report_filename), "w") as f: - f.write(msg) - - -def make_entries(entry, level=0): - """ - Recursively generates lines of text from a nested dictionary for badges.md - - Parameters - ---------- - entry : dict, str, bool, float, int - A level from a nested dictionary - - level : int - How far down the rabbit hole we are w.r.t the nested dictionary - - Returns - ------- - msg : str - A string for the current entry / dict of entries. To be written to - badges.md - - """ - special_strings = {"observation" : "blueviolet", - "support" : "blue", - "missing" : "red", - "error" : "red"} - - badge_pattern = "[![](https://img.shields.io/badge/{}-{}-{})]()" - msg = "" - if isinstance(entry, dict): - for key in entry: - msg += "\n" + " " * level - if isinstance(entry[key], dict): - msg += f"* {key}: " if level else f"## {key}: " - msg += make_entries(entry[key], level=level+1) - else: - if isinstance(entry[key], bool): - clr = "green" if entry[key] else "red" - - elif isinstance(entry[key], str): - clr = "lightgrey" - if entry[key].lower() in special_strings: - clr = special_strings[entry[key].lower()] - - elif isinstance(entry[key], (int, float)): - clr = "lightblue" - msg += "* " + badge_pattern.format(key, entry[key], clr) - - return msg - def recursive_filename_search(entry): """ From df5439512c8759c3fecd58280746acaa044ddf49 Mon Sep 17 00:00:00 2001 From: teutoburg Date: Mon, 31 Jul 2023 12:35:11 +0200 Subject: [PATCH 05/23] Use pathlib, logging instead of print, use items() Also rename BADGES -> badges, use append() and extend(), remove unnecessary list comprehensions, better bool statements, renamed some badge messages for clarity --- irdb/tests/test_package_contents.py | 157 +++++++++++++++------------- 1 file changed, 85 insertions(+), 72 deletions(-) diff --git a/irdb/tests/test_package_contents.py b/irdb/tests/test_package_contents.py index 099a6e73..6bb45245 100644 --- a/irdb/tests/test_package_contents.py +++ b/irdb/tests/test_package_contents.py @@ -1,5 +1,7 @@ -from os import path as pth -from glob import glob +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import logging +from pathlib import Path import pytest import yaml @@ -10,8 +12,8 @@ from irdb.utils import get_packages, load_badge_yaml, write_badge_yaml, \ recursive_filename_search -PKG_DIR = pth.abspath(pth.join(pth.dirname(__file__), "../../")) -PKG_DICT = get_packages() +PKG_DIR = Path(__file__).parent.parent.parent +PKG_DICT = dict(get_packages()) BADGES = load_badge_yaml() @@ -27,31 +29,34 @@ def test_all_packages_have_a_self_named_yaml(self): whether it has a yaml file in it with the same name. """ bad_packages = [] - for pkg_name in PKG_DICT: - result = pth.exists(pth.join(PKG_DICT[pkg_name], f"{pkg_name}.yaml")) - BADGES[f"!{pkg_name}.structure.self_named_yaml"] = result - if not result: + for pkg_name, pkg_path in PKG_DICT.items(): + if (pkg_path / f"{pkg_name}.yaml").exists(): + badges[f"!{pkg_name}.structure.self_named_yaml"] = "found" + else: + badges[f"!{pkg_name}.structure.self_named_yaml"] = "not found" bad_packages.append(pkg_name) assert not bad_packages def test_default_yaml_contains_packages_list(self): bad_packages = [] - for pkg_name in PKG_DICT: - default_yaml = pth.join(PKG_DICT[pkg_name], "default.yaml") - if pth.exists(default_yaml): - with open(default_yaml) as f: - yaml_dicts = [dic for dic in yaml.full_load_all(f)] - - result = "packages" in yaml_dicts[0] and \ - "yamls" in yaml_dicts[0] and \ - f"{pkg_name}.yaml" in yaml_dicts[0]["yamls"] - BADGES[f"!{pkg_name}.structure.default_yaml"] = result - if not result: - bad_packages.append(pkg_name) - - BADGES[f"!{pkg_name}.package_type"] = "observation" + for pkg_name, pkg_path in PKG_DICT.items(): + default_yaml = pkg_path / "default.yaml" + if not default_yaml.exists(): + badges[f"!{pkg_name}.package_type"] = "support" + continue + + with default_yaml.open(encoding="utf-8") as file: + yaml_dict = next(yaml.full_load_all(file)) + + result = "packages" in yaml_dict and "yamls" in yaml_dict and \ + f"{pkg_name}.yaml" in yaml_dict["yamls"] + if result: + badges[f"!{pkg_name}.structure.default_yaml"] = "OK" else: - BADGES[f"!{pkg_name}.package_type"] = "support" + badges[f"!{pkg_name}.structure.default_yaml"] = "incomplete" + bad_packages.append(pkg_name) + + badges[f"!{pkg_name}.package_type"] = "observation" assert not bad_packages @pytest.mark.xfail( @@ -59,53 +64,52 @@ def test_default_yaml_contains_packages_list(self): ) def test_all_files_referenced_in_yamls_exist(self): missing_files = [] - for pkg_name in PKG_DICT: + for pkg_name, pkg_path in PKG_DICT.items(): - no_missing = 0 - yaml_files = glob(PKG_DICT[pkg_name]+"/*.yaml") - for yaml_file in yaml_files: - with open(yaml_file) as f: + num_missing = 0 + for yaml_file in pkg_path.glob("*.yaml"): + with yaml_file.open(encoding="utf-8") as file: try: - yaml_dicts = [dic for dic in yaml.full_load_all(f)] - except: + yaml_dicts = list(yaml.full_load_all(file)) + except Exception: yaml_dicts = [] fnames = [] for yaml_dict in yaml_dicts: - fnames += recursive_filename_search(yaml_dict) + fnames.extend(recursive_filename_search(yaml_dict)) for fname in fnames: - if fname is not None: - if not isinstance(fname, (list, tuple)): - fname = [fname] - for fn in fname: - if fn.lower() != "none" and fn[0] != "!": - full_fname = pth.join(PKG_DICT[pkg_name], fn) - if not pth.exists(full_fname): - BADGES[pkg_name]["structure"][fn] = "missing" - no_missing += 1 - missing_files += [full_fname] - - if no_missing == 0: - BADGES[f"!{pkg_name}.structure.no_missing_files"] = True + if fname is None: + continue + if not isinstance(fname, (list, tuple)): + fname = [fname] + for fn in fname: + if fn.lower() != "none" and not fn.startswith("!"): + full_fname = pkg_path / fn + if not full_fname.exists(): + badges[pkg_name]["structure"][fn] = "missing" + num_missing += 1 + missing_files.append(str(full_fname)) + + if not num_missing: + badges[f"!{pkg_name}.structure.no_missing_files"] = "!OK" assert not missing_files, f"{missing_files}" def test_all_yaml_files_readable(self): yamls_bad = [] - for pkg_name in PKG_DICT: - yaml_files = glob(PKG_DICT[pkg_name]+"/*.yaml") - no_errors = 0 - for yaml_file in yaml_files: - with open(yaml_file) as f: + for pkg_name, pkg_path in PKG_DICT.items(): + num_errors = 0 + for yaml_file in pkg_path.glob("*.yaml"): + with yaml_file.open(encoding="utf-8") as file: try: - yaml_dicts = [dic for dic in yaml.full_load_all(f)] - except: - no_errors += 1 - yamls_bad += [yaml_file] - BADGES[f"!{pkg_name}.contents"][pth.basename(yaml_file)] = "error" - - if no_errors == 0: - BADGES[f"!{pkg_name}.contents.all_yamls_readable"] = True + _ = list(yaml.full_load_all(file)) + except Exception: + num_errors += 1 + yamls_bad.append(str(yaml_file)) + badges[f"!{pkg_name}.contents"][yaml_file.name] = "error" + + if not num_errors: + badges[f"!{pkg_name}.contents.all_yamls_readable"] = "!OK" assert not yamls_bad, f"Errors found in yaml files: {yamls_bad}" def test_all_dat_files_readable(self): @@ -113,23 +117,32 @@ def test_all_dat_files_readable(self): how_bad = {"inconsistent_table_error": [], "value_error": [], "unexpected_error": []} - fns_dat = glob(PKG_DIR + "/**/*.dat") + fns_dat = PKG_DIR.rglob("*.dat") + # TODO: the following assert now always passed because fns_dat is a + # generator object (while the check was likely meant to catch + # empty lists) assert fns_dat for fn_dat in fns_dat: + fn_loc = fn_dat.relative_to(PKG_DIR) try: - datacont = DataContainer(fn_dat) - except InconsistentTableError as e: - print(fn_dat, "InconsistentTableError", e) - bad_files.append(fn_dat) - how_bad["inconsistent_table_error"] += [fn_dat] - except ValueError as e: - print(fn_dat, "ValeError", e) - bad_files.append(fn_dat) - how_bad["value_error"] += [fn_dat] - except Exception as e: - print(fn_dat, "Unexpected Exception", e.__class__, e) - bad_files.append(fn_dat) - how_bad["unexpected_error"] += [fn_dat] - print(how_bad) + # FIXME: DataContainer should be updated to support Path objects... + _ = DataContainer(str(fn_dat)) + except InconsistentTableError as err: + logging.error("%s InconsistentTableError %s", str(fn_loc), err) + bad_files.append(fn_loc) + badges[f"!{fn_loc.parts[0]}.contents"][fn_loc.name] = "error" + how_bad["inconsistent_table_error"].append(fn_loc) + except ValueError as err: + logging.error("%s ValeError %s", str(fn_loc), err) + bad_files.append(fn_loc) + badges[f"!{fn_loc.parts[0]}.contents"][fn_loc.name] = "error" + how_bad["value_error"].append(fn_loc) + except Exception as err: + logging.error("%s Unexpected Exception %s %s", str(fn_loc), + err.__class__, err) + bad_files.append(fn_loc) + badges[f"!{fn_loc.parts[0]}.contents"][fn_loc.name] = "error" + how_bad["unexpected_error"].append(fn_loc) + logging.warning(how_bad) assert not bad_files, bad_files From 816a65037b68b40d507adb7b620292a8a6c5b1be Mon Sep 17 00:00:00 2001 From: teutoburg Date: Mon, 31 Jul 2023 12:35:56 +0200 Subject: [PATCH 06/23] Use fixture instead of teardown_module, use context manager --- irdb/tests/test_package_contents.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/irdb/tests/test_package_contents.py b/irdb/tests/test_package_contents.py index 6bb45245..50d006b8 100644 --- a/irdb/tests/test_package_contents.py +++ b/irdb/tests/test_package_contents.py @@ -9,20 +9,21 @@ from scopesim.effects.data_container import DataContainer from astropy.io.ascii import InconsistentTableError -from irdb.utils import get_packages, load_badge_yaml, write_badge_yaml, \ - recursive_filename_search +from irdb.utils import get_packages, recursive_filename_search +from irdb.badges import BadgeReport PKG_DIR = Path(__file__).parent.parent.parent PKG_DICT = dict(get_packages()) -BADGES = load_badge_yaml() -def teardown_module(): - write_badge_yaml(BADGES) +@pytest.fixture(scope="class") +def badges(): + with BadgeReport() as report: + yield report class TestFileStructureOfPackages: - def test_all_packages_have_a_self_named_yaml(self): + def test_all_packages_have_a_self_named_yaml(self, badges): """This test can never fail. get_packages() decides whether a directory is a package based on @@ -37,7 +38,7 @@ def test_all_packages_have_a_self_named_yaml(self): bad_packages.append(pkg_name) assert not bad_packages - def test_default_yaml_contains_packages_list(self): + def test_default_yaml_contains_packages_list(self, badges): bad_packages = [] for pkg_name, pkg_path in PKG_DICT.items(): default_yaml = pkg_path / "default.yaml" @@ -57,12 +58,13 @@ def test_default_yaml_contains_packages_list(self): bad_packages.append(pkg_name) badges[f"!{pkg_name}.package_type"] = "observation" + assert not bad_packages @pytest.mark.xfail( reason="Most of the missing files seem to exist in other packages though, so they are probably okay." ) - def test_all_files_referenced_in_yamls_exist(self): + def test_all_files_referenced_in_yamls_exist(self, badges): missing_files = [] for pkg_name, pkg_path in PKG_DICT.items(): @@ -95,7 +97,7 @@ def test_all_files_referenced_in_yamls_exist(self): badges[f"!{pkg_name}.structure.no_missing_files"] = "!OK" assert not missing_files, f"{missing_files}" - def test_all_yaml_files_readable(self): + def test_all_yaml_files_readable(self, badges): yamls_bad = [] for pkg_name, pkg_path in PKG_DICT.items(): num_errors = 0 @@ -112,7 +114,8 @@ def test_all_yaml_files_readable(self): badges[f"!{pkg_name}.contents.all_yamls_readable"] = "!OK" assert not yamls_bad, f"Errors found in yaml files: {yamls_bad}" - def test_all_dat_files_readable(self): + @pytest.mark.xfail + def test_all_dat_files_readable(self, badges): bad_files = [] how_bad = {"inconsistent_table_error": [], "value_error": [], From b01523d3ff9214633d41aaf55bcccc58adc886f8 Mon Sep 17 00:00:00 2001 From: teutoburg Date: Mon, 31 Jul 2023 12:37:11 +0200 Subject: [PATCH 07/23] Reorder tests for nicer output --- irdb/tests/test_package_contents.py | 31 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/irdb/tests/test_package_contents.py b/irdb/tests/test_package_contents.py index 50d006b8..1b2544f5 100644 --- a/irdb/tests/test_package_contents.py +++ b/irdb/tests/test_package_contents.py @@ -23,21 +23,6 @@ def badges(): class TestFileStructureOfPackages: - def test_all_packages_have_a_self_named_yaml(self, badges): - """This test can never fail. - - get_packages() decides whether a directory is a package based on - whether it has a yaml file in it with the same name. - """ - bad_packages = [] - for pkg_name, pkg_path in PKG_DICT.items(): - if (pkg_path / f"{pkg_name}.yaml").exists(): - badges[f"!{pkg_name}.structure.self_named_yaml"] = "found" - else: - badges[f"!{pkg_name}.structure.self_named_yaml"] = "not found" - bad_packages.append(pkg_name) - assert not bad_packages - def test_default_yaml_contains_packages_list(self, badges): bad_packages = [] for pkg_name, pkg_path in PKG_DICT.items(): @@ -46,6 +31,8 @@ def test_default_yaml_contains_packages_list(self, badges): badges[f"!{pkg_name}.package_type"] = "support" continue + badges[f"!{pkg_name}.package_type"] = "observation" + with default_yaml.open(encoding="utf-8") as file: yaml_dict = next(yaml.full_load_all(file)) @@ -56,9 +43,21 @@ def test_default_yaml_contains_packages_list(self, badges): else: badges[f"!{pkg_name}.structure.default_yaml"] = "incomplete" bad_packages.append(pkg_name) + assert not bad_packages - badges[f"!{pkg_name}.package_type"] = "observation" + def test_all_packages_have_a_self_named_yaml(self, badges): + """This test can never fail. + get_packages() decides whether a directory is a package based on + whether it has a yaml file in it with the same name. + """ + bad_packages = [] + for pkg_name, pkg_path in PKG_DICT.items(): + if (pkg_path / f"{pkg_name}.yaml").exists(): + badges[f"!{pkg_name}.structure.self_named_yaml"] = "found" + else: + badges[f"!{pkg_name}.structure.self_named_yaml"] = "not found" + bad_packages.append(pkg_name) assert not bad_packages @pytest.mark.xfail( From c278e1cdfd4a6cd9f43ba55febe32a1e3ff0e69d Mon Sep 17 00:00:00 2001 From: teutoburg Date: Mon, 31 Jul 2023 13:14:14 +0200 Subject: [PATCH 08/23] Pytest markers --- irdb/tests/test_package_contents.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/irdb/tests/test_package_contents.py b/irdb/tests/test_package_contents.py index 1b2544f5..cc674674 100644 --- a/irdb/tests/test_package_contents.py +++ b/irdb/tests/test_package_contents.py @@ -23,6 +23,7 @@ def badges(): class TestFileStructureOfPackages: + @pytest.mark.usefixtures("badges") def test_default_yaml_contains_packages_list(self, badges): bad_packages = [] for pkg_name, pkg_path in PKG_DICT.items(): @@ -45,6 +46,7 @@ def test_default_yaml_contains_packages_list(self, badges): bad_packages.append(pkg_name) assert not bad_packages + @pytest.mark.usefixtures("badges") def test_all_packages_have_a_self_named_yaml(self, badges): """This test can never fail. @@ -63,6 +65,7 @@ def test_all_packages_have_a_self_named_yaml(self, badges): @pytest.mark.xfail( reason="Most of the missing files seem to exist in other packages though, so they are probably okay." ) + @pytest.mark.usefixtures("badges") def test_all_files_referenced_in_yamls_exist(self, badges): missing_files = [] for pkg_name, pkg_path in PKG_DICT.items(): @@ -96,6 +99,7 @@ def test_all_files_referenced_in_yamls_exist(self, badges): badges[f"!{pkg_name}.structure.no_missing_files"] = "!OK" assert not missing_files, f"{missing_files}" + @pytest.mark.usefixtures("badges") def test_all_yaml_files_readable(self, badges): yamls_bad = [] for pkg_name, pkg_path in PKG_DICT.items(): @@ -114,6 +118,7 @@ def test_all_yaml_files_readable(self, badges): assert not yamls_bad, f"Errors found in yaml files: {yamls_bad}" @pytest.mark.xfail + @pytest.mark.usefixtures("badges") def test_all_dat_files_readable(self, badges): bad_files = [] how_bad = {"inconsistent_table_error": [], From 792259f7c71d82400ca960c3621ba99ee4c0bdc4 Mon Sep 17 00:00:00 2001 From: teutoburg Date: Mon, 31 Jul 2023 13:14:53 +0200 Subject: [PATCH 09/23] Subsub-headings up to nested level 2, some refactoring and logging --- irdb/badges.py | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/irdb/badges.py b/irdb/badges.py index c32b6c22..31dce188 100644 --- a/irdb/badges.py +++ b/irdb/badges.py @@ -51,7 +51,6 @@ def __init__(self, key: str, value): def write(self, stream: TextIO) -> None: """Write formatted pattern to I/O stream""" - stream.write("* ") _dict = {"key": self.key, "val": self.value, "col": self.colour} stream.write(self.pattern.substitute(_dict)) @@ -98,28 +97,36 @@ def __init__(self, key: str, value: str): class BadgeReport(SystemDict): - def __init__(self, filename=None): - print("\nREPORT INIT") + def __init__(self, filename=None, report_filename=None): + logging.debug("REPORT INIT") self.filename = filename or "badges.yaml" - self.path = Path(PKG_DIR, "_REPORTS", self.filename) + self.yamlpath = Path(PKG_DIR, "_REPORTS", self.filename) + self.report_name = report_filename or "badges.md" + self.report_path = Path(PKG_DIR, "_REPORTS", self.report_name) super().__init__() def __enter__(self): - print("\nREPORT ENTER") + logging.debug("REPORT ENTER") try: - with self.path.open(encoding="utf-8") as file: + with self.yamlpath.open(encoding="utf-8") as file: self.update(yaml.full_load(file)) except FileNotFoundError: - logging.warning("%s not found, init empty dict", self.path) + logging.warning("%s not found, init empty dict", self.yamlpath) return self def __exit__(self, exc_type, exc_value, exc_traceback): - print("\nREPORT EXIT") + logging.debug("REPORT EXIT") self.write_yaml() + self.generate_report() + logging.debug("REPORT DONE") def write_yaml(self): dumpstr = yaml.dump(self.dic, sort_keys=False) - self.path.write_text(dumpstr, encoding="utf-8") + self.yamlpath.write_text(dumpstr, encoding="utf-8") + + def generate_report(self): + with self.report_path.open("w", encoding="utf-8") as file: + make_entries(file, self.dic) def load_badge_yaml(filename=None): @@ -181,6 +188,8 @@ def make_badge_report(badge_filename=None, report_filename=None): """ Generates the badges.md file which describes the state of the packages """ + warn(("Using this function directly is deprecated, use BadgeReport " + "context manager instead."), DeprecationWarning, stacklevel=2) if badge_filename is None: badge_filename = "badges.yaml" if report_filename is None: @@ -193,6 +202,12 @@ def make_badge_report(badge_filename=None, report_filename=None): make_entries(file, badge_dict.dic) +def _get_nested_header(key: str, level: int) -> str: + if level > 2: + return f"* {key}: " + return f"{'#' * (level + 2)} {key.title() if level else key}" + + def make_entries(stream: TextIO, entry, level=0) -> None: """ Recursively write lines of text from a nested dictionary to text stream. @@ -217,12 +232,14 @@ def make_entries(stream: TextIO, entry, level=0) -> None: for key, value in entry.items(): stream.write("\n") - stream.write(" " * level) + stream.write(" " * (level - 2)) if isinstance(value, Mapping): - stream.write(f"* {key}: " if level else f"## {key}: ") + stream.write(_get_nested_header(key, level)) # recursive make_entries(stream, value, level=level+1) else: + if level > 1: + stream.write("* ") Badge(key, value).write(stream) From 6524ec8d1dd71b62829f5ebb2682772f88d74d22 Mon Sep 17 00:00:00 2001 From: teutoburg Date: Mon, 31 Jul 2023 15:15:41 +0200 Subject: [PATCH 10/23] Properly do parametrisation --- irdb/tests/test_package_contents.py | 189 +++++++++++++++------------- 1 file changed, 101 insertions(+), 88 deletions(-) diff --git a/irdb/tests/test_package_contents.py b/irdb/tests/test_package_contents.py index cc674674..1fa421db 100644 --- a/irdb/tests/test_package_contents.py +++ b/irdb/tests/test_package_contents.py @@ -12,125 +12,138 @@ from irdb.utils import get_packages, recursive_filename_search from irdb.badges import BadgeReport -PKG_DIR = Path(__file__).parent.parent.parent -PKG_DICT = dict(get_packages()) +@pytest.fixture(scope="module") +def pkg_dir(): + return Path(__file__).parent.parent.parent -@pytest.fixture(scope="class") + +@pytest.fixture(scope="module") def badges(): with BadgeReport() as report: yield report class TestFileStructureOfPackages: + @pytest.mark.parametrize("package", list(get_packages())) @pytest.mark.usefixtures("badges") - def test_default_yaml_contains_packages_list(self, badges): - bad_packages = [] - for pkg_name, pkg_path in PKG_DICT.items(): - default_yaml = pkg_path / "default.yaml" - if not default_yaml.exists(): - badges[f"!{pkg_name}.package_type"] = "support" - continue - - badges[f"!{pkg_name}.package_type"] = "observation" - - with default_yaml.open(encoding="utf-8") as file: - yaml_dict = next(yaml.full_load_all(file)) - - result = "packages" in yaml_dict and "yamls" in yaml_dict and \ - f"{pkg_name}.yaml" in yaml_dict["yamls"] - if result: - badges[f"!{pkg_name}.structure.default_yaml"] = "OK" - else: - badges[f"!{pkg_name}.structure.default_yaml"] = "incomplete" - bad_packages.append(pkg_name) - assert not bad_packages - + def test_default_yaml_contains_packages_list(self, package, badges): + pkg_name, pkg_path = package + default_yaml = pkg_path / "default.yaml" + if not default_yaml.exists(): + badges[f"!{pkg_name}.package_type"] = "support" + return + + badges[f"!{pkg_name}.package_type"] = "observation" + + with default_yaml.open(encoding="utf-8") as file: + yaml_dict = next(yaml.full_load_all(file)) + + result = "packages" in yaml_dict and "yamls" in yaml_dict and \ + f"{pkg_name}.yaml" in yaml_dict["yamls"] + if result: + badges[f"!{pkg_name}.structure.default_yaml"] = "OK" + else: + badges[f"!{pkg_name}.structure.default_yaml"] = "incomplete" + assert result, pkg_name + + @pytest.mark.parametrize("package", list(get_packages())) @pytest.mark.usefixtures("badges") - def test_all_packages_have_a_self_named_yaml(self, badges): + def test_all_packages_have_a_self_named_yaml(self, package, badges): """This test can never fail. get_packages() decides whether a directory is a package based on whether it has a yaml file in it with the same name. """ - bad_packages = [] - for pkg_name, pkg_path in PKG_DICT.items(): - if (pkg_path / f"{pkg_name}.yaml").exists(): - badges[f"!{pkg_name}.structure.self_named_yaml"] = "found" - else: - badges[f"!{pkg_name}.structure.self_named_yaml"] = "not found" - bad_packages.append(pkg_name) - assert not bad_packages + pkg_name, pkg_path = package + if (result := (pkg_path / f"{pkg_name}.yaml").exists()): + badges[f"!{pkg_name}.structure.self_named_yaml"] = "found" + else: + badges[f"!{pkg_name}.structure.self_named_yaml"] = "not found" + assert result, pkg_name @pytest.mark.xfail( - reason="Most of the missing files seem to exist in other packages though, so they are probably okay." + reason=("Most of the missing files seem to exist in other packages " + "though, so they are probably okay.") ) + @pytest.mark.parametrize("package", list(get_packages())) @pytest.mark.usefixtures("badges") - def test_all_files_referenced_in_yamls_exist(self, badges): + def test_all_files_referenced_in_yamls_exist(self, package, badges): missing_files = [] - for pkg_name, pkg_path in PKG_DICT.items(): - - num_missing = 0 - for yaml_file in pkg_path.glob("*.yaml"): - with yaml_file.open(encoding="utf-8") as file: - try: - yaml_dicts = list(yaml.full_load_all(file)) - except Exception: - yaml_dicts = [] - - fnames = [] - for yaml_dict in yaml_dicts: - fnames.extend(recursive_filename_search(yaml_dict)) - - for fname in fnames: - if fname is None: - continue - if not isinstance(fname, (list, tuple)): - fname = [fname] - for fn in fname: - if fn.lower() != "none" and not fn.startswith("!"): - full_fname = pkg_path / fn - if not full_fname.exists(): - badges[pkg_name]["structure"][fn] = "missing" - num_missing += 1 - missing_files.append(str(full_fname)) - - if not num_missing: - badges[f"!{pkg_name}.structure.no_missing_files"] = "!OK" - assert not missing_files, f"{missing_files}" - + pkg_name, pkg_path = package + for yaml_file in pkg_path.glob("*.yaml"): + with yaml_file.open(encoding="utf-8") as file: + # An error here shouldn't pass silently. If this regularly + # produces [the same] error(s), *that* error(s) should be + # caught and handled accordingly! + # Additionally: should test_all_yaml_files_readable assert + # that these files don't produce any errors? Hmmm + # try: + yaml_dicts = list(yaml.full_load_all(file)) + # except Exception: + # yaml_dicts = [] + + fnames = [] + for yaml_dict in yaml_dicts: + fnames.extend(recursive_filename_search(yaml_dict)) + + for fname in fnames: + if fname is None: + continue + if not isinstance(fname, (list, tuple)): + fname = [fname] + for fn in fname: + if fn.lower() != "none" and not fn.startswith("!"): + full_fname = pkg_path / fn + if not full_fname.exists(): + badges[pkg_name]["structure"][fn] = "missing" + # Since with the parametrisation, we only have one + # missing_files list per pkg, don't need full name. + # missing_files.append(str(full_fname)) + missing_files.append(fn) + + if not missing_files: + badges[f"!{pkg_name}.structure.no_missing_files"] = "!OK" + assert not missing_files, f"{pkg_name}: {missing_files=}" + + @pytest.mark.parametrize("package", list(get_packages())) @pytest.mark.usefixtures("badges") - def test_all_yaml_files_readable(self, badges): + def test_all_yaml_files_readable(self, package, badges): yamls_bad = [] - for pkg_name, pkg_path in PKG_DICT.items(): - num_errors = 0 - for yaml_file in pkg_path.glob("*.yaml"): - with yaml_file.open(encoding="utf-8") as file: - try: - _ = list(yaml.full_load_all(file)) - except Exception: - num_errors += 1 - yamls_bad.append(str(yaml_file)) - badges[f"!{pkg_name}.contents"][yaml_file.name] = "error" - - if not num_errors: - badges[f"!{pkg_name}.contents.all_yamls_readable"] = "!OK" - assert not yamls_bad, f"Errors found in yaml files: {yamls_bad}" - - @pytest.mark.xfail - @pytest.mark.usefixtures("badges") - def test_all_dat_files_readable(self, badges): + pkg_name, pkg_path = package + for yaml_file in pkg_path.glob("*.yaml"): + with yaml_file.open(encoding="utf-8") as file: + try: + _ = list(yaml.full_load_all(file)) + except Exception: + # TODO: maybe Exception is too general? This used to be a + # bare except anyway... + # TODO: maybe record *what* error was produced here, + # similar to how we do in test_all_dat_files_readable + yamls_bad.append(str(yaml_file)) + badges[f"!{pkg_name}.contents"][yaml_file.name] = "error" + + if not yamls_bad: + badges[f"!{pkg_name}.contents.all_yamls_readable"] = "!OK" + assert not yamls_bad, f"Errors in {pkg_name} yaml files: {yamls_bad}" + + @pytest.mark.xfail( + reason=("Due to bad globbing, files in subfolders were not checked " + "previously, some of those now fail. idk y tho") + ) + @pytest.mark.usefixtures("pkg_dir", "badges") + def test_all_dat_files_readable(self, pkg_dir, badges): bad_files = [] how_bad = {"inconsistent_table_error": [], "value_error": [], "unexpected_error": []} - fns_dat = PKG_DIR.rglob("*.dat") + fns_dat = pkg_dir.rglob("*.dat") # TODO: the following assert now always passed because fns_dat is a # generator object (while the check was likely meant to catch # empty lists) assert fns_dat for fn_dat in fns_dat: - fn_loc = fn_dat.relative_to(PKG_DIR) + fn_loc = fn_dat.relative_to(pkg_dir) try: # FIXME: DataContainer should be updated to support Path objects... _ = DataContainer(str(fn_dat)) From befd7f7ded05d948e6a87d47750f801751ccfb7a Mon Sep 17 00:00:00 2001 From: teutoburg Date: Mon, 31 Jul 2023 18:03:38 +0200 Subject: [PATCH 11/23] typing --- irdb/badges.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/irdb/badges.py b/irdb/badges.py index 31dce188..db5bc823 100644 --- a/irdb/badges.py +++ b/irdb/badges.py @@ -120,11 +120,11 @@ def __exit__(self, exc_type, exc_value, exc_traceback): self.generate_report() logging.debug("REPORT DONE") - def write_yaml(self): + def write_yaml(self) -> None: dumpstr = yaml.dump(self.dic, sort_keys=False) self.yamlpath.write_text(dumpstr, encoding="utf-8") - def generate_report(self): + def generate_report(self) -> None: with self.report_path.open("w", encoding="utf-8") as file: make_entries(file, self.dic) From 1b73304d0f0ec02f15c605b340cc7769d1a86e70 Mon Sep 17 00:00:00 2001 From: teutoburg Date: Mon, 31 Jul 2023 18:03:58 +0200 Subject: [PATCH 12/23] minor fixes --- irdb/tests/test_package_contents.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/irdb/tests/test_package_contents.py b/irdb/tests/test_package_contents.py index 1fa421db..41d26e4a 100644 --- a/irdb/tests/test_package_contents.py +++ b/irdb/tests/test_package_contents.py @@ -45,7 +45,7 @@ def test_default_yaml_contains_packages_list(self, package, badges): badges[f"!{pkg_name}.structure.default_yaml"] = "OK" else: badges[f"!{pkg_name}.structure.default_yaml"] = "incomplete" - assert result, pkg_name + assert result, pkg_name @pytest.mark.parametrize("package", list(get_packages())) @pytest.mark.usefixtures("badges") @@ -149,20 +149,20 @@ def test_all_dat_files_readable(self, pkg_dir, badges): _ = DataContainer(str(fn_dat)) except InconsistentTableError as err: logging.error("%s InconsistentTableError %s", str(fn_loc), err) - bad_files.append(fn_loc) + bad_files.append(str(fn_loc)) badges[f"!{fn_loc.parts[0]}.contents"][fn_loc.name] = "error" - how_bad["inconsistent_table_error"].append(fn_loc) + how_bad["inconsistent_table_error"].append(str(fn_loc)) except ValueError as err: logging.error("%s ValeError %s", str(fn_loc), err) - bad_files.append(fn_loc) + bad_files.append(str(fn_loc)) badges[f"!{fn_loc.parts[0]}.contents"][fn_loc.name] = "error" - how_bad["value_error"].append(fn_loc) + how_bad["value_error"].append(str(fn_loc)) except Exception as err: logging.error("%s Unexpected Exception %s %s", str(fn_loc), err.__class__, err) - bad_files.append(fn_loc) + bad_files.append(str(fn_loc)) badges[f"!{fn_loc.parts[0]}.contents"][fn_loc.name] = "error" - how_bad["unexpected_error"].append(fn_loc) + how_bad["unexpected_error"].append(str(fn_loc)) logging.warning(how_bad) assert not bad_files, bad_files From c56381422214038c24b319f0bb76859d27c7aed4 Mon Sep 17 00:00:00 2001 From: teutoburg Date: Mon, 31 Jul 2023 18:06:55 +0200 Subject: [PATCH 13/23] move file to tests --- {MICADO/docs => irdb/tests}/fileversions.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {MICADO/docs => irdb/tests}/fileversions.py (100%) diff --git a/MICADO/docs/fileversions.py b/irdb/tests/fileversions.py similarity index 100% rename from MICADO/docs/fileversions.py rename to irdb/tests/fileversions.py From 81376b0d8b7f358216428005534d2b99893e710d Mon Sep 17 00:00:00 2001 From: teutoburg Date: Mon, 31 Jul 2023 18:14:01 +0200 Subject: [PATCH 14/23] move file (again...) --- irdb/{tests => }/fileversions.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename irdb/{tests => }/fileversions.py (100%) diff --git a/irdb/tests/fileversions.py b/irdb/fileversions.py similarity index 100% rename from irdb/tests/fileversions.py rename to irdb/fileversions.py From bc388141267167cc57bd03b156b593ad0b64c02b Mon Sep 17 00:00:00 2001 From: teutoburg Date: Mon, 31 Jul 2023 18:19:52 +0200 Subject: [PATCH 15/23] Correct script mode, better constructors --- irdb/fileversions.py | 90 +++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/irdb/fileversions.py b/irdb/fileversions.py index ac71fa71..7b3a53bd 100644 --- a/irdb/fileversions.py +++ b/irdb/fileversions.py @@ -37,20 +37,24 @@ class IRDBFile(): def last_change(self): return max(self.changes) + @classmethod + def from_file(cls, file): + table = ioascii.read(file, format="basic", guess=False) + comments_str = "\n".join(table.meta["comments"]) + meta = yaml.full_load(comments_str) + try: + chgs = list(cls._parse_changes(meta["changes"])) + except KeyError: + chgs = None + return cls(file.name, + meta["date_created"], + meta.get("date_modified", None), + chgs) + @classmethod def from_folder(cls, folder): for file in folder.glob("*.dat"): - table = ioascii.read(file, format="basic", guess=False) - comments_str = "\n".join(table.meta["comments"]) - meta = yaml.full_load(comments_str) - try: - chgs = list(cls._parse_changes(meta["changes"])) - except KeyError: - chgs = None - yield cls(file.name, - meta["date_created"], - meta.get("date_modified", None), - chgs) + yield cls.from_file(file) @staticmethod def _parse_changes(changes): @@ -80,35 +84,35 @@ def validate_dates(self) -> None: msg = f"{date_modified=!s} earlier than {date_created=!s}" raise ValueError(msg) - -files = list(IRDBFile.from_folder(Path("../"))) - -db = {"Data files": {}} -for f in files: - try: - f.validate_dates() - db["Data files"][f.name] = {"no_conflicts": True} - except ValueError as err: - msg = str(err).replace(" ", "_").replace("-", "--") - db["Data files"][f.name] = {msg: "error"} - -from irdb import utils -utils.write_badge_yaml(db, "test.yaml") -utils.make_badge_report("test.yaml", "test.md") - -# colnames = ["File", "Last modification"] -# data = [[f.name for f in files], [f.date_modified for f in files]] -# tbl = Table(names=colnames, data=data, copy=False) - -# nb = nbf.v4.new_notebook() -# text = """\ -# # My first automatic Jupyter Notebook -# This is an auto-generated notebook.""" - - -# nb["cells"] = [nbf.v4.new_markdown_cell(text), -# nbf.v4.new_markdown_cell(tbl.show_in_notebook().data)] -# fname = "test.ipynb" - -# with open(fname, "w") as f: -# nbf.write(nb, f) +if __name__ == "__main__": + files = list(IRDBFile.from_folder(Path("../"))) + + db = {"Data files": {}} + for f in files: + try: + f.validate_dates() + db["Data files"][f.name] = {"no_conflicts": True} + except ValueError as err: + msg = str(err).replace(" ", "_").replace("-", "--") + db["Data files"][f.name] = {msg: "error"} + + from irdb import utils + utils.write_badge_yaml(db, "test.yaml") + utils.make_badge_report("test.yaml", "test.md") + + # colnames = ["File", "Last modification"] + # data = [[f.name for f in files], [f.date_modified for f in files]] + # tbl = Table(names=colnames, data=data, copy=False) + + # nb = nbf.v4.new_notebook() + # text = """\ + # # My first automatic Jupyter Notebook + # This is an auto-generated notebook.""" + + + # nb["cells"] = [nbf.v4.new_markdown_cell(text), + # nbf.v4.new_markdown_cell(tbl.show_in_notebook().data)] + # fname = "test.ipynb" + + # with open(fname, "w") as f: + # nbf.write(nb, f) From b0cb30d0a211274116a11420c7ffd17db8ffa816 Mon Sep 17 00:00:00 2001 From: teutoburg Date: Mon, 31 Jul 2023 18:56:46 +0200 Subject: [PATCH 16/23] Add new date test --- irdb/tests/test_package_contents.py | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/irdb/tests/test_package_contents.py b/irdb/tests/test_package_contents.py index 41d26e4a..242efcd0 100644 --- a/irdb/tests/test_package_contents.py +++ b/irdb/tests/test_package_contents.py @@ -11,6 +11,7 @@ from irdb.utils import get_packages, recursive_filename_search from irdb.badges import BadgeReport +from irdb.fileversions import IRDBFile @pytest.fixture(scope="module") @@ -166,3 +167,46 @@ def test_all_dat_files_readable(self, pkg_dir, badges): logging.warning(how_bad) assert not bad_files, bad_files + + @pytest.mark.xfail( + reason=("This (new) test shows many previously unknown, but not " + "critical inconsistencies, which would pollute the tests, so " + "xfail this for now, report is generated regardless.") + ) + @pytest.mark.usefixtures("pkg_dir", "badges") + def test_all_dat_files_consistent(self, pkg_dir, badges): + bad_files = [] + how_bad = {"file_read_error": [], + "value_error": [], + "unexpected_error": []} + # fns_dat = list(IRDBFile.from_folder(pkg_dir)) + fns_dat = pkg_dir.rglob("*.dat") + # TODO: the following assert now always passed because fns_dat is a + # generator object (while the check was likely meant to catch + # empty lists) + assert fns_dat + for fn_dat in fns_dat: + fn_loc = fn_dat.relative_to(pkg_dir) + try: + dat_file = IRDBFile.from_file(fn_dat) + except Exception as err: + logging.error("%s Error reading dat file %s", str(fn_loc), err) + # bad_files.append(str(fn_loc)) + how_bad["file_read_error"].append(str(fn_loc)) + + try: + dat_file.validate_dates() + except ValueError as err: + logging.error("%s ValeError %s", str(fn_loc), err) + bad_files.append(str(fn_loc)) + badges[f"!{fn_loc.parts[0]}.dates"][dat_file.name] = "conflict" + how_bad["value_error"].append(str(fn_loc)) + except Exception as err: + logging.error("%s Unexpected Exception %s %s", str(fn_loc), + err.__class__, err) + bad_files.append(str(fn_loc)) + badges[f"!{fn_loc.parts[0]}.dates"][dat_file.name] = "error" + how_bad["unexpected_error"].append(str(fn_loc)) + logging.warning(how_bad) + + assert not bad_files, bad_files From 1281c928cede4993d067b5bac218d336c3930610 Mon Sep 17 00:00:00 2001 From: teutoburg Date: Mon, 31 Jul 2023 18:56:27 +0200 Subject: [PATCH 17/23] pylinted and minor fixes --- irdb/badges.py | 19 +++++++++-------- irdb/fileversions.py | 33 +++++++++++++---------------- irdb/tests/test_package_contents.py | 8 +++---- 3 files changed, 29 insertions(+), 31 deletions(-) diff --git a/irdb/badges.py b/irdb/badges.py index db5bc823..3a42df89 100644 --- a/irdb/badges.py +++ b/irdb/badges.py @@ -47,7 +47,7 @@ def __new__(cls, key: str, value): def __init__(self, key: str, value): self.key = _fix_badge_str(key) - self.value = _fix_badge_str(value) + self.value = _fix_badge_str(value) if isinstance(value, str) else value def write(self, stream: TextIO) -> None: """Write formatted pattern to I/O stream""" @@ -69,16 +69,17 @@ class NumBadge(Badge): class StrBadge(Badge): special_strings = { - "observation" : "blueviolet", - "support" : "deepskyblue", - "error" : "red", - "missing" : "red", - "warning" : "orange", - "conflict" : "orange", - "incomplete" : "orange", + "observation": "blueviolet", + "support": "deepskyblue", + "error": "red", + "missing": "red", + "warning": "orange", + "conflict": "orange", + "incomplete": "orange", "ok": "green", "found": "green", - "not found": "red", + "not_found": "red", + "none": "yellowgreen", } def __init__(self, key: str, value: str): diff --git a/irdb/fileversions.py b/irdb/fileversions.py index 7b3a53bd..6769c486 100644 --- a/irdb/fileversions.py +++ b/irdb/fileversions.py @@ -6,17 +6,18 @@ """ # import sys -import logging -import yaml +# import logging # from typing import TextIO # from io import StringIO import datetime as dt from pathlib import Path from dataclasses import dataclass -from astropy.table import Table +import yaml + +# from astropy.table import Table from astropy.io import ascii as ioascii -import nbformat as nbf +# import nbformat as nbf @dataclass(order=True) @@ -24,7 +25,7 @@ class FileChange(): date: dt.date author: str comment: str = "" - + @dataclass class IRDBFile(): @@ -47,13 +48,13 @@ def from_file(cls, file): except KeyError: chgs = None return cls(file.name, - meta["date_created"], + meta.get("date_created", None), meta.get("date_modified", None), chgs) @classmethod def from_folder(cls, folder): - for file in folder.glob("*.dat"): + for file in folder.rglob("*.dat"): yield cls.from_file(file) @staticmethod @@ -80,13 +81,13 @@ def validate_dates(self) -> None: if date_modified is not None: if self.changes is None: raise ValueError("Modified date set but no changes listed.") - if not date_modified >= date_created: + if date_modified < date_created: msg = f"{date_modified=!s} earlier than {date_created=!s}" raise ValueError(msg) if __name__ == "__main__": files = list(IRDBFile.from_folder(Path("../"))) - + db = {"Data files": {}} for f in files: try: @@ -95,24 +96,20 @@ def validate_dates(self) -> None: except ValueError as err: msg = str(err).replace(" ", "_").replace("-", "--") db["Data files"][f.name] = {msg: "error"} - - from irdb import utils - utils.write_badge_yaml(db, "test.yaml") - utils.make_badge_report("test.yaml", "test.md") - + # colnames = ["File", "Last modification"] # data = [[f.name for f in files], [f.date_modified for f in files]] # tbl = Table(names=colnames, data=data, copy=False) - + # nb = nbf.v4.new_notebook() # text = """\ # # My first automatic Jupyter Notebook # This is an auto-generated notebook.""" - - + + # nb["cells"] = [nbf.v4.new_markdown_cell(text), # nbf.v4.new_markdown_cell(tbl.show_in_notebook().data)] # fname = "test.ipynb" - + # with open(fname, "w") as f: # nbf.write(nb, f) diff --git a/irdb/tests/test_package_contents.py b/irdb/tests/test_package_contents.py index 242efcd0..fd6e045e 100644 --- a/irdb/tests/test_package_contents.py +++ b/irdb/tests/test_package_contents.py @@ -14,13 +14,13 @@ from irdb.fileversions import IRDBFile -@pytest.fixture(scope="module") -def pkg_dir(): +@pytest.fixture(name="pkg_dir", scope="module") +def fixture_pkg_dir(): return Path(__file__).parent.parent.parent -@pytest.fixture(scope="module") -def badges(): +@pytest.fixture(name="badges", scope="module") +def fixture_badges(): with BadgeReport() as report: yield report From 88b42209b0d15d281f841dde886a39077a514fba Mon Sep 17 00:00:00 2001 From: teutoburg Date: Mon, 31 Jul 2023 23:05:36 +0200 Subject: [PATCH 18/23] Get all packages, even without self-named yaml --- irdb/utils.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/irdb/utils.py b/irdb/utils.py index 1b0f4c24..a0984d75 100644 --- a/irdb/utils.py +++ b/irdb/utils.py @@ -19,7 +19,15 @@ def get_packages(): """ # TODO: update docstring for generator for pkg_path in PKG_DIR.iterdir(): - if (pkg_path / f"{pkg_path.name}.yaml").exists(): + specials = {"irdb", "docs"} + # NOTE: Previously, only folders with a self-named yaml file were + # considered packages by this function. This caused some packages + # to 'slip under the radar' by the tests, and also defeated the + # purpose of test_all_packages_have_a_self_named_yaml. + # if (pkg_path / f"{pkg_path.name}.yaml").exists(): + if (pkg_path.is_dir() + and not pkg_path.name.startswith((".", "_")) + and not pkg_path.name in specials): yield pkg_path.name, pkg_path From dd157b57ed32cc0f5e0da75c2ac0fa7392a547f0 Mon Sep 17 00:00:00 2001 From: teutoburg Date: Mon, 31 Jul 2023 23:06:01 +0200 Subject: [PATCH 19/23] Update tests, fix and capture relevant logging, parametrize and fixture on classes. --- irdb/tests/test_badges.py | 129 ++++++++++++++++++++++++++++ irdb/tests/test_package_contents.py | 76 ++++++++++------ irdb/tests/test_utils.py | 61 ++++++------- 3 files changed, 205 insertions(+), 61 deletions(-) create mode 100644 irdb/tests/test_badges.py diff --git a/irdb/tests/test_badges.py b/irdb/tests/test_badges.py new file mode 100644 index 00000000..0b422737 --- /dev/null +++ b/irdb/tests/test_badges.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Tests for irdb.badges +""" + +from io import StringIO +from unittest import mock + +import yaml +import pytest + +from irdb.badges import BadgeReport, Badge, BoolBadge, NumBadge, StrBadge, \ + MsgOnlyBadge +from irdb.system_dict import SystemDict + + +@pytest.fixture(name="temp_dir", scope="module") +def fixture_temp_dir(tmp_path_factory): + tmpdir = tmp_path_factory.mktemp("PKG_DIR") + (tmpdir / "_REPORTS").mkdir() + return tmpdir + + +class TestBadgeSubclasses: + def test_bool(self): + assert isinstance(Badge("bogus", True), BoolBadge) + assert isinstance(Badge("bogus", False), BoolBadge) + + def test_num(self): + assert isinstance(Badge("bogus", 7), NumBadge) + assert isinstance(Badge("bogus", 3.14), NumBadge) + + def test_str(self): + assert isinstance(Badge("bogus", "foo"), StrBadge) + + def test_msgonly(self): + assert isinstance(Badge("bogus", "!foo"), MsgOnlyBadge) + + +class TestColours: + @pytest.mark.parametrize("value, colour", [ + ("observation", "blueviolet"), + ("support", "deepskyblue"), + ("error", "red"), + ("missing", "red"), + ("warning", "orange"), + ("conflict", "orange"), + ("incomplete", "orange"), + ("ok", "green"), + ("found", "green"), + ("not_found", "red"), + ("none", "yellowgreen"), + ]) + def test_special_strings(self, value, colour): + assert Badge("bogus", value).colour == colour + + def test_bool(self): + assert Badge("bogus", True).colour == "green" + assert Badge("bogus", False).colour == "red" + + def test_num(self): + assert Badge("bogus", 7).colour == "lightblue" + + +class TestPattern: + def test_simple(self): + with StringIO() as str_stream: + Badge("bogus", "Error").write(str_stream) + pattern = "[![](https://img.shields.io/badge/bogus-Error-red)]()" + assert pattern in str_stream.getvalue() + + def test_msg_only(self): + with StringIO() as str_stream: + Badge("bogus", "!OK").write(str_stream) + pattern = "[![](https://img.shields.io/badge/bogus-green)]()" + assert pattern in str_stream.getvalue() + + +class TestSpecialChars: + def test_space(self): + badge = Badge("bogus foo", "bar baz") + assert badge.key == "bogus_foo" + assert badge.value == "bar_baz" + + def test_dash(self): + badge = Badge("bogus-foo", "bar-baz") + assert badge.key == "bogus--foo" + assert badge.value == "bar--baz" + + +class TestReport: + # TODO: the repeated setup stuff should be a fixture or something I guess + + @pytest.mark.usefixtures("temp_dir") + def test_writes_yaml(self, temp_dir): + with mock.patch("irdb.badges.PKG_DIR", temp_dir): + with BadgeReport("test.yaml", "test.md") as report: + report["!foo.bar"] = "bogus" + assert (temp_dir / "_REPORTS/test.yaml").exists() + + @pytest.mark.usefixtures("temp_dir") + def test_writes_md(self, temp_dir): + with mock.patch("irdb.badges.PKG_DIR", temp_dir): + with BadgeReport("test.yaml", "test.md") as report: + report["!foo.bar"] = "bogus" + assert (temp_dir / "_REPORTS/test.md").exists() + + @pytest.mark.usefixtures("temp_dir") + def test_yaml_content(self, temp_dir): + with mock.patch("irdb.badges.PKG_DIR", temp_dir): + with BadgeReport("test.yaml", "test.md") as report: + report["!foo.bar"] = "bogus" + path = temp_dir / "_REPORTS/test.yaml" + with path.open(encoding="utf-8") as file: + dic = SystemDict(yaml.full_load(file)) + assert "!foo.bar" in dic + assert dic["!foo.bar"] == "bogus" + + @pytest.mark.usefixtures("temp_dir") + def test_md_content(self, temp_dir): + with mock.patch("irdb.badges.PKG_DIR", temp_dir): + with BadgeReport("test.yaml", "test.md") as report: + report["!foo.bar"] = "bogus" + path = temp_dir / "_REPORTS/test.md" + markdown = path.read_text(encoding="utf-8") + assert "## foo" in markdown + badge = "[![](https://img.shields.io/badge/bar-bogus-lightgrey)]()" + assert badge in markdown diff --git a/irdb/tests/test_package_contents.py b/irdb/tests/test_package_contents.py index fd6e045e..c4546091 100644 --- a/irdb/tests/test_package_contents.py +++ b/irdb/tests/test_package_contents.py @@ -13,6 +13,18 @@ from irdb.badges import BadgeReport from irdb.fileversions import IRDBFile +# HACK: This is necessary because scopesim has import side effects that mess up +# logging here, specifically capture. Once that's solved, the following +# lines should be removed! +from importlib import reload +logging.shutdown() +reload(logging) + + +# TODO: some tests should be skipped if something else has failed. +# howto: https://stackoverflow.com/questions/75377691/skip-test-case-if-above-test-case-failed-pytest +# and: https://pytest-dependency.readthedocs.io/en/stable/advanced.html + @pytest.fixture(name="pkg_dir", scope="module") def fixture_pkg_dir(): @@ -25,9 +37,9 @@ def fixture_badges(): yield report +@pytest.mark.parametrize("package", list(get_packages())) +@pytest.mark.usefixtures("badges") class TestFileStructureOfPackages: - @pytest.mark.parametrize("package", list(get_packages())) - @pytest.mark.usefixtures("badges") def test_default_yaml_contains_packages_list(self, package, badges): pkg_name, pkg_path = package default_yaml = pkg_path / "default.yaml" @@ -48,10 +60,16 @@ def test_default_yaml_contains_packages_list(self, package, badges): badges[f"!{pkg_name}.structure.default_yaml"] = "incomplete" assert result, pkg_name - @pytest.mark.parametrize("package", list(get_packages())) - @pytest.mark.usefixtures("badges") + @pytest.mark.xfail( + reason=("After updating get_packages(), some (almost) empty packages " + "are now recognised, which don't have a self named yaml. " + "This should be in the reports, but doesn't need to make the " + "tests fail overall.") + ) def test_all_packages_have_a_self_named_yaml(self, package, badges): - """This test can never fail. + """The following is *no longer true* after an update to get_packages(): + + This test can never fail. get_packages() decides whether a directory is a package based on whether it has a yaml file in it with the same name. @@ -67,12 +85,11 @@ def test_all_packages_have_a_self_named_yaml(self, package, badges): reason=("Most of the missing files seem to exist in other packages " "though, so they are probably okay.") ) - @pytest.mark.parametrize("package", list(get_packages())) - @pytest.mark.usefixtures("badges") def test_all_files_referenced_in_yamls_exist(self, package, badges): missing_files = [] pkg_name, pkg_path = package - for yaml_file in pkg_path.glob("*.yaml"): + yaml_files = list(pkg_path.glob("*.yaml")) + for yaml_file in yaml_files: with yaml_file.open(encoding="utf-8") as file: # An error here shouldn't pass silently. If this regularly # produces [the same] error(s), *that* error(s) should be @@ -103,16 +120,17 @@ def test_all_files_referenced_in_yamls_exist(self, package, badges): # missing_files.append(str(full_fname)) missing_files.append(fn) - if not missing_files: + if not yaml_files: + badges[f"!{pkg_name}.structure.no_files_referenced"] = "!NONE" + elif yaml_files and not missing_files: badges[f"!{pkg_name}.structure.no_missing_files"] = "!OK" assert not missing_files, f"{pkg_name}: {missing_files=}" - @pytest.mark.parametrize("package", list(get_packages())) - @pytest.mark.usefixtures("badges") def test_all_yaml_files_readable(self, package, badges): yamls_bad = [] pkg_name, pkg_path = package - for yaml_file in pkg_path.glob("*.yaml"): + yaml_files = list(pkg_path.glob("*.yaml")) + for yaml_file in yaml_files: with yaml_file.open(encoding="utf-8") as file: try: _ = list(yaml.full_load_all(file)) @@ -124,24 +142,27 @@ def test_all_yaml_files_readable(self, package, badges): yamls_bad.append(str(yaml_file)) badges[f"!{pkg_name}.contents"][yaml_file.name] = "error" - if not yamls_bad: + if not yaml_files: + badges[f"!{pkg_name}.contents.no_yaml_files"] = "!NONE" + elif yaml_files and not yamls_bad: badges[f"!{pkg_name}.contents.all_yamls_readable"] = "!OK" assert not yamls_bad, f"Errors in {pkg_name} yaml files: {yamls_bad}" + +@pytest.mark.parametrize("package", list(get_packages())) +@pytest.mark.usefixtures("pkg_dir", "badges") +class TestPackageDatFiles: @pytest.mark.xfail( reason=("Due to bad globbing, files in subfolders were not checked " "previously, some of those now fail. idk y tho") ) - @pytest.mark.usefixtures("pkg_dir", "badges") - def test_all_dat_files_readable(self, pkg_dir, badges): + def test_all_dat_files_readable(self, package, pkg_dir, badges, caplog): bad_files = [] how_bad = {"inconsistent_table_error": [], "value_error": [], "unexpected_error": []} - fns_dat = pkg_dir.rglob("*.dat") - # TODO: the following assert now always passed because fns_dat is a - # generator object (while the check was likely meant to catch - # empty lists) + pkg_name, pkg_path = package + fns_dat = list(pkg_path.rglob("*.dat")) assert fns_dat for fn_dat in fns_dat: fn_loc = fn_dat.relative_to(pkg_dir) @@ -164,7 +185,9 @@ def test_all_dat_files_readable(self, pkg_dir, badges): bad_files.append(str(fn_loc)) badges[f"!{fn_loc.parts[0]}.contents"][fn_loc.name] = "error" how_bad["unexpected_error"].append(str(fn_loc)) - logging.warning(how_bad) + if any(how_bad.values()): + logging.info(how_bad) + badges.logs.extend(caplog.records) assert not bad_files, bad_files @@ -173,17 +196,14 @@ def test_all_dat_files_readable(self, pkg_dir, badges): "critical inconsistencies, which would pollute the tests, so " "xfail this for now, report is generated regardless.") ) - @pytest.mark.usefixtures("pkg_dir", "badges") - def test_all_dat_files_consistent(self, pkg_dir, badges): + def test_all_dat_files_consistent(self, package, pkg_dir, badges, caplog): bad_files = [] how_bad = {"file_read_error": [], "value_error": [], "unexpected_error": []} # fns_dat = list(IRDBFile.from_folder(pkg_dir)) - fns_dat = pkg_dir.rglob("*.dat") - # TODO: the following assert now always passed because fns_dat is a - # generator object (while the check was likely meant to catch - # empty lists) + pkg_name, pkg_path = package + fns_dat = list(pkg_path.rglob("*.dat")) assert fns_dat for fn_dat in fns_dat: fn_loc = fn_dat.relative_to(pkg_dir) @@ -207,6 +227,8 @@ def test_all_dat_files_consistent(self, pkg_dir, badges): bad_files.append(str(fn_loc)) badges[f"!{fn_loc.parts[0]}.dates"][dat_file.name] = "error" how_bad["unexpected_error"].append(str(fn_loc)) - logging.warning(how_bad) + if any(how_bad.values()): + logging.info(how_bad) + badges.logs.extend(caplog.records) assert not bad_files, bad_files diff --git a/irdb/tests/test_utils.py b/irdb/tests/test_utils.py index be89ed76..3057a735 100644 --- a/irdb/tests/test_utils.py +++ b/irdb/tests/test_utils.py @@ -1,45 +1,38 @@ -import os -from os import path as pth +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Tests for irdb.utils -from irdb.utils import load_badge_yaml, write_badge_yaml, make_badge_report -from irdb.system_dict import SystemDict +Does currently not test utils.recursive_filename_search() +""" +from pathlib import Path -class TestMakeBadgeReport: - """ - Run this to make a Badge report +import pytest - Run test_package_contents before running this test - """ - def test_reads_yaml_correctly(self): - # if not pth.exists("../_REPORTS/badges.yaml"): - # with open(pth.join(pth.dirname(__file__), - # "../_REPORTS/badges.yaml"), "w") as f: - # f.write("") +from irdb.utils import get_packages - print(make_badge_report()) +@pytest.fixture(name="packages", scope="class") +def fixture_packages(): + return dict(get_packages()) -class TestLoadBadgeYaml: - def test_reads_in_badge_yaml(self): - badges = load_badge_yaml() - assert isinstance(badges, SystemDict) - assert len(badges.dic) > 0 +class TestGetPackages: + @pytest.mark.usefixtures("packages") + def test_includes_various_packages(self, packages): + wanted = {"Armazones", "ELT", "METIS", "MICADO", "test_package"} + assert all(pkg_name in packages.keys() for pkg_name in wanted) - print(badges) + @pytest.mark.usefixtures("packages") + def test_doesnt_includes_specials(self, packages): + wanted = {"irdb", "docs", "_REPORTS", ".github"} + assert all(pkg_name not in packages.keys() for pkg_name in wanted) + @pytest.mark.usefixtures("packages") + def test_values_are_path_objects(self, packages): + assert isinstance(packages["test_package"], Path) -class TestWriteBadgeYaml: - def test_write_badge_yaml_contents_to_file(self): - fname = "new_badges.yaml" - badges = load_badge_yaml() - write_badge_yaml(badges, fname) - new_badges = load_badge_yaml(fname) - - for key in badges.dic: - assert badges[key] == new_badges[key] - - dname = pth.abspath(pth.join(pth.dirname(__file__), - "../", "../", "_REPORTS")) - os.remove(pth.join(dname, fname)) + @pytest.mark.usefixtures("packages") + def test_only_includes_dirs(self, packages): + assert all(path.is_dir() for path in packages.values()) From 0d8e68f9d2702552d2a4dcadc5af1c353b1275ff Mon Sep 17 00:00:00 2001 From: teutoburg Date: Tue, 1 Aug 2023 23:01:28 +0200 Subject: [PATCH 20/23] Add logs and report preamble --- irdb/badges.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/irdb/badges.py b/irdb/badges.py index 3a42df89..b5d5af56 100644 --- a/irdb/badges.py +++ b/irdb/badges.py @@ -15,6 +15,7 @@ # from io import StringIO from numbers import Number from string import Template +from datetime import datetime as dt from collections.abc import Mapping import yaml @@ -98,12 +99,21 @@ def __init__(self, key: str, value: str): class BadgeReport(SystemDict): - def __init__(self, filename=None, report_filename=None): + def __init__(self, filename=None, report_filename=None, logs_filename=None, + save_logs=True): logging.debug("REPORT INIT") + base_path = Path(PKG_DIR, "_REPORTS") + self.filename = filename or "badges.yaml" - self.yamlpath = Path(PKG_DIR, "_REPORTS", self.filename) + self.yamlpath = base_path / self.filename self.report_name = report_filename or "badges.md" - self.report_path = Path(PKG_DIR, "_REPORTS", self.report_name) + self.report_path = base_path / self.report_name + + self.save_logs = save_logs + self.logs = [] + self.logs_name = logs_filename or "badge_report_log.txt" + self.log_path = base_path / self.logs_name + super().__init__() def __enter__(self): @@ -119,14 +129,29 @@ def __exit__(self, exc_type, exc_value, exc_traceback): logging.debug("REPORT EXIT") self.write_yaml() self.generate_report() + if self.save_logs: + self.write_logs() logging.debug("REPORT DONE") + def write_logs(self) -> None: + with self.log_path.open("w", encoding="utf-8") as file: + for log in self.logs: + file.write(f"{log.levelname}::{log.message}\n") + def write_yaml(self) -> None: dumpstr = yaml.dump(self.dic, sort_keys=False) self.yamlpath.write_text(dumpstr, encoding="utf-8") + def _make_preamble(self) -> str: + preamble = ("# IRDB Packages Report\n\n" + f"**Created on UTC {dt.utcnow():%Y-%m-%d %H:%M:%S}**\n\n" + "For details on errors and conflicts, see badge report " + "log file in this directory.\n\n") + return preamble + def generate_report(self) -> None: with self.report_path.open("w", encoding="utf-8") as file: + file.write(self._make_preamble()) make_entries(file, self.dic) From 8848a3eebaaadca354d42b810b86ace8739d99d2 Mon Sep 17 00:00:00 2001 From: teutoburg Date: Tue, 1 Aug 2023 23:05:11 +0200 Subject: [PATCH 21/23] Update gitignore to whitelist badge report log --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 27f41b05..9fdc88e9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,10 @@ _Legacy_packages/ *ezphp.exe to_be_processed/ _TO_BE_PROCESSED/ -_REPORTS/ +_REPORTS/* +!badge_report_log.txt +!badges.yaml +!badges.md .ipynb_checkpoints/ *TEST.fits From d73ebeed6de24f088793d17f900d80ad9521adbf Mon Sep 17 00:00:00 2001 From: teutoburg Date: Mon, 31 Jul 2023 18:57:12 +0200 Subject: [PATCH 22/23] Newly created badges files --- _REPORTS/badge_report_log.txt | 178 +++++++++++++ _REPORTS/badges.md | 481 ++++++++++++++++++++++------------ _REPORTS/badges.yaml | 353 ++++++++++++++++++------- 3 files changed, 747 insertions(+), 265 deletions(-) create mode 100644 _REPORTS/badge_report_log.txt diff --git a/_REPORTS/badge_report_log.txt b/_REPORTS/badge_report_log.txt new file mode 100644 index 00000000..31addf31 --- /dev/null +++ b/_REPORTS/badge_report_log.txt @@ -0,0 +1,178 @@ +WARNING::Couldn't convert .meta['comments'] to dict +ERROR::LFOA\filters\old_filters\TC_filter_Halpha_wide_old.dat ValeError dictionary update sequence element #0 has length 49; 2 is required +ERROR::NACO\filters\NB_3.74.dat ValeError chr() arg not in range(0x110000) +WARNING::Couldn't convert
.meta['comments'] to dict +ERROR::NACO\filters\NB_4.05.dat ValeError dictionary update sequence element #0 has length 85; 2 is required +ERROR::OSIRIS\traces\TRACE_R1000B.dat ValeError dictionary update sequence element #0 has length 1; 2 is required +ERROR::OSIRIS\traces\TRACE_R1000R.dat ValeError dictionary update sequence element #0 has length 1; 2 is required +ERROR::OSIRIS\traces\TRACE_R2000B.dat ValeError dictionary update sequence element #0 has length 1; 2 is required +ERROR::OSIRIS\traces\TRACE_R2500I.dat ValeError dictionary update sequence element #0 has length 1; 2 is required +ERROR::OSIRIS\traces\TRACE_R2500R.dat ValeError dictionary update sequence element #0 has length 1; 2 is required +ERROR::OSIRIS\traces\TRACE_R2500U.dat ValeError dictionary update sequence element #0 has length 1; 2 is required +ERROR::OSIRIS\traces\TRACE_R2500V.dat ValeError dictionary update sequence element #0 has length 1; 2 is required +ERROR::OSIRIS\traces\TRACE_R300B.dat ValeError dictionary update sequence element #0 has length 1; 2 is required +ERROR::OSIRIS\traces\TRACE_R300R.dat ValeError dictionary update sequence element #0 has length 1; 2 is required +ERROR::OSIRIS\traces\TRACE_R500B.dat ValeError dictionary update sequence element #0 has length 1; 2 is required +ERROR::OSIRIS\traces\TRACE_R500R.dat ValeError dictionary update sequence element #0 has length 1; 2 is required +ERROR::Armazones\TER_armazones_default_NIR_IMG.dat ValeError date_last_change=2020-10-29 not equal to date_modified=2019-08-09 +ERROR::ELT\TER_ELT_5_mirror_clean.dat ValeError date_last_change=2020-08-17 not equal to date_modified=2019-06-11 +ERROR::ELT\TER_ELT_mirror_aluminium.dat ValeError date_last_change=2020-08-25 not equal to date_modified=2018-11-19 +ERROR::ELT\TER_ELT_mirror_mgf2agal.dat ValeError date_last_change=2020-08-25 not equal to date_modified=2018-11-19 +ERROR::GTC\unity.dat ValeError Modified date set but no changes listed. +ERROR::HAWKI\FPA_hawki_layout.dat ValeError date_last_change=2018-11-19 not equal to date_modified=2019-08-13 +ERROR::HAWKI\LIST_HAWKI_mirrors.dat ValeError date_last_change=2020-10-06 not equal to date_modified=2019-08-13 +ERROR::HAWKI\TER_mirror_gold.dat ValeError date_last_change=2020-10-06 not equal to date_modified=2019-01-28 +ERROR::HAWKI\TER_window.dat ValeError date_last_change=2020-10-06 not equal to date_modified=2018-11-19 +ERROR::HAWKI\filters\TC_filter_BrGamma.dat ValeError date_last_change=2019-08-13 not equal to date_modified=2018-03-21 +ERROR::HAWKI\filters\TC_filter_CH4.dat ValeError date_last_change=2019-08-13 not equal to date_modified=2018-03-21 +ERROR::HAWKI\filters\TC_filter_H.dat ValeError date_last_change=2019-08-13 not equal to date_modified=2018-03-21 +ERROR::HAWKI\filters\TC_filter_H2.dat ValeError date_last_change=2019-08-13 not equal to date_modified=2018-03-21 +ERROR::HAWKI\filters\TC_filter_J.dat ValeError date_last_change=2019-08-13 not equal to date_modified=2018-03-21 +ERROR::HAWKI\filters\TC_filter_Ks.dat ValeError date_last_change=2019-08-13 not equal to date_modified=2018-03-21 +ERROR::HAWKI\filters\TC_filter_NB1060.dat ValeError date_last_change=2019-08-13 not equal to date_modified=2018-03-21 +ERROR::HAWKI\filters\TC_filter_NB1190.dat ValeError date_last_change=2019-08-13 not equal to date_modified=2018-03-21 +ERROR::HAWKI\filters\TC_filter_NB2090.dat ValeError date_last_change=2019-08-13 not equal to date_modified=2018-03-21 +ERROR::HAWKI\filters\TC_filter_Y.dat ValeError date_last_change=2019-08-13 not equal to date_modified=2018-03-21 +ERROR::HST\TER_ELT_mirror_mgf2agal.dat ValeError date_last_change=2019-07-25 not equal to date_modified=2018-11-19 +ERROR::HST\wfc3_ir_primary_001_syn.dat ValeError Modified date set but no changes listed. +ERROR::HST\wfc3_ir_secondary_001_syn.dat ValeError Modified date set but no changes listed. +ERROR::LFOA\TER_focal_reducer.dat ValeError Modified date set but no changes listed. +ERROR::LFOA\TER_mirror_aluminium.dat ValeError date_last_change=2019-07-25 not equal to date_modified=2018-11-19 +ERROR::LFOA\filters\old_filters\TC_filter_Halpha_wide_old.dat Error reading dat file expected '', but found '' + in "", line 2, column 1: + wavelength_unit : nm + ^ +ERROR::METIS\FPA_linearity_GeoSnap_low_capacity.dat Error reading dat file 'NoneType' object is not iterable +ERROR::METIS\FPA_linearity_HxRG.dat Error reading dat file 'NoneType' object is not iterable +ERROR::METIS\FPA_metis_img_lm_layout.dat ValeError date_last_change=2022-04-22 not equal to date_modified=2020-02-04 +ERROR::METIS\FPA_metis_img_nq_aquarius_layout.dat ValeError date_last_change=2022-04-22 not equal to date_modified=2022-04-26 +ERROR::METIS\FPA_metis_img_n_geosnap_layout.dat ValeError date_last_change=2022-02-26 not equal to date_modified=2022-04-26 +ERROR::METIS\LIST_ifu_apertures.dat Error reading dat file month must be in 1..12 +ERROR::METIS\LIST_METIS_mirrors_img_n.dat Error reading dat file 'NoneType' object is not iterable +ERROR::METIS\QE_detector_geosnap.dat ValeError date_last_change=2020-07-23 not equal to date_modified=2020-07-14 +ERROR::METIS\QE_detector_H2RG_METIS.dat ValeError date_last_change=2020-07-23 not equal to date_modified=2020-07-14 +ERROR::METIS\TER_ADC_const_90.dat Error reading dat file 'NoneType' object is not iterable +ERROR::METIS\TER_ADC_const_90.dat ValeError date_last_change=2020-07-23 not equal to date_modified=2020-07-14 +ERROR::METIS\TER_img_dichroic.dat ValeError Modified date set but no changes listed. +ERROR::METIS\TER_lms_grism_max.dat Error reading dat file 'NoneType' object is not iterable +ERROR::METIS\TER_lms_grism_max.dat ValeError Modified date set but no changes listed. +ERROR::METIS\TER_lms_grism_min.dat Error reading dat file 'NoneType' object is not iterable +ERROR::METIS\TER_lms_grism_min.dat ValeError Modified date set but no changes listed. +ERROR::METIS\TER_mirror_gold.dat ValeError Modified date set but no changes listed. +ERROR::METIS\filters\TC_filter_Br_alpha.dat Error reading dat file 'dict' object has no attribute 'split' +ERROR::METIS\filters\TC_filter_Br_alpha_ref.dat Error reading dat file 'dict' object has no attribute 'split' +ERROR::METIS\filters\TC_filter_CO_1-0_ice.dat Error reading dat file 'dict' object has no attribute 'split' +ERROR::METIS\filters\TC_filter_CO_ref.dat Error reading dat file 'dict' object has no attribute 'split' +ERROR::METIS\filters\TC_filter_H2O-ice.dat Error reading dat file 'dict' object has no attribute 'split' +ERROR::METIS\filters\TC_filter_HCI_L_long.dat Error reading dat file 'NoneType' object is not iterable +ERROR::METIS\filters\TC_filter_HCI_L_short.dat Error reading dat file 'NoneType' object is not iterable +ERROR::METIS\filters\TC_filter_HCI_M.dat Error reading dat file 'NoneType' object is not iterable +ERROR::METIS\filters\TC_filter_IB_4.05.dat Error reading dat file 'dict' object has no attribute 'split' +ERROR::METIS\filters\TC_filter_L.dat Error reading dat file 'dict' object has no attribute 'split' +ERROR::METIS\filters\TC_filter_Lp.dat Error reading dat file 'dict' object has no attribute 'split' +ERROR::METIS\filters\TC_filter_L_spec.dat Error reading dat file 'NoneType' object is not iterable +ERROR::METIS\filters\TC_filter_Mp.dat Error reading dat file 'dict' object has no attribute 'split' +ERROR::METIS\filters\TC_filter_M_spec.dat Error reading dat file 'NoneType' object is not iterable +ERROR::METIS\filters\TC_filter_N1.dat Error reading dat file 'dict' object has no attribute 'split' +ERROR::METIS\filters\TC_filter_N2.dat Error reading dat file 'dict' object has no attribute 'split' +ERROR::METIS\filters\TC_filter_N3.dat Error reading dat file 'dict' object has no attribute 'split' +ERROR::METIS\filters\TC_filter_ND_OD1.dat Error reading dat file 'NoneType' object is not iterable +ERROR::METIS\filters\TC_filter_ND_OD2.dat Error reading dat file 'NoneType' object is not iterable +ERROR::METIS\filters\TC_filter_ND_OD3.dat Error reading dat file 'NoneType' object is not iterable +ERROR::METIS\filters\TC_filter_ND_OD4.dat Error reading dat file 'NoneType' object is not iterable +ERROR::METIS\filters\TC_filter_ND_OD5.dat Error reading dat file 'NoneType' object is not iterable +ERROR::METIS\filters\TC_filter_Ne_II.dat Error reading dat file 'dict' object has no attribute 'split' +ERROR::METIS\filters\TC_filter_Ne_II_ref.dat Error reading dat file 'dict' object has no attribute 'split' +ERROR::METIS\filters\TC_filter_N_spec.dat Error reading dat file 'NoneType' object is not iterable +ERROR::METIS\filters\TC_filter_PAH_11.25.dat Error reading dat file 'dict' object has no attribute 'split' +ERROR::METIS\filters\TC_filter_PAH_11.25_ref.dat Error reading dat file 'dict' object has no attribute 'split' +ERROR::METIS\filters\TC_filter_PAH_3.3.dat Error reading dat file 'dict' object has no attribute 'split' +ERROR::METIS\filters\TC_filter_PAH_3.3_ref.dat Error reading dat file 'dict' object has no attribute 'split' +ERROR::METIS\filters\TC_filter_PAH_8.6.dat Error reading dat file 'dict' object has no attribute 'split' +ERROR::METIS\filters\TC_filter_PAH_8.6_ref.dat Error reading dat file 'dict' object has no attribute 'split' +ERROR::METIS\filters\TC_filter_Q1.dat Error reading dat file 'dict' object has no attribute 'split' +ERROR::METIS\filters\TC_filter_short-L.dat Error reading dat file 'dict' object has no attribute 'split' +ERROR::METIS\filters\TC_filter_S_IV.dat Error reading dat file 'dict' object has no attribute 'split' +ERROR::METIS\filters\TC_filter_S_IV_ref.dat Error reading dat file 'dict' object has no attribute 'split' +ERROR::MICADO\FPA_linearity.dat ValeError date_last_change=2022-03-17 not equal to date_modified=2018-11-19 +ERROR::MICADO\LIST_RO_SCAO_mirrors.dat ValeError date_last_change=2020-07-18 not equal to date_modified=2020-08-17 +ERROR::MICADO\MASK_slit_3000x20.dat ValeError date_last_change=2020-03-24 not equal to date_modified=2019-07-10 +ERROR::MICADO\MASK_slit_3000x50.dat ValeError date_last_change=2020-03-24 not equal to date_modified=2019-07-10 +ERROR::MICADO\TER_entrance_window.dat ValeError Changes listed but no modified date set. +ERROR::MICADO\TER_MICADO_mirror_mgf2agal.dat ValeError date_last_change=2019-07-25 not equal to date_modified=2018-11-19 +ERROR::MICADO\TER_mirror_gold.dat ValeError date_last_change=2023-07-13 not equal to date_modified=2023-07-23 +ERROR::MICADO\TER_SCAO_dichroic.dat ValeError date_last_change=2023-07-04 not equal to date_modified=2023-06-13 +ERROR::MICADO\filters\TC_filter_blank.dat ValeError Modified date set but no changes listed. +ERROR::MICADO\filters\TC_filter_block.dat ValeError Modified date set but no changes listed. +ERROR::MICADO\filters\TC_filter_Ks2.dat ValeError Modified date set but no changes listed. +ERROR::MICADO\filters\TC_filter_ND1.dat ValeError Modified date set but no changes listed. +ERROR::MICADO\filters\TC_filter_ND3.dat ValeError Modified date set but no changes listed. +ERROR::MICADO\filters\TC_filter_open.dat ValeError Modified date set but no changes listed. +ERROR::MICADO\filters\TC_filter_stop_IJH.dat ValeError Modified date set but no changes listed. +ERROR::MICADO\filters\TC_filter_stop_K.dat ValeError Modified date set but no changes listed. +ERROR::MICADO_Sci\TER_MICADO_IMG_common.dat ValeError Modified date set but no changes listed. +ERROR::MICADO_Sci\TER_MICADO_RO.dat ValeError Modified date set but no changes listed. +ERROR::MICADO_Sci\TER_MORFEO_MMS.dat ValeError Modified date set but no changes listed. +ERROR::MORFEO\TER_mirror_silver.dat ValeError date_last_change=2020-06-23 not equal to date_modified=2020-06-22 +ERROR::MORFEO\TER_MORFEO_entrance_window.dat Unexpected Exception '<' not supported between instances of 'datetime.date' and 'int' +ERROR::MORFEO\TER_MORFEO_mirror_aluminium.dat ValeError date_last_change=2019-07-25 not equal to date_modified=2018-11-19 +ERROR::MORFEO\TER_MORFEO_mirror_mgf2agal.dat ValeError date_last_change=2020-08-25 not equal to date_modified=2018-11-19 +ERROR::OSIRIS\QE_OSIRIS_new_detector.dat ValeError date_last_change=2022-06-14 not equal to date_modified=2022-03-12 +ERROR::OSIRIS\QE_OSIRIS_old_detector_plus_M3.dat ValeError Modified date set but no changes listed. +ERROR::OSIRIS\unity.dat ValeError date_last_change=2022-03-02 not equal to date_modified=22022-03-02 +ERROR::OSIRIS\code\OSIRIS_stitchedArc.dat Error reading dat file 'comments' +ERROR::OSIRIS\code\OSIRIS_stitchedArc.dat ValeError date_last_change=2022-03-02 not equal to date_modified=22022-03-02 +ERROR::OSIRIS\tests\test_data\fg191b2b.dat Error reading dat file 'comments' +ERROR::OSIRIS\tests\test_data\fg191b2b.dat ValeError date_last_change=2022-03-02 not equal to date_modified=22022-03-02 +ERROR::OSIRIS\tests\test_data\OSIRIS_stitchedArc.dat Error reading dat file 'comments' +ERROR::OSIRIS\tests\test_data\OSIRIS_stitchedArc.dat ValeError date_last_change=2022-03-02 not equal to date_modified=22022-03-02 +ERROR::OSIRIS\traces\TRACE_R1000B.dat Error reading dat file string indices must be integers +ERROR::OSIRIS\traces\TRACE_R1000B.dat ValeError date_last_change=2022-03-02 not equal to date_modified=22022-03-02 +ERROR::OSIRIS\traces\TRACE_R1000R.dat Error reading dat file string indices must be integers +ERROR::OSIRIS\traces\TRACE_R1000R.dat ValeError date_last_change=2022-03-02 not equal to date_modified=22022-03-02 +ERROR::OSIRIS\traces\TRACE_R2000B.dat Error reading dat file string indices must be integers +ERROR::OSIRIS\traces\TRACE_R2000B.dat ValeError date_last_change=2022-03-02 not equal to date_modified=22022-03-02 +ERROR::OSIRIS\traces\TRACE_R2500I.dat Error reading dat file string indices must be integers +ERROR::OSIRIS\traces\TRACE_R2500I.dat ValeError date_last_change=2022-03-02 not equal to date_modified=22022-03-02 +ERROR::OSIRIS\traces\TRACE_R2500R.dat Error reading dat file string indices must be integers +ERROR::OSIRIS\traces\TRACE_R2500R.dat ValeError date_last_change=2022-03-02 not equal to date_modified=22022-03-02 +ERROR::OSIRIS\traces\TRACE_R2500U.dat Error reading dat file string indices must be integers +ERROR::OSIRIS\traces\TRACE_R2500U.dat ValeError date_last_change=2022-03-02 not equal to date_modified=22022-03-02 +ERROR::OSIRIS\traces\TRACE_R2500V.dat Error reading dat file string indices must be integers +ERROR::OSIRIS\traces\TRACE_R2500V.dat ValeError date_last_change=2022-03-02 not equal to date_modified=22022-03-02 +ERROR::OSIRIS\traces\TRACE_R300B.dat Error reading dat file string indices must be integers +ERROR::OSIRIS\traces\TRACE_R300B.dat ValeError date_last_change=2022-03-02 not equal to date_modified=22022-03-02 +ERROR::OSIRIS\traces\TRACE_R300R.dat Error reading dat file string indices must be integers +ERROR::OSIRIS\traces\TRACE_R300R.dat ValeError date_last_change=2022-03-02 not equal to date_modified=22022-03-02 +ERROR::OSIRIS\traces\TRACE_R500B.dat Error reading dat file string indices must be integers +ERROR::OSIRIS\traces\TRACE_R500B.dat ValeError date_last_change=2022-03-02 not equal to date_modified=22022-03-02 +ERROR::OSIRIS\traces\TRACE_R500R.dat Error reading dat file string indices must be integers +ERROR::OSIRIS\traces\TRACE_R500R.dat ValeError date_last_change=2022-03-02 not equal to date_modified=22022-03-02 +ERROR::test_package\TC_filter_Ks.dat ValeError date_last_change=2019-11-09 not equal to date_modified=2018-01-28 +ERROR::VLT\LIST_VLT_mirrors.dat ValeError date_last_change=2020-10-06 not equal to date_modified=2019-08-13 +ERROR::VLT\TC_mirror_aluminium.dat ValeError date_last_change=2019-08-13 not equal to date_modified=2018-11-19 +ERROR::WFC3\TER_filter_F098M.dat ValeError Modified date set but no changes listed. +ERROR::WFC3\TER_filter_F105W.dat ValeError Modified date set but no changes listed. +ERROR::WFC3\TER_filter_F110W.dat ValeError Modified date set but no changes listed. +ERROR::WFC3\TER_filter_F125W.dat ValeError Modified date set but no changes listed. +ERROR::WFC3\TER_filter_F126N.dat ValeError Modified date set but no changes listed. +ERROR::WFC3\TER_filter_F127M.dat ValeError Modified date set but no changes listed. +ERROR::WFC3\TER_filter_F128N.dat ValeError Modified date set but no changes listed. +ERROR::WFC3\TER_filter_F130N.dat ValeError Modified date set but no changes listed. +ERROR::WFC3\TER_filter_F132N.dat ValeError Modified date set but no changes listed. +ERROR::WFC3\TER_filter_F139M.dat ValeError Modified date set but no changes listed. +ERROR::WFC3\TER_filter_F140W.dat ValeError Modified date set but no changes listed. +ERROR::WFC3\TER_filter_F153M.dat ValeError Modified date set but no changes listed. +ERROR::WFC3\TER_filter_F160W.dat ValeError Modified date set but no changes listed. +ERROR::WFC3\TER_filter_F164N.dat ValeError Modified date set but no changes listed. +ERROR::WFC3\TER_filter_F167N.dat ValeError Modified date set but no changes listed. +ERROR::WFC3\wfc3_ir_cor_004_syn.dat ValeError Modified date set but no changes listed. +ERROR::WFC3\wfc3_ir_csm_001_syn.dat ValeError Modified date set but no changes listed. +ERROR::WFC3\wfc3_ir_dn_002_syn.dat ValeError Modified date set but no changes listed. +ERROR::WFC3\wfc3_ir_fold_001_syn.dat ValeError Modified date set but no changes listed. +ERROR::WFC3\wfc3_ir_mask_001_syn.dat ValeError Modified date set but no changes listed. +ERROR::WFC3\wfc3_ir_mir1_001_syn.dat ValeError Modified date set but no changes listed. +ERROR::WFC3\wfc3_ir_mir2_001_syn.dat ValeError Modified date set but no changes listed. +ERROR::WFC3\wfc3_ir_qe_003_syn.dat ValeError Modified date set but no changes listed. +ERROR::WFC3\wfc3_ir_rcp_001_syn.dat ValeError Modified date set but no changes listed. +ERROR::WFC3\wfc3_ir_win_001_syn.dat ValeError Modified date set but no changes listed. diff --git a/_REPORTS/badges.md b/_REPORTS/badges.md index 989ecd08..3d06a0fe 100644 --- a/_REPORTS/badges.md +++ b/_REPORTS/badges.md @@ -1,164 +1,319 @@ +# IRDB Packages Report -## Armazones: - * contents: - * [![](https://img.shields.io/badge/all_yamls_readable-True-green)]() - * [![](https://img.shields.io/badge/package_type-support-blue)]() - * structure: - * [![](https://img.shields.io/badge/no_missing_files-True-green)]() - * [![](https://img.shields.io/badge/self_named_yaml-True-green)]() -## ELT: - * contents: - * [![](https://img.shields.io/badge/all_yamls_readable-True-green)]() - * [![](https://img.shields.io/badge/package_type-support-blue)]() - * structure: - * [![](https://img.shields.io/badge/no_missing_files-True-green)]() - * [![](https://img.shields.io/badge/self_named_yaml-True-green)]() -## GTC: - * contents: - * [![](https://img.shields.io/badge/all_yamls_readable-True-green)]() - * [![](https://img.shields.io/badge/package_type-support-blue)]() - * structure: - * [![](https://img.shields.io/badge/no_missing_files-True-green)]() - * [![](https://img.shields.io/badge/self_named_yaml-True-green)]() -## HAWKI: - * contents: - * [![](https://img.shields.io/badge/all_yamls_readable-True-green)]() - * [![](https://img.shields.io/badge/package_type-observation-blueviolet)]() - * structure: - * [![](https://img.shields.io/badge/default_yaml-True-green)]() - * [![](https://img.shields.io/badge/no_missing_files-True-green)]() - * [![](https://img.shields.io/badge/self_named_yaml-True-green)]() -## HST: - * contents: - * [![](https://img.shields.io/badge/all_yamls_readable-True-green)]() - * [![](https://img.shields.io/badge/package_type-support-blue)]() - * structure: - * [![](https://img.shields.io/badge/no_missing_files-True-green)]() - * [![](https://img.shields.io/badge/self_named_yaml-True-green)]() -## LFOA: - * contents: - * [![](https://img.shields.io/badge/all_yamls_readable-True-green)]() - * [![](https://img.shields.io/badge/package_type-observation-blueviolet)]() - * structure: - * [![](https://img.shields.io/badge/default_yaml-True-green)]() - * [![](https://img.shields.io/badge/no_missing_files-True-green)]() - * [![](https://img.shields.io/badge/self_named_yaml-True-green)]() -## LaPalma: - * contents: - * [![](https://img.shields.io/badge/all_yamls_readable-True-green)]() - * [![](https://img.shields.io/badge/package_type-support-blue)]() - * structure: - * [![](https://img.shields.io/badge/no_missing_files-True-green)]() - * [![](https://img.shields.io/badge/self_named_yaml-True-green)]() -## MAAT: - * contents: - * [![](https://img.shields.io/badge/all_yamls_readable-True-green)]() - * [![](https://img.shields.io/badge/package_type-support-blue)]() - * structure: - * [![](https://img.shields.io/badge/no_missing_files-True-green)]() - * [![](https://img.shields.io/badge/self_named_yaml-True-green)]() -## MAORY: - * contents: - * [![](https://img.shields.io/badge/all_yamls_readable-True-green)]() - * [![](https://img.shields.io/badge/package_type-support-blue)]() - * structure: - * [![](https://img.shields.io/badge/no_missing_files-True-green)]() - * [![](https://img.shields.io/badge/self_named_yaml-True-green)]() -## METIS: - * contents: - * [![](https://img.shields.io/badge/all_yamls_readable-True-green)]() - * [![](https://img.shields.io/badge/package_type-observation-blueviolet)]() - * structure: - * [![](https://img.shields.io/badge/FPA_linearity.dat-missing-red)]() - * [![](https://img.shields.io/badge/FPA_linearity_Aquarius.dat-missing-red)]() - * [![](https://img.shields.io/badge/INST_METIS_wavefront_error_budget.dat-missing-red)]() - * [![](https://img.shields.io/badge/QE_detector_Aquarius.dat-missing-red)]() - * [![](https://img.shields.io/badge/QE_detector_H2RG.dat-missing-red)]() - * [![](https://img.shields.io/badge/QE_detector_geosnap.dat-missing-red)]() - * [![](https://img.shields.io/badge/TER_ELT_6_mirror_field_track.dat-missing-red)]() - * [![](https://img.shields.io/badge/TER_armazones_default_MIR_IMG.dat-missing-red)]() - * [![](https://img.shields.io/badge/default_yaml-True-green)]() - * [![](https://img.shields.io/badge/self_named_yaml-True-green)]() -## MICADO: - * contents: - * [![](https://img.shields.io/badge/all_yamls_readable-True-green)]() - * [![](https://img.shields.io/badge/package_type-observation-blueviolet)]() - * structure: - * [![](https://img.shields.io/badge/default_yaml-True-green)]() - * [![](https://img.shields.io/badge/no_missing_files-True-green)]() - * [![](https://img.shields.io/badge/self_named_yaml-True-green)]() -## MICADO_ETC: - * contents: - * [![](https://img.shields.io/badge/all_yamls_readable-True-green)]() - * [![](https://img.shields.io/badge/package_type-observation-blueviolet)]() - * structure: - * [![](https://img.shields.io/badge/MICADO_AnisoCADO_rms_map.fits-missing-red)]() - * [![](https://img.shields.io/badge/default_yaml-True-green)]() - * [![](https://img.shields.io/badge/self_named_yaml-True-green)]() -## MICADO_Sci: - * contents: - * [![](https://img.shields.io/badge/all_yamls_readable-True-green)]() - * [![](https://img.shields.io/badge/package_type-observation-blueviolet)]() - * structure: - * [![](https://img.shields.io/badge/FPA_linearity.dat-missing-red)]() - * [![](https://img.shields.io/badge/MICADO_AnisoCADO_rms_map.fits-missing-red)]() - * [![](https://img.shields.io/badge/QE_detector_H2RG.dat-missing-red)]() - * [![](https://img.shields.io/badge/TER_ELT_5_mirror.dat-missing-red)]() - * [![](https://img.shields.io/badge/TER_ELT_System_20190611.dat-missing-red)]() - * [![](https://img.shields.io/badge/TER_armazones_default_NIR_IMG.dat-missing-red)]() - * [![](https://img.shields.io/badge/default_yaml-True-green)]() - * [![](https://img.shields.io/badge/self_named_yaml-True-green)]() -## OSIRIS: - * contents: - * [![](https://img.shields.io/badge/all_yamls_readable-True-green)]() - * [![](https://img.shields.io/badge/package_type-observation-blueviolet)]() - * structure: - * [![](https://img.shields.io/badge/default_yaml-True-green)]() - * [![](https://img.shields.io/badge/no_missing_files-True-green)]() - * [![](https://img.shields.io/badge/self_named_yaml-True-green)]() -## Paranal: - * contents: - * [![](https://img.shields.io/badge/all_yamls_readable-True-green)]() - * [![](https://img.shields.io/badge/package_type-support-blue)]() - * structure: - * [![](https://img.shields.io/badge/no_missing_files-True-green)]() - * [![](https://img.shields.io/badge/self_named_yaml-True-green)]() -## SimpleCADO: - * contents: - * [![](https://img.shields.io/badge/all_yamls_readable-True-green)]() - * [![](https://img.shields.io/badge/package_type-support-blue)]() - * structure: - * [![](https://img.shields.io/badge/no_missing_files-True-green)]() - * [![](https://img.shields.io/badge/self_named_yaml-True-green)]() -## VLT: - * contents: - * [![](https://img.shields.io/badge/all_yamls_readable-True-green)]() - * [![](https://img.shields.io/badge/package_type-support-blue)]() - * structure: - * [![](https://img.shields.io/badge/no_missing_files-True-green)]() - * [![](https://img.shields.io/badge/self_named_yaml-True-green)]() -## ViennaLT: - * contents: - * [![](https://img.shields.io/badge/all_yamls_readable-True-green)]() - * [![](https://img.shields.io/badge/package_type-observation-blueviolet)]() - * structure: - * [![](https://img.shields.io/badge/QE_SBIG.dat-missing-red)]() - * [![](https://img.shields.io/badge/TER_atmosphere.dat-missing-red)]() - * [![](https://img.shields.io/badge/default_yaml-True-green)]() - * [![](https://img.shields.io/badge/self_named_yaml-True-green)]() -## WFC3: - * contents: - * [![](https://img.shields.io/badge/all_yamls_readable-True-green)]() - * [![](https://img.shields.io/badge/package_type-observation-blueviolet)]() - * structure: - * [![](https://img.shields.io/badge/default_yaml-True-green)]() - * [![](https://img.shields.io/badge/no_missing_files-True-green)]() - * [![](https://img.shields.io/badge/self_named_yaml-True-green)]() -## test_package: - * contents: - * [![](https://img.shields.io/badge/all_yamls_readable-True-green)]() - * [![](https://img.shields.io/badge/package_type-observation-blueviolet)]() - * structure: - * [![](https://img.shields.io/badge/default_yaml-True-green)]() - * [![](https://img.shields.io/badge/no_missing_files-True-green)]() - * [![](https://img.shields.io/badge/self_named_yaml-True-green)]() \ No newline at end of file +**Created on UTC 2023-08-01 22:03:17** + +For details on errors and conflicts, see badge report log file in this directory. + + +## Armazones +[![](https://img.shields.io/badge/package_type-support-deepskyblue)]() +### Structure +* [![](https://img.shields.io/badge/self_named_yaml-found-green)]() +* [![](https://img.shields.io/badge/no_missing_files-green)]() +### Contents +* [![](https://img.shields.io/badge/all_yamls_readable-green)]() +### Dates +* [![](https://img.shields.io/badge/TER_armazones_default_NIR_IMG.dat-conflict-orange)]() +## ELT +[![](https://img.shields.io/badge/package_type-support-deepskyblue)]() +### Structure +* [![](https://img.shields.io/badge/self_named_yaml-found-green)]() +* [![](https://img.shields.io/badge/no_missing_files-green)]() +### Contents +* [![](https://img.shields.io/badge/all_yamls_readable-green)]() +### Dates +* [![](https://img.shields.io/badge/TER_ELT_5_mirror_clean.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_ELT_mirror_aluminium.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_ELT_mirror_mgf2agal.dat-conflict-orange)]() +## ERIS +[![](https://img.shields.io/badge/package_type-support-deepskyblue)]() +### Structure +* [![](https://img.shields.io/badge/self_named_yaml-not_found-red)]() +* [![](https://img.shields.io/badge/no_files_referenced-yellowgreen)]() +### Contents +* [![](https://img.shields.io/badge/no_yaml_files-yellowgreen)]() +## FORS +[![](https://img.shields.io/badge/package_type-support-deepskyblue)]() +### Structure +* [![](https://img.shields.io/badge/self_named_yaml-not_found-red)]() +* [![](https://img.shields.io/badge/no_files_referenced-yellowgreen)]() +### Contents +* [![](https://img.shields.io/badge/no_yaml_files-yellowgreen)]() +## GTC +[![](https://img.shields.io/badge/package_type-support-deepskyblue)]() +### Structure +* [![](https://img.shields.io/badge/self_named_yaml-found-green)]() +* [![](https://img.shields.io/badge/no_missing_files-green)]() +### Contents +* [![](https://img.shields.io/badge/all_yamls_readable-green)]() +### Dates +* [![](https://img.shields.io/badge/unity.dat-conflict-orange)]() +## HARMONI +[![](https://img.shields.io/badge/package_type-support-deepskyblue)]() +### Structure +* [![](https://img.shields.io/badge/self_named_yaml-not_found-red)]() +* [![](https://img.shields.io/badge/no_files_referenced-yellowgreen)]() +### Contents +* [![](https://img.shields.io/badge/no_yaml_files-yellowgreen)]() +## HAWKI +[![](https://img.shields.io/badge/package_type-observation-blueviolet)]() +### Structure +* [![](https://img.shields.io/badge/default_yaml-OK-green)]() +* [![](https://img.shields.io/badge/self_named_yaml-found-green)]() +* [![](https://img.shields.io/badge/no_missing_files-green)]() +### Contents +* [![](https://img.shields.io/badge/all_yamls_readable-green)]() +### Dates +* [![](https://img.shields.io/badge/FPA_hawki_layout.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/LIST_HAWKI_mirrors.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_mirror_gold.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_window.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TC_filter_BrGamma.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TC_filter_CH4.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TC_filter_H.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TC_filter_H2.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TC_filter_J.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TC_filter_Ks.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TC_filter_NB1060.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TC_filter_NB1190.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TC_filter_NB2090.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TC_filter_Y.dat-conflict-orange)]() +## HST +[![](https://img.shields.io/badge/package_type-support-deepskyblue)]() +### Structure +* [![](https://img.shields.io/badge/self_named_yaml-found-green)]() +* [![](https://img.shields.io/badge/no_missing_files-green)]() +### Contents +* [![](https://img.shields.io/badge/all_yamls_readable-green)]() +### Dates +* [![](https://img.shields.io/badge/TER_ELT_mirror_mgf2agal.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/wfc3_ir_primary_001_syn.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/wfc3_ir_secondary_001_syn.dat-conflict-orange)]() +## KMOS +[![](https://img.shields.io/badge/package_type-support-deepskyblue)]() +### Structure +* [![](https://img.shields.io/badge/self_named_yaml-not_found-red)]() +* [![](https://img.shields.io/badge/no_files_referenced-yellowgreen)]() +### Contents +* [![](https://img.shields.io/badge/no_yaml_files-yellowgreen)]() +## LaPalma +[![](https://img.shields.io/badge/package_type-support-deepskyblue)]() +### Structure +* [![](https://img.shields.io/badge/self_named_yaml-found-green)]() +* [![](https://img.shields.io/badge/no_missing_files-green)]() +### Contents +* [![](https://img.shields.io/badge/all_yamls_readable-green)]() +## LFOA +[![](https://img.shields.io/badge/package_type-observation-blueviolet)]() +### Structure +* [![](https://img.shields.io/badge/default_yaml-OK-green)]() +* [![](https://img.shields.io/badge/self_named_yaml-found-green)]() +* [![](https://img.shields.io/badge/no_missing_files-green)]() +### Contents +* [![](https://img.shields.io/badge/all_yamls_readable-green)]() +* [![](https://img.shields.io/badge/TC_filter_Halpha_wide_old.dat-error-red)]() +### Dates +* [![](https://img.shields.io/badge/TER_focal_reducer.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_mirror_aluminium.dat-conflict-orange)]() +## MCIFU +[![](https://img.shields.io/badge/package_type-support-deepskyblue)]() +### Structure +* [![](https://img.shields.io/badge/self_named_yaml-not_found-red)]() +* [![](https://img.shields.io/badge/no_files_referenced-yellowgreen)]() +### Contents +* [![](https://img.shields.io/badge/no_yaml_files-yellowgreen)]() +## METIS +[![](https://img.shields.io/badge/package_type-observation-blueviolet)]() +### Structure +* [![](https://img.shields.io/badge/default_yaml-OK-green)]() +* [![](https://img.shields.io/badge/self_named_yaml-found-green)]() +* [![](https://img.shields.io/badge/TER_ELT_6_mirror_field_track.dat-missing-red)]() +* [![](https://img.shields.io/badge/QE_detector_Aquarius.dat-missing-red)]() +* [![](https://img.shields.io/badge/FPA_linearity_Aquarius.dat-missing-red)]() +### Contents +* [![](https://img.shields.io/badge/all_yamls_readable-green)]() +### Dates +* [![](https://img.shields.io/badge/FPA_metis_img_lm_layout.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/FPA_metis_img_nq_aquarius_layout.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/FPA_metis_img_n_geosnap_layout.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/QE_detector_geosnap.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/QE_detector_H2RG_METIS.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_img_dichroic.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_mirror_gold.dat-conflict-orange)]() +## MICADO +[![](https://img.shields.io/badge/package_type-observation-blueviolet)]() +### Structure +* [![](https://img.shields.io/badge/default_yaml-OK-green)]() +* [![](https://img.shields.io/badge/self_named_yaml-found-green)]() +* [![](https://img.shields.io/badge/no_missing_files-green)]() +### Contents +* [![](https://img.shields.io/badge/all_yamls_readable-green)]() +### Dates +* [![](https://img.shields.io/badge/FPA_linearity.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/LIST_RO_SCAO_mirrors.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/MASK_slit_3000x20.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/MASK_slit_3000x50.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_entrance_window.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_MICADO_mirror_mgf2agal.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_mirror_gold.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_SCAO_dichroic.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TC_filter_blank.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TC_filter_block.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TC_filter_Ks2.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TC_filter_ND1.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TC_filter_ND3.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TC_filter_open.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TC_filter_stop_IJH.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TC_filter_stop_K.dat-conflict-orange)]() +## MICADO_Sci +[![](https://img.shields.io/badge/package_type-observation-blueviolet)]() +### Structure +* [![](https://img.shields.io/badge/default_yaml-OK-green)]() +* [![](https://img.shields.io/badge/self_named_yaml-found-green)]() +* [![](https://img.shields.io/badge/TER_armazones_default_NIR_IMG.dat-missing-red)]() +* [![](https://img.shields.io/badge/TER_ELT_5_mirror.dat-missing-red)]() +* [![](https://img.shields.io/badge/QE_detector_H2RG.dat-missing-red)]() +* [![](https://img.shields.io/badge/FPA_linearity.dat-missing-red)]() +* [![](https://img.shields.io/badge/MICADO_AnisoCADO_rms_map.fits-missing-red)]() +### Contents +* [![](https://img.shields.io/badge/all_yamls_readable-green)]() +### Dates +* [![](https://img.shields.io/badge/TER_MICADO_IMG_common.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_MICADO_RO.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_MORFEO_MMS.dat-conflict-orange)]() +## MORFEO +[![](https://img.shields.io/badge/package_type-support-deepskyblue)]() +### Structure +* [![](https://img.shields.io/badge/self_named_yaml-found-green)]() +* [![](https://img.shields.io/badge/no_missing_files-green)]() +### Contents +* [![](https://img.shields.io/badge/all_yamls_readable-green)]() +### Dates +* [![](https://img.shields.io/badge/TER_mirror_silver.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_MORFEO_entrance_window.dat-error-red)]() +* [![](https://img.shields.io/badge/TER_MORFEO_mirror_aluminium.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_MORFEO_mirror_mgf2agal.dat-conflict-orange)]() +## MUSE +[![](https://img.shields.io/badge/package_type-support-deepskyblue)]() +### Structure +* [![](https://img.shields.io/badge/self_named_yaml-not_found-red)]() +* [![](https://img.shields.io/badge/no_files_referenced-yellowgreen)]() +### Contents +* [![](https://img.shields.io/badge/no_yaml_files-yellowgreen)]() +## NACO +[![](https://img.shields.io/badge/package_type-support-deepskyblue)]() +### Structure +* [![](https://img.shields.io/badge/self_named_yaml-not_found-red)]() +* [![](https://img.shields.io/badge/no_files_referenced-yellowgreen)]() +### Contents +* [![](https://img.shields.io/badge/no_yaml_files-yellowgreen)]() +* [![](https://img.shields.io/badge/NB_3.74.dat-error-red)]() +* [![](https://img.shields.io/badge/NB_4.05.dat-error-red)]() +### Dates +## OSIRIS +[![](https://img.shields.io/badge/package_type-observation-blueviolet)]() +### Structure +* [![](https://img.shields.io/badge/default_yaml-OK-green)]() +* [![](https://img.shields.io/badge/self_named_yaml-found-green)]() +* [![](https://img.shields.io/badge/no_missing_files-green)]() +### Contents +* [![](https://img.shields.io/badge/all_yamls_readable-green)]() +* [![](https://img.shields.io/badge/TRACE_R1000B.dat-error-red)]() +* [![](https://img.shields.io/badge/TRACE_R1000R.dat-error-red)]() +* [![](https://img.shields.io/badge/TRACE_R2000B.dat-error-red)]() +* [![](https://img.shields.io/badge/TRACE_R2500I.dat-error-red)]() +* [![](https://img.shields.io/badge/TRACE_R2500R.dat-error-red)]() +* [![](https://img.shields.io/badge/TRACE_R2500U.dat-error-red)]() +* [![](https://img.shields.io/badge/TRACE_R2500V.dat-error-red)]() +* [![](https://img.shields.io/badge/TRACE_R300B.dat-error-red)]() +* [![](https://img.shields.io/badge/TRACE_R300R.dat-error-red)]() +* [![](https://img.shields.io/badge/TRACE_R500B.dat-error-red)]() +* [![](https://img.shields.io/badge/TRACE_R500R.dat-error-red)]() +### Dates +* [![](https://img.shields.io/badge/QE_OSIRIS_new_detector.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/QE_OSIRIS_old_detector_plus_M3.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/unity.dat-conflict-orange)]() +## Paranal +[![](https://img.shields.io/badge/package_type-support-deepskyblue)]() +### Structure +* [![](https://img.shields.io/badge/self_named_yaml-found-green)]() +* [![](https://img.shields.io/badge/no_missing_files-green)]() +### Contents +* [![](https://img.shields.io/badge/all_yamls_readable-green)]() +## test_package +[![](https://img.shields.io/badge/package_type-observation-blueviolet)]() +### Structure +* [![](https://img.shields.io/badge/default_yaml-OK-green)]() +* [![](https://img.shields.io/badge/self_named_yaml-found-green)]() +* [![](https://img.shields.io/badge/no_missing_files-green)]() +### Contents +* [![](https://img.shields.io/badge/all_yamls_readable-green)]() +### Dates +* [![](https://img.shields.io/badge/TC_filter_Ks.dat-conflict-orange)]() +## UVES +[![](https://img.shields.io/badge/package_type-support-deepskyblue)]() +### Structure +* [![](https://img.shields.io/badge/self_named_yaml-not_found-red)]() +* [![](https://img.shields.io/badge/no_files_referenced-yellowgreen)]() +### Contents +* [![](https://img.shields.io/badge/no_yaml_files-yellowgreen)]() +## ViennaLT +[![](https://img.shields.io/badge/package_type-observation-blueviolet)]() +### Structure +* [![](https://img.shields.io/badge/default_yaml-OK-green)]() +* [![](https://img.shields.io/badge/self_named_yaml-found-green)]() +* [![](https://img.shields.io/badge/TER_atmosphere.dat-missing-red)]() +* [![](https://img.shields.io/badge/QE_SBIG.dat-missing-red)]() +### Contents +* [![](https://img.shields.io/badge/all_yamls_readable-green)]() +## VISIR +[![](https://img.shields.io/badge/package_type-support-deepskyblue)]() +### Structure +* [![](https://img.shields.io/badge/self_named_yaml-not_found-red)]() +* [![](https://img.shields.io/badge/no_files_referenced-yellowgreen)]() +### Contents +* [![](https://img.shields.io/badge/no_yaml_files-yellowgreen)]() +## VLT +[![](https://img.shields.io/badge/package_type-support-deepskyblue)]() +### Structure +* [![](https://img.shields.io/badge/self_named_yaml-found-green)]() +* [![](https://img.shields.io/badge/no_missing_files-green)]() +### Contents +* [![](https://img.shields.io/badge/all_yamls_readable-green)]() +### Dates +* [![](https://img.shields.io/badge/LIST_VLT_mirrors.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TC_mirror_aluminium.dat-conflict-orange)]() +## WFC3 +[![](https://img.shields.io/badge/package_type-observation-blueviolet)]() +### Structure +* [![](https://img.shields.io/badge/default_yaml-OK-green)]() +* [![](https://img.shields.io/badge/self_named_yaml-found-green)]() +* [![](https://img.shields.io/badge/no_missing_files-green)]() +### Contents +* [![](https://img.shields.io/badge/all_yamls_readable-green)]() +### Dates +* [![](https://img.shields.io/badge/TER_filter_F098M.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_filter_F105W.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_filter_F110W.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_filter_F125W.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_filter_F126N.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_filter_F127M.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_filter_F128N.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_filter_F130N.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_filter_F132N.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_filter_F139M.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_filter_F140W.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_filter_F153M.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_filter_F160W.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_filter_F164N.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/TER_filter_F167N.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/wfc3_ir_cor_004_syn.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/wfc3_ir_csm_001_syn.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/wfc3_ir_dn_002_syn.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/wfc3_ir_fold_001_syn.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/wfc3_ir_mask_001_syn.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/wfc3_ir_mir1_001_syn.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/wfc3_ir_mir2_001_syn.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/wfc3_ir_qe_003_syn.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/wfc3_ir_rcp_001_syn.dat-conflict-orange)]() +* [![](https://img.shields.io/badge/wfc3_ir_win_001_syn.dat-conflict-orange)]() \ No newline at end of file diff --git a/_REPORTS/badges.yaml b/_REPORTS/badges.yaml index 78cbab9d..c02ce58d 100644 --- a/_REPORTS/badges.yaml +++ b/_REPORTS/badges.yaml @@ -1,163 +1,312 @@ Armazones: - contents: - all_yamls_readable: true package_type: support structure: - no_missing_files: true - self_named_yaml: true + self_named_yaml: found + no_missing_files: '!OK' + contents: + all_yamls_readable: '!OK' + dates: + TER_armazones_default_NIR_IMG.dat: conflict ELT: + package_type: support + structure: + self_named_yaml: found + no_missing_files: '!OK' contents: - all_yamls_readable: true + all_yamls_readable: '!OK' + dates: + TER_ELT_5_mirror_clean.dat: conflict + TER_ELT_mirror_aluminium.dat: conflict + TER_ELT_mirror_mgf2agal.dat: conflict +ERIS: package_type: support structure: - no_missing_files: true - self_named_yaml: true -GTC: + self_named_yaml: not found + no_files_referenced: '!NONE' contents: - all_yamls_readable: true + no_yaml_files: '!NONE' +FORS: package_type: support structure: - no_missing_files: true - self_named_yaml: true -HAWKI: + self_named_yaml: not found + no_files_referenced: '!NONE' contents: - all_yamls_readable: true - package_type: observation + no_yaml_files: '!NONE' +GTC: + package_type: support structure: - default_yaml: true - no_missing_files: true - self_named_yaml: true -HST: + self_named_yaml: found + no_missing_files: '!OK' contents: - all_yamls_readable: true + all_yamls_readable: '!OK' + dates: + unity.dat: conflict +HARMONI: package_type: support structure: - no_missing_files: true - self_named_yaml: true -LFOA: + self_named_yaml: not found + no_files_referenced: '!NONE' contents: - all_yamls_readable: true + no_yaml_files: '!NONE' +HAWKI: package_type: observation structure: - default_yaml: true - no_missing_files: true - self_named_yaml: true -LaPalma: + default_yaml: OK + self_named_yaml: found + no_missing_files: '!OK' contents: - all_yamls_readable: true + all_yamls_readable: '!OK' + dates: + FPA_hawki_layout.dat: conflict + LIST_HAWKI_mirrors.dat: conflict + TER_mirror_gold.dat: conflict + TER_window.dat: conflict + TC_filter_BrGamma.dat: conflict + TC_filter_CH4.dat: conflict + TC_filter_H.dat: conflict + TC_filter_H2.dat: conflict + TC_filter_J.dat: conflict + TC_filter_Ks.dat: conflict + TC_filter_NB1060.dat: conflict + TC_filter_NB1190.dat: conflict + TC_filter_NB2090.dat: conflict + TC_filter_Y.dat: conflict +HST: package_type: support structure: - no_missing_files: true - self_named_yaml: true -MAAT: + self_named_yaml: found + no_missing_files: '!OK' contents: - all_yamls_readable: true + all_yamls_readable: '!OK' + dates: + TER_ELT_mirror_mgf2agal.dat: conflict + wfc3_ir_primary_001_syn.dat: conflict + wfc3_ir_secondary_001_syn.dat: conflict +KMOS: package_type: support structure: - no_missing_files: true - self_named_yaml: true -MAORY: + self_named_yaml: not found + no_files_referenced: '!NONE' contents: - all_yamls_readable: true + no_yaml_files: '!NONE' +LaPalma: package_type: support structure: - no_missing_files: true - self_named_yaml: true -METIS: + self_named_yaml: found + no_missing_files: '!OK' contents: - all_yamls_readable: true + all_yamls_readable: '!OK' +LFOA: package_type: observation structure: - FPA_linearity.dat: missing - FPA_linearity_Aquarius.dat: missing - INST_METIS_wavefront_error_budget.dat: missing - QE_detector_Aquarius.dat: missing - QE_detector_H2RG.dat: missing - QE_detector_geosnap.dat: missing - TER_ELT_6_mirror_field_track.dat: missing - TER_armazones_default_MIR_IMG.dat: missing - default_yaml: true - self_named_yaml: true -MICADO: + default_yaml: OK + self_named_yaml: found + no_missing_files: '!OK' contents: - all_yamls_readable: true + all_yamls_readable: '!OK' + TC_filter_Halpha_wide_old.dat: error + dates: + TER_focal_reducer.dat: conflict + TER_mirror_aluminium.dat: conflict +MCIFU: + package_type: support + structure: + self_named_yaml: not found + no_files_referenced: '!NONE' + contents: + no_yaml_files: '!NONE' +METIS: package_type: observation structure: - default_yaml: true - no_missing_files: true - self_named_yaml: true -MICADO_ETC: + default_yaml: OK + self_named_yaml: found + TER_ELT_6_mirror_field_track.dat: missing + QE_detector_Aquarius.dat: missing + FPA_linearity_Aquarius.dat: missing contents: - all_yamls_readable: true + all_yamls_readable: '!OK' + dates: + FPA_metis_img_lm_layout.dat: conflict + FPA_metis_img_nq_aquarius_layout.dat: conflict + FPA_metis_img_n_geosnap_layout.dat: conflict + QE_detector_geosnap.dat: conflict + QE_detector_H2RG_METIS.dat: conflict + TER_img_dichroic.dat: conflict + TER_mirror_gold.dat: conflict +MICADO: package_type: observation structure: - MICADO_AnisoCADO_rms_map.fits: missing - default_yaml: true - self_named_yaml: true -MICADO_Sci: + default_yaml: OK + self_named_yaml: found + no_missing_files: '!OK' contents: - all_yamls_readable: true + all_yamls_readable: '!OK' + dates: + FPA_linearity.dat: conflict + LIST_RO_SCAO_mirrors.dat: conflict + MASK_slit_3000x20.dat: conflict + MASK_slit_3000x50.dat: conflict + TER_entrance_window.dat: conflict + TER_MICADO_mirror_mgf2agal.dat: conflict + TER_mirror_gold.dat: conflict + TER_SCAO_dichroic.dat: conflict + TC_filter_blank.dat: conflict + TC_filter_block.dat: conflict + TC_filter_Ks2.dat: conflict + TC_filter_ND1.dat: conflict + TC_filter_ND3.dat: conflict + TC_filter_open.dat: conflict + TC_filter_stop_IJH.dat: conflict + TC_filter_stop_K.dat: conflict +MICADO_Sci: package_type: observation structure: + default_yaml: OK + self_named_yaml: found + TER_armazones_default_NIR_IMG.dat: missing + TER_ELT_5_mirror.dat: missing + QE_detector_H2RG.dat: missing FPA_linearity.dat: missing MICADO_AnisoCADO_rms_map.fits: missing - QE_detector_H2RG.dat: missing - TER_ELT_5_mirror.dat: missing - TER_ELT_System_20190611.dat: missing - TER_armazones_default_NIR_IMG.dat: missing - default_yaml: true - self_named_yaml: true -OSIRIS: contents: - all_yamls_readable: true - package_type: observation + all_yamls_readable: '!OK' + dates: + TER_MICADO_IMG_common.dat: conflict + TER_MICADO_RO.dat: conflict + TER_MORFEO_MMS.dat: conflict +MORFEO: + package_type: support structure: - default_yaml: true - no_missing_files: true - self_named_yaml: true -Paranal: + self_named_yaml: found + no_missing_files: '!OK' contents: - all_yamls_readable: true + all_yamls_readable: '!OK' + dates: + TER_mirror_silver.dat: conflict + TER_MORFEO_entrance_window.dat: error + TER_MORFEO_mirror_aluminium.dat: conflict + TER_MORFEO_mirror_mgf2agal.dat: conflict +MUSE: package_type: support structure: - no_missing_files: true - self_named_yaml: true -SimpleCADO: + self_named_yaml: not found + no_files_referenced: '!NONE' contents: - all_yamls_readable: true + no_yaml_files: '!NONE' +NACO: package_type: support structure: - no_missing_files: true - self_named_yaml: true -VLT: + self_named_yaml: not found + no_files_referenced: '!NONE' contents: - all_yamls_readable: true + no_yaml_files: '!NONE' + NB_3.74.dat: error + NB_4.05.dat: error + dates: {} +OSIRIS: + package_type: observation + structure: + default_yaml: OK + self_named_yaml: found + no_missing_files: '!OK' + contents: + all_yamls_readable: '!OK' + TRACE_R1000B.dat: error + TRACE_R1000R.dat: error + TRACE_R2000B.dat: error + TRACE_R2500I.dat: error + TRACE_R2500R.dat: error + TRACE_R2500U.dat: error + TRACE_R2500V.dat: error + TRACE_R300B.dat: error + TRACE_R300R.dat: error + TRACE_R500B.dat: error + TRACE_R500R.dat: error + dates: + QE_OSIRIS_new_detector.dat: conflict + QE_OSIRIS_old_detector_plus_M3.dat: conflict + unity.dat: conflict +Paranal: package_type: support structure: - no_missing_files: true - self_named_yaml: true -ViennaLT: + self_named_yaml: found + no_missing_files: '!OK' contents: - all_yamls_readable: true + all_yamls_readable: '!OK' +test_package: package_type: observation structure: - QE_SBIG.dat: missing - TER_atmosphere.dat: missing - default_yaml: true - self_named_yaml: true -WFC3: + default_yaml: OK + self_named_yaml: found + no_missing_files: '!OK' contents: - all_yamls_readable: true + all_yamls_readable: '!OK' + dates: + TC_filter_Ks.dat: conflict +UVES: + package_type: support + structure: + self_named_yaml: not found + no_files_referenced: '!NONE' + contents: + no_yaml_files: '!NONE' +ViennaLT: package_type: observation structure: - default_yaml: true - no_missing_files: true - self_named_yaml: true -test_package: + default_yaml: OK + self_named_yaml: found + TER_atmosphere.dat: missing + QE_SBIG.dat: missing contents: - all_yamls_readable: true + all_yamls_readable: '!OK' +VISIR: + package_type: support + structure: + self_named_yaml: not found + no_files_referenced: '!NONE' + contents: + no_yaml_files: '!NONE' +VLT: + package_type: support + structure: + self_named_yaml: found + no_missing_files: '!OK' + contents: + all_yamls_readable: '!OK' + dates: + LIST_VLT_mirrors.dat: conflict + TC_mirror_aluminium.dat: conflict +WFC3: package_type: observation structure: - default_yaml: true - no_missing_files: true - self_named_yaml: true + default_yaml: OK + self_named_yaml: found + no_missing_files: '!OK' + contents: + all_yamls_readable: '!OK' + dates: + TER_filter_F098M.dat: conflict + TER_filter_F105W.dat: conflict + TER_filter_F110W.dat: conflict + TER_filter_F125W.dat: conflict + TER_filter_F126N.dat: conflict + TER_filter_F127M.dat: conflict + TER_filter_F128N.dat: conflict + TER_filter_F130N.dat: conflict + TER_filter_F132N.dat: conflict + TER_filter_F139M.dat: conflict + TER_filter_F140W.dat: conflict + TER_filter_F153M.dat: conflict + TER_filter_F160W.dat: conflict + TER_filter_F164N.dat: conflict + TER_filter_F167N.dat: conflict + wfc3_ir_cor_004_syn.dat: conflict + wfc3_ir_csm_001_syn.dat: conflict + wfc3_ir_dn_002_syn.dat: conflict + wfc3_ir_fold_001_syn.dat: conflict + wfc3_ir_mask_001_syn.dat: conflict + wfc3_ir_mir1_001_syn.dat: conflict + wfc3_ir_mir2_001_syn.dat: conflict + wfc3_ir_qe_003_syn.dat: conflict + wfc3_ir_rcp_001_syn.dat: conflict + wfc3_ir_win_001_syn.dat: conflict From c92a32322d868e12ab44d3f9f8baa07bd7af2ec2 Mon Sep 17 00:00:00 2001 From: teutoburg Date: Wed, 2 Aug 2023 00:04:15 +0200 Subject: [PATCH 23/23] Mostly docstrings... --- irdb/badges.py | 138 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 126 insertions(+), 12 deletions(-) diff --git a/irdb/badges.py b/irdb/badges.py index b5d5af56..4fb0d46f 100644 --- a/irdb/badges.py +++ b/irdb/badges.py @@ -1,18 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ -Module/script Description. - -Created on Fri Jul 28 15:42:59 2023 - -@author: ghost +Everything to do with report badges and more! """ import logging from warnings import warn from pathlib import Path from typing import TextIO -# from io import StringIO from numbers import Number from string import Template from datetime import datetime as dt @@ -32,6 +27,47 @@ def _fix_badge_str(badge_str: str) -> str: class Badge(): + """Base class for markdown report badges. + + Based on the type and (in case of strings) value of the parameter `value`, + the appropriate subclass is returned, which also deals with the colour of + the badge. These subclasses should *not* be instantiated directly, but + rather this base class should always be used. + + In the case of a string-type `value`, the colour of the badge is based on + a set of special strings, e.g. red for 'error' or green for 'found'. + A complete list of these special strings can be accessed via + ``StrBadge.special_strings``. The default colour for any string value not + listed as a special string is lightgrey. + + By default, all badges appear as "key/label-value" badges with a grey label + on the left side and a coloured value on the right side. For simple + messages, it is also possible to produce a "message-only" badge. This can + simply be done by adding a leading '!' to the (string) `value` parameter. + The message of the badge is then only the `key` parameter, while the colour + of the badge is again decided by the special strings, after the leading '!' + is stripped. + + Any spaces or single dashes present in either `key` or `value` are + automatically replaced by underscores or double dashes, respectively, to + comply with the format requirements for the badges. + + It is possible to manually change the colour of any badge after creation + by setting the desired colour (string) for the `colour` attribute. + + Parameters + ---------- + key : str + Dictionary key, become (left-side) label of the badge. + value : str, bool, int or float + Dictionary key, become (right-side) value of the badge. + Subclass dispatch is decided based on type and value of this parameter. + + Attributes + ---------- + colour : str + The (auto-assigned) colour of the badge. + """ pattern = Template("[![](https://img.shields.io/badge/$key-$val-$col)]()") colour = "lightgrey" @@ -57,6 +93,7 @@ def write(self, stream: TextIO) -> None: class BoolBadge(Badge): + """Key-value Badge for bool values, True -> green, False -> red.""" colour = "red" def __init__(self, key: str, value: bool): super().__init__(key, value) @@ -65,10 +102,12 @@ def __init__(self, key: str, value: bool): class NumBadge(Badge): + """Key-value Badge for numerical values, lightblue.""" colour = "lightblue" class StrBadge(Badge): + """Key-value Badge for string values, colour based on special strings.""" special_strings = { "observation": "blueviolet", "support": "deepskyblue", @@ -89,6 +128,7 @@ def __init__(self, key: str, value: str): class MsgOnlyBadge(StrBadge): + """Key-only Badge for string values, colour based on special strings.""" pattern = Template("[![](https://img.shields.io/badge/$key-$col)]()") def __init__(self, key: str, value: str): @@ -99,26 +139,93 @@ def __init__(self, key: str, value: str): class BadgeReport(SystemDict): - def __init__(self, filename=None, report_filename=None, logs_filename=None, - save_logs=True): + """Context manager class for collection and generation of report badges. + + Intended usage is in a pytest fixture with a scope that covers all tests + that should be included in that report file: + + >>> import pytest + >>> + >>> @pytest.fixture(name="badges", scope="module") + >>> def fixture_badges(): + >>> with BadgeReport() as report: + >>> yield report + + This fixture can then be used inside the tests like a dictionary: + + >>> def test_something(self, badges): + >>> badges[f"!foo.bar.baz"] = "OK" + + Because `BadgeReport` inherits from ``SystemDict``, the use of '!'-type + "bang-strings" is supported. + + Additionally, any logging generated within a test can be captured and + stored in the report, to be written in a separate log file at teardown: + + >>> import logging + >>> + >>> def test_something_else(self, badges, caplog): + >>> logging.warning("Oh no!") + >>> badges.logs.extend(caplog.records) + + Note the use of ``caplog.records`` to access the ``logging.LogRecord`` + objects rather then the string output, as `BadgeReport` performs very basic + custom formatting. Further note the use of ``logs.extend()``, because + ``caplog.records`` returns a ``list``, to not end up with nested lists. + + The level of logging recorded is controlled by the logging settings in the + test script. `BadgeReport` handles all ``logging.LogRecord`` objects in + the final `.logs` list. + + Parameters + ---------- + filename : str, optional + Name for yaml file, should end in '.yaml. The default is "badges.yaml". + report_filename : str, optional + Name for report file, should end in '.md'. The default is "badges.md". + logs_filename : str, optional + Name for log file. The default is "badge_report_log.txt". + save_logs : bool, optional + Whether to output logs. The default is True. + + Attributes + ---------- + yamlpath : Path + Full path for yaml file. + report_path : Path + Full path for report file. + log_path : Path + Full path for log file. + logs : list of logging.LogRecord + List of logging.LogRecord objects to be saved to `logs_filename`. + """ + def __init__(self, + filename: str = "badges.yaml", + report_filename: str = "badges.md", + logs_filename: str = "badge_report_log.txt", + save_logs: bool = True, + ): logging.debug("REPORT INIT") base_path = Path(PKG_DIR, "_REPORTS") - self.filename = filename or "badges.yaml" + self.filename = filename self.yamlpath = base_path / self.filename - self.report_name = report_filename or "badges.md" + self.report_name = report_filename self.report_path = base_path / self.report_name self.save_logs = save_logs self.logs = [] - self.logs_name = logs_filename or "badge_report_log.txt" - self.log_path = base_path / self.logs_name + logs_name = logs_filename or "badge_report_log.txt" + self.log_path = base_path / logs_name super().__init__() def __enter__(self): logging.debug("REPORT ENTER") try: + # TODO: WHY do we actually load this first? It caused some issues + # with 'old' badges that are not cleared. Is there any good + # reason at all to load the previous yaml file??? with self.yamlpath.open(encoding="utf-8") as file: self.update(yaml.full_load(file)) except FileNotFoundError: @@ -134,11 +241,13 @@ def __exit__(self, exc_type, exc_value, exc_traceback): logging.debug("REPORT DONE") def write_logs(self) -> None: + """Dump logs to file (`logs_filename`).""" with self.log_path.open("w", encoding="utf-8") as file: for log in self.logs: file.write(f"{log.levelname}::{log.message}\n") def write_yaml(self) -> None: + """Dump dict to yaml file (`filename`).""" dumpstr = yaml.dump(self.dic, sort_keys=False) self.yamlpath.write_text(dumpstr, encoding="utf-8") @@ -150,6 +259,11 @@ def _make_preamble(self) -> str: return preamble def generate_report(self) -> None: + """Write markdown badge report to `report_filename`.""" + if not self.report_path.suffix == ".md": + logging.warning(("Expected '.md' suffix for report file name, but " + "found %s. Report file might not be readable."), + self.report_path.suffix) with self.report_path.open("w", encoding="utf-8") as file: file.write(self._make_preamble()) make_entries(file, self.dic)