From 3fc19fb0b6d04acd736e5da0e091c52f5345fbbe Mon Sep 17 00:00:00 2001 From: Gavin Huttley Date: Tue, 8 Oct 2024 08:59:48 +1100 Subject: [PATCH 01/10] MAINT: update tests to use pytest fixtures --- tests/test_logging.py | 294 ++++++++++++++---------------------------- 1 file changed, 99 insertions(+), 195 deletions(-) diff --git a/tests/test_logging.py b/tests/test_logging.py index 60cd96a..957b4e1 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -1,21 +1,12 @@ -# -*- coding: utf-8 -*- -import shutil +import contextlib import sys - from collections import Counter from pathlib import Path -from tempfile import TemporaryDirectory import pytest -from scitrack import ( - CachingLogger, - get_file_hexdigest, - get_package_name, - get_text_hexdigest, - get_version_for_package, -) - +from scitrack import (CachingLogger, get_file_hexdigest, get_package_name, + get_text_hexdigest, get_version_for_package) __author__ = "Gavin Huttley" __copyright__ = "Copyright 2016-2021, Gavin Huttley" @@ -28,105 +19,86 @@ TEST_ROOTDIR = Path(__file__).parent -DIRNAME = TEST_ROOTDIR / "delme" -LOGFILE_NAME = DIRNAME / "delme.log" +DIRNAME = "delme" +LOGFILE_NAME = "delme.log" + + +@pytest.fixture +def logfile(tmp_path): + return tmp_path / LOGFILE_NAME -def test_creates_path(): +def test_creates_path(logfile): """creates a log path""" LOGGER = CachingLogger(create_dir=True) - LOGGER.log_file_path = LOGFILE_NAME + LOGGER.log_file_path = logfile LOGGER.input_file(TEST_ROOTDIR / "sample-lf.fasta") LOGGER.shutdown() - assert DIRNAME.exists() - assert LOGFILE_NAME.exists() + assert logfile.exists() - try: - shutil.rmtree(DIRNAME) - except OSError: - pass - -def test_set_path_if_exists(): +def test_set_path_if_exists(logfile): """cannot change an existing logging path""" - with TemporaryDirectory(dir=".") as dirname: - dirname = Path(dirname) - LOGGER = CachingLogger(create_dir=True) - LOGGER.log_file_path = dirname / LOGFILE_NAME - LOGGER.input_file(TEST_ROOTDIR / "sample-lf.fasta") - with pytest.raises(AttributeError): - LOGGER.log_file_path = dirname / "invalid.log" - LOGGER.shutdown() + LOGGER = CachingLogger(create_dir=True) + LOGGER.log_file_path = logfile + LOGGER.input_file(TEST_ROOTDIR / "sample-lf.fasta") + with pytest.raises(AttributeError): + LOGGER.log_file_path = logfile.parent / "invalid.log" + LOGGER.shutdown() -def test_tracks_args(): +def test_tracks_args(logfile): """details on host, python version should be present in log""" LOGGER = CachingLogger(create_dir=True) - LOGGER.log_file_path = LOGFILE_NAME + LOGGER.log_file_path = logfile LOGGER.input_file(TEST_ROOTDIR / "sample-lf.fasta") LOGGER.shutdown() - contents = LOGFILE_NAME.read_text() + contents = logfile.read_text() for label in ["system_details", "python", "user", "command_string"]: assert contents.count(f"\t{label}") == 1, ( label, contents.count(label), ) - try: - shutil.rmtree(DIRNAME) - except OSError: - pass - -def test_tracks_locals(): +def test_tracks_locals(logfile): """details on local arguments should be present in log""" LOGGER = CachingLogger(create_dir=True) - LOGGER.log_file_path = LOGFILE_NAME + LOGGER.log_file_path = logfile def track_func(a=1, b="abc"): LOGGER.log_args() track_func() LOGGER.shutdown() - with open(LOGFILE_NAME, "r") as infile: - for line in infile: - index = line.find("params :") - if index > 0: - got = eval(line.split("params :")[1]) - break + log_data = logfile.read_text().splitlines() + for line in log_data: + index = line.find("params :") + if index > 0: + got = eval(line.split("params :")[1]) + break assert got == dict(a=1, b="abc") - try: - shutil.rmtree(DIRNAME) - except OSError: - pass - -def test_tracks_locals_skip_module(): +def test_tracks_locals_skip_module(logfile): """local arguments should exclude modules""" LOGGER = CachingLogger(create_dir=True) - LOGGER.log_file_path = LOGFILE_NAME + LOGGER.log_file_path = logfile def track_func(a=1, b="abc"): - import gzip + import gzip # noqa LOGGER.log_args() track_func() LOGGER.shutdown() - with open(LOGFILE_NAME, "r") as infile: - for line in infile: - index = line.find("params :") - if index > 0: - got = eval(line.split("params :")[1]) - break + for line in logfile.read_text().splitlines(): + index = line.find("params :") + if index > 0: + got = eval(line.split("params :")[1]) + break assert got == dict(a=1, b="abc") - try: - shutil.rmtree(DIRNAME) - except OSError: - pass - def test_package_inference(): """correctly identify the package name""" @@ -138,25 +110,20 @@ def test_package_versioning(): """correctly identify versions for specified packages""" vn = get_version_for_package("numpy") assert type(vn) is str - try: # not installed, but using valuerrror rather than import error + with contextlib.suppress(ValueError): get_version_for_package("gobbledygook") - except ValueError: - pass - - try: + with contextlib.suppress(ValueError): get_version_for_package(1) - except ValueError: - pass -def test_tracks_versions(): +def test_tracks_versions(logfile): """should track versions""" LOGGER = CachingLogger(create_dir=True) - LOGGER.log_file_path = LOGFILE_NAME + LOGGER.log_file_path = logfile LOGGER.input_file(TEST_ROOTDIR / "sample-lf.fasta") LOGGER.log_versions(["numpy"]) LOGGER.shutdown() - contents = LOGFILE_NAME.read_text() + contents = logfile.read_text() for label in ["system_details", "python", "user", "command_string"]: assert contents.count(f"\t{label}") == 1, ( label, @@ -165,17 +132,12 @@ def test_tracks_versions(): for line in contents.splitlines(): if "version :" in line: if "numpy" not in line: - assert "==%s" % __version__ in line, line + assert f"=={__version__}" in line, line else: assert "numpy" in line, line - try: - shutil.rmtree(DIRNAME) - except OSError: - pass - -def test_caching(): +def test_caching(logfile): """should cache calls prior to logging""" LOGGER = CachingLogger(create_dir=True) LOGGER.input_file(TEST_ROOTDIR / "sample-lf.fasta") @@ -185,34 +147,26 @@ def test_caching(): LOGGER.log_versions(["numpy"]) assert "numpy==" in LOGGER._messages[-1] - LOGGER.log_file_path = LOGFILE_NAME + LOGGER.log_file_path = logfile LOGGER.shutdown() - try: - shutil.rmtree(DIRNAME) - except OSError: - pass -def test_shutdown(): +def test_shutdown(logfile): """correctly purges contents""" LOGGER = CachingLogger(create_dir=True) - LOGGER.log_file_path = LOGFILE_NAME + LOGGER.log_file_path = logfile LOGGER.input_file(TEST_ROOTDIR / "sample-lf.fasta") LOGGER.shutdown() - try: - shutil.rmtree(DIRNAME) - except OSError: - pass -def test_tracks_versions_empty(): +def test_tracks_versions_empty(logfile): """should track version of scitrack""" LOGGER = CachingLogger(create_dir=True) - LOGGER.log_file_path = LOGFILE_NAME + LOGGER.log_file_path = logfile LOGGER.input_file(TEST_ROOTDIR / "sample-lf.fasta") LOGGER.log_versions() LOGGER.shutdown() - contents = LOGFILE_NAME.read_text() + contents = logfile.read_text() for label in ["system_details", "python", "user", "command_string"]: assert contents.count(f"\t{label}") == 1, ( label, @@ -220,34 +174,22 @@ def test_tracks_versions_empty(): ) for line in contents.splitlines(): if "version :" in line: - assert "==%s" % __version__ in line, line + assert f"=={__version__}" in line, line - try: - shutil.rmtree(DIRNAME) - except OSError: - pass - -def test_tracks_versions_string(): +def test_tracks_versions_string(logfile): """should track version if package name is a string""" LOGGER = CachingLogger(create_dir=True) - LOGGER.log_file_path = LOGFILE_NAME + LOGGER.log_file_path = logfile LOGGER.log_versions("numpy") LOGGER.shutdown() import numpy - expect = "numpy==%s" % numpy.__version__ + expect = f"numpy=={numpy.__version__}" del numpy - with open(LOGFILE_NAME, "r") as infile: - contents = "".join(infile.readlines()) - for line in contents.splitlines(): - if "version :" in line and "numpy" in line: - assert expect in line, line - - try: - shutil.rmtree(DIRNAME) - except OSError: - pass + for line in logfile.read_text().splitlines(): + if "version :" in line and "numpy" in line: + assert expect in line, line def test_get_version_for_package(): @@ -260,7 +202,7 @@ def test_get_version_for_package(): pyfile = TEST_ROOTDIR / "delme.py" pyfile.write_text("\n".join(["def version():", " return 'my-version'"])) sys.path.append(TEST_ROOTDIR) - import delme + import delme # noqa got = get_version_for_package("delme") assert got == "my-version" @@ -268,71 +210,57 @@ def test_get_version_for_package(): # func returns a list pyfile.write_text("version = ['my-version']\n") - from importlib import reload + from importlib import reload # noqa got = get_version_for_package(reload(delme)) assert got == "my-version" pyfile.unlink() -def test_tracks_versions_module(): +def test_tracks_versions_module(logfile): """should track version if package is a module""" LOGGER = CachingLogger(create_dir=True) - LOGGER.log_file_path = LOGFILE_NAME + LOGGER.log_file_path = logfile import numpy - expect = "numpy==%s" % numpy.__version__ + expect = f"numpy=={numpy.__version__}" LOGGER.log_versions(numpy) LOGGER.shutdown() del numpy - with open(LOGFILE_NAME, "r") as infile: - contents = "".join(infile.readlines()) - for line in contents.splitlines(): - if "version :" in line and "numpy" in line: - assert expect in line, line - - try: - shutil.rmtree(DIRNAME) - except OSError: - pass + for line in logfile.read_text().splitlines(): + if "version :" in line and "numpy" in line: + assert expect in line, line -def test_appending(): +def test_appending(logfile): """appending to an existing logfile should work""" LOGGER = CachingLogger(create_dir=True) - LOGGER.log_file_path = LOGFILE_NAME + LOGGER.log_file_path = logfile LOGGER.input_file(TEST_ROOTDIR / "sample-lf.fasta") LOGGER.shutdown() records = Counter() - with open(LOGFILE_NAME) as infile: - for line in infile: - records[line] += 1 + for line in logfile.read_text().splitlines(): + records[line] += 1 vals = set(list(records.values())) assert vals == {1} LOGGER = CachingLogger(create_dir=True) LOGGER.mode = "a" - LOGGER.log_file_path = LOGFILE_NAME + LOGGER.log_file_path = logfile LOGGER.input_file(TEST_ROOTDIR / "sample-lf.fasta") LOGGER.shutdown() records = Counter() - with open(LOGFILE_NAME) as infile: - for line in infile: - records[line] += 1 + for line in logfile.read_text().splitlines(): + records[line] += 1 vals = set(list(records.values())) assert vals == {2} - try: - shutil.rmtree(DIRNAME) - except OSError: - pass - -def test_mdsum_input(): +def test_mdsum_input(logfile): """md5 sum of input file should be correct""" LOGGER = CachingLogger(create_dir=True) - LOGGER.log_file_path = LOGFILE_NAME + LOGGER.log_file_path = logfile # first file has LF, second has CRLF line endings hex_path = [ ("96eb2c2632bae19eb65ea9224aaafdad", "sample-lf.fasta"), @@ -342,7 +270,7 @@ def test_mdsum_input(): LOGGER.input_file(TEST_ROOTDIR / "sample-crlf.fasta") LOGGER.shutdown() - with open(LOGFILE_NAME, "r") as infile: + with open(logfile, "r") as infile: num = 0 for line in infile: for h, p in hex_path: @@ -354,16 +282,11 @@ def test_mdsum_input(): assert num == len(hex_path) - try: - shutil.rmtree(DIRNAME) - except OSError: - pass - -def test_mdsum_output(): +def test_mdsum_output(logfile): """md5 sum of output file should be correct""" LOGGER = CachingLogger(create_dir=True) - LOGGER.log_file_path = LOGFILE_NAME + LOGGER.log_file_path = logfile # first file has LF, second has CRLF line endings hex_path = [ ("96eb2c2632bae19eb65ea9224aaafdad", "sample-lf.fasta"), @@ -371,7 +294,7 @@ def test_mdsum_output(): LOGGER.output_file(TEST_ROOTDIR / "sample-lf.fasta") LOGGER.shutdown() - with open(LOGFILE_NAME, "r") as infile: + with open(logfile, "r") as infile: num = 0 for line in infile: for h, p in hex_path: @@ -382,42 +305,25 @@ def test_mdsum_output(): assert num == len(hex_path) - try: - shutil.rmtree(DIRNAME) - except OSError: - pass - -def test_logging_text(): +def test_logging_text(logfile): """correctly logs text data""" text = "abcde\nedfgu\nyhbnd" hexd = "f06597f8a983dfc93744192b505a8af9" LOGGER = CachingLogger(create_dir=True) - LOGGER.log_file_path = LOGFILE_NAME + LOGGER.log_file_path = logfile LOGGER.text_data(text, label="UNIQUE") LOGGER.shutdown() - contents = LOGFILE_NAME.read_text().splitlines() - unique = None - for line in contents: - if "UNIQUE" in line: - unique = line - break + contents = logfile.read_text().splitlines() + unique = next((line for line in contents if "UNIQUE" in line), None) assert hexd in unique - try: - shutil.rmtree(DIRNAME) - except OSError: - pass -def test_logfile_path(): +def test_logfile_path(logfile): """correctly assigned""" - LOGGER = CachingLogger(create_dir=True, log_file_path=LOGFILE_NAME) - assert LOGGER.log_file_path == str(LOGFILE_NAME) + LOGGER = CachingLogger(create_dir=True, log_file_path=logfile) + assert LOGGER.log_file_path == str(logfile) LOGGER.shutdown() - try: - shutil.rmtree(DIRNAME) - except OSError: - pass def test_md5sum_text(): @@ -437,7 +343,6 @@ def test_md5sum_text(): for h, p in hex_path: p = TEST_ROOTDIR / p data = p.read_bytes() - print(p, repr(data)) got = get_text_hexdigest(data) assert got == h, (p, repr(data)) @@ -451,18 +356,17 @@ def test_get_text_hexdigest_invalid(): get_text_hexdigest([]) -def test_read_from_written(): +def test_read_from_written(tmp_path): """create files with different line endings dynamically""" text = "abcdeENDedfguENDyhbnd" - with TemporaryDirectory(dir=TEST_ROOTDIR) as dirname: - for ex, lf in ( - ("f06597f8a983dfc93744192b505a8af9", "\n"), - ("39db5cc2f7749f02e0c712a3ece12ffc", "\r\n"), - ): - p = Path(dirname) / "test.txt" - data = text.replace("END", lf) - p.write_bytes(data.encode("utf-8")) - expect = get_text_hexdigest(data) - assert expect == ex, (expect, ex) - got = get_file_hexdigest(p) - assert got == expect, f"FAILED: {repr(lf)}, {(ex, got)}" + for ex, lf in ( + ("f06597f8a983dfc93744192b505a8af9", "\n"), + ("39db5cc2f7749f02e0c712a3ece12ffc", "\r\n"), + ): + p = tmp_path / "test.txt" + data = text.replace("END", lf) + p.write_bytes(data.encode("utf-8")) + expect = get_text_hexdigest(data) + assert expect == ex, (expect, ex) + got = get_file_hexdigest(p) + assert got == expect, f"FAILED: {repr(lf)}, {(ex, got)}" From 558cedbc46075cf4051eeba3b7d54216b80b448a Mon Sep 17 00:00:00 2001 From: Gavin Huttley Date: Tue, 8 Oct 2024 09:02:57 +1100 Subject: [PATCH 02/10] MAINT: modernise code --- src/scitrack/__init__.py | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/scitrack/__init__.py b/src/scitrack/__init__.py index 1beef5a..1dc3097 100644 --- a/src/scitrack/__init__.py +++ b/src/scitrack/__init__.py @@ -1,6 +1,8 @@ """ SciTrack provides basic logging capabilities to track scientific computations. """ + +import contextlib import hashlib import importlib import inspect @@ -9,10 +11,8 @@ import platform import socket import sys - from getpass import getuser - __author__ = "Gavin Huttley" __copyright__ = "Copyright 2016-2021, Gavin Huttley" __credits__ = ["Gavin Huttley"] @@ -42,8 +42,7 @@ def _create_path(path): def get_package_name(object): """returns the package name for the provided object""" name = inspect.getmodule(object).__name__ - package = name.split(".")[0] - return package + return name.split(".")[0] def get_version_for_package(package): @@ -51,25 +50,22 @@ def get_version_for_package(package): if type(package) == str: try: mod = importlib.import_module(package) - except ModuleNotFoundError: - raise ValueError("Unknown package %s" % package) + except ModuleNotFoundError as e: + raise ValueError(f"Unknown package {package}") from e elif inspect.ismodule(package): mod = package else: - raise ValueError("Unknown type, package %s" % package) + raise ValueError(f"Unknown type, package {package}") vn = None for v in VERSION_ATTRS: - try: + with contextlib.suppress(AttributeError): vn = getattr(mod, v) if callable(vn): vn = vn() break - except AttributeError: - pass - if type(vn) in (tuple, list): vn = vn[0] @@ -149,7 +145,7 @@ def _record_file(self, file_class, file_path): file_path = abspath(file_path) md5sum = get_file_hexdigest(file_path) self.log_message(file_path, label=file_class) - self.log_message(md5sum, label="%s md5sum" % file_class) + self.log_message(md5sum, label=f"{file_class} md5sum") def input_file(self, file_path, label="input_file_path"): """logs path and md5 checksum @@ -230,7 +226,7 @@ def log_versions(self, packages=None): vn = get_version_for_package(name) else: vn = [g.get(v, None) for v in VERSION_ATTRS if g.get(v, None)] - vn = None if not vn else vn[0] + vn = vn[0] if vn else None name = get_package_name(parent) versions = [(name, vn)] @@ -246,16 +242,16 @@ def set_logger(log_file_path, level=logging.DEBUG, mode="w"): """setup logging""" handler = FileHandler(log_file_path, mode) handler.setLevel(level) - hostpid = socket.gethostname() + ":" + str(os.getpid()) + hostpid = f"{socket.gethostname()}:{os.getpid()}" fmt = "%(asctime)s\t" + hostpid + "\t%(levelname)s\t%(message)s" formatter = logging.Formatter(fmt, datefmt="%Y-%m-%d %H:%M:%S") handler.setFormatter(formatter) logging.root.addHandler(handler) logging.root.setLevel(level) - logging.info("system_details : system=%s" % platform.version()) - logging.info("python : %s" % platform.python_version()) - logging.info("user : %s" % getuser()) - logging.info("command_string : %s" % " ".join(sys.argv)) + logging.info(f"system_details : system={platform.version()}") + logging.info(f"python : {platform.python_version()}") + logging.info(f"user : {getuser()}") + logging.info(f'command_string : {" ".join(sys.argv)}') return handler @@ -273,11 +269,11 @@ def get_file_hexdigest(filename): with open(filename, "rb") as infile: md5 = hashlib.md5() while True: - data = infile.read(128) - if not data: + if data := infile.read(128): + md5.update(data) + else: break - md5.update(data) return md5.hexdigest() From 0b25115150eb676ce2f060030e74e5389e8ebeaf Mon Sep 17 00:00:00 2001 From: Gavin Huttley Date: Tue, 8 Oct 2024 09:03:27 +1100 Subject: [PATCH 03/10] DEV: numpy should be part of test install --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 11bd616..704fe33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,8 +35,7 @@ Documentation = "https://github.com/HuttleyLab/scitrack" [tool.flit.metadata.requires-extra] test = [ - "black", - "isort", + "numpy", "pytest", "pytest-cov", "nox"] From 145cf5038e467b6d59f05097eee33705214d14d2 Mon Sep 17 00:00:00 2001 From: Gavin Huttley Date: Tue, 8 Oct 2024 09:03:46 +1100 Subject: [PATCH 04/10] DEV: use ruff for linting --- README.rst | 3 +- pyproject.toml | 107 ++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 85 insertions(+), 25 deletions(-) diff --git a/README.rst b/README.rst index f30a33b..37ad9bc 100644 --- a/README.rst +++ b/README.rst @@ -7,7 +7,8 @@ .. |coverall| image:: https://coveralls.io/repos/github/GavinHuttley/scitrack/badge.svg?branch=develop :target: https://coveralls.io/github/GavinHuttley/scitrack?branch=develop -.. |Using Black Formatting| image:: https://img.shields.io/badge/code%20style-black-000000.svg +.. |Using Ruff| image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json + :target: https://github.com/astral-sh/ruff .. |Python 3.8+| image:: https://img.shields.io/badge/python-3.8+-blue.svg :target: https://www.python.org/downloads/release/python-380/ diff --git a/pyproject.toml b/pyproject.toml index 704fe33..a2a4279 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,30 +38,89 @@ test = [ "numpy", "pytest", "pytest-cov", + "ruff", "nox"] -[tool.black] +[tool.ruff] +exclude = [ + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "build", + "dist", + "site-packages", + "venv", +] + +# Same as Black. line-length = 88 -exclude = ''' -/( - \.eggs - | \.git - | \.hg - | \.mypy_cache - | \.nox - | \.venv - | _build - | build - | dist - | tests/data -)/ -''' - -[tool.isort] -atomic=true -force_grid_wrap=0 -include_trailing_comma=true -lines_after_imports=2 -lines_between_types=1 -multi_line_output=3 -use_parentheses=true +indent-width = 4 + +target-version = "py39" + +[tool.ruff.lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or +# McCabe complexity (`C901`) by default. +select = ["ALL"] +ignore = ["EXE002", "FA100", "E501", "D"] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.ruff.lint.per-file-ignores] +"tests/**/*.py" = [ + "S101", # asserts allowed in tests... + "INP001", # __init__.py files are not required... + "ANN", + "N802", + "N803" +] +"noxfile.py" = [ + "S101", # asserts allowed in tests... + "INP001", # __init__.py files are not required... + "ANN", + "N802", + "N803" +] + +[tool.ruff.format] +# Like Black, use double quotes for strings. +quote-style = "double" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "lf" +docstring-code-format = true + +# Set the line length limit used when formatting code snippets in +# docstrings. +# +# This only has an effect when the `docstring-code-format` setting is +# enabled. +docstring-code-line-length = "dynamic" \ No newline at end of file From 5b81e2eb9042aa297515b8911b5ecabecd6b246f Mon Sep 17 00:00:00 2001 From: Gavin Huttley Date: Tue, 8 Oct 2024 09:13:42 +1100 Subject: [PATCH 05/10] DEV: drop support for 3.8 --- README.rst | 8 ++++---- pyproject.toml | 8 +++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index 37ad9bc..5af3b1a 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,6 @@ -|Build Status| |coverall| |Using Black Formatting| |Python 3.8+| +|Status| |coverall| |Using Ruff| |Python 3.9+| -.. |Build Status| image:: https://github.com/HuttleyLab/scitrack/workflows/CI/badge.svg?branch=develop +.. |Status| image:: https://github.com/HuttleyLab/scitrack/workflows/CI/badge.svg?branch=develop :target: https://github.com/HuttleyLab/scitrack/actions?workflow=CI :alt: CI Status @@ -10,8 +10,8 @@ .. |Using Ruff| image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json :target: https://github.com/astral-sh/ruff -.. |Python 3.8+| image:: https://img.shields.io/badge/python-3.8+-blue.svg - :target: https://www.python.org/downloads/release/python-380/ +.. |Python 3.9+| image:: https://img.shields.io/badge/python-3.9+-blue.svg + :target: https://www.python.org/downloads/release/python-390/ ################## diff --git a/pyproject.toml b/pyproject.toml index a2a4279..c95d868 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,11 +17,13 @@ classifiers = [ "Topic :: Scientific/Engineering :: Bio-Informatics", "Topic :: Software Development :: Libraries :: Python Modules", "Operating System :: OS Independent", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", - ] -requires-python = ">=3.8" + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", +] +requires-python = ">=3.9" [tool.flit.sdist] include = ["src/*", "pyproject.toml", "*.rst"] From 4257db8235e80a0e1018b7853665e32ade8875c7 Mon Sep 17 00:00:00 2001 From: Gavin Huttley Date: Tue, 8 Oct 2024 09:21:18 +1100 Subject: [PATCH 06/10] STY: code linting --- src/scitrack/__init__.py | 6 +++--- tests/test_logging.py | 19 ++++++++++++------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/scitrack/__init__.py b/src/scitrack/__init__.py index 1dc3097..1430e5a 100644 --- a/src/scitrack/__init__.py +++ b/src/scitrack/__init__.py @@ -78,7 +78,7 @@ def get_version_for_package(package): FileHandler = logging.FileHandler -class CachingLogger(object): +class CachingLogger: """stores log messages until a log filename is provided""" def __init__(self, log_file_path=None, create_dir=True, mode="w"): @@ -113,7 +113,7 @@ def log_file_path(self, path): """set the log file path and then dump cached log messages""" if self._log_file_path is not None: raise AttributeError( - f"log_file_path already defined as {self._log_file_path}" + f"log_file_path already defined as {self._log_file_path}", ) path = abspath(path) @@ -288,7 +288,7 @@ def get_text_hexdigest(data): """ data_class = data.__class__ # fmt: off - if data_class in ("".__class__, u"".__class__): + if data_class in ("".__class__, "".__class__): data = data.encode("utf-8") elif data.__class__ != b"".__class__: raise TypeError("can only checksum string, unicode or bytes data") diff --git a/tests/test_logging.py b/tests/test_logging.py index 957b4e1..c688f88 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -5,8 +5,13 @@ import pytest -from scitrack import (CachingLogger, get_file_hexdigest, get_package_name, - get_text_hexdigest, get_version_for_package) +from scitrack import ( + CachingLogger, + get_file_hexdigest, + get_package_name, + get_text_hexdigest, + get_version_for_package, +) __author__ = "Gavin Huttley" __copyright__ = "Copyright 2016-2021, Gavin Huttley" @@ -202,7 +207,7 @@ def test_get_version_for_package(): pyfile = TEST_ROOTDIR / "delme.py" pyfile.write_text("\n".join(["def version():", " return 'my-version'"])) sys.path.append(TEST_ROOTDIR) - import delme # noqa + import delme got = get_version_for_package("delme") assert got == "my-version" @@ -210,7 +215,7 @@ def test_get_version_for_package(): # func returns a list pyfile.write_text("version = ['my-version']\n") - from importlib import reload # noqa + from importlib import reload got = get_version_for_package(reload(delme)) assert got == "my-version" @@ -270,7 +275,7 @@ def test_mdsum_input(logfile): LOGGER.input_file(TEST_ROOTDIR / "sample-crlf.fasta") LOGGER.shutdown() - with open(logfile, "r") as infile: + with open(logfile) as infile: num = 0 for line in infile: for h, p in hex_path: @@ -294,7 +299,7 @@ def test_mdsum_output(logfile): LOGGER.output_file(TEST_ROOTDIR / "sample-lf.fasta") LOGGER.shutdown() - with open(logfile, "r") as infile: + with open(logfile) as infile: num = 0 for line in infile: for h, p in hex_path: @@ -369,4 +374,4 @@ def test_read_from_written(tmp_path): expect = get_text_hexdigest(data) assert expect == ex, (expect, ex) got = get_file_hexdigest(p) - assert got == expect, f"FAILED: {repr(lf)}, {(ex, got)}" + assert got == expect, f"FAILED: {lf!r}, {(ex, got)}" From bf3279fbb173318fb7a19d91a683e9f9c63eb05e Mon Sep 17 00:00:00 2001 From: Gavin Huttley Date: Tue, 8 Oct 2024 09:22:13 +1100 Subject: [PATCH 07/10] DEV: pin ruff version --- noxfile.py | 14 ++++---------- pyproject.toml | 2 +- src/scitrack/__init__.py | 2 -- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/noxfile.py b/noxfile.py index 4a76bfc..76cbfae 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,19 +1,13 @@ import nox -dependencies = "numpy", "click", "pytest", "pytest-cov" - - -@nox.session(python=[f"3.{v}" for v in range(8, 11)]) +@nox.session(python=[f"3.{v}" for v in range(9, 13)]) def test(session): - session.install(*dependencies) - session.install(".") + session.install(".[test]") session.chdir("tests") session.run( "pytest", + "-s", "-x", - "--cov-report", - f"lcov:lcov-{session.python}.info", - "--cov", - "scitrack", + *session.posargs, ) diff --git a/pyproject.toml b/pyproject.toml index c95d868..aa0aa48 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ test = [ "numpy", "pytest", "pytest-cov", - "ruff", + "ruff==0.6.9", "nox"] [tool.ruff] diff --git a/src/scitrack/__init__.py b/src/scitrack/__init__.py index 1430e5a..9d5244f 100644 --- a/src/scitrack/__init__.py +++ b/src/scitrack/__init__.py @@ -194,8 +194,6 @@ def log_args(self, args=None): parent = inspect.currentframe().f_back args = inspect.getargvalues(parent).locals - # remove args whose value is a CachingLogger - # or a module for k in list(args): if type(args[k]) == self.__class__ or type(args[k]).__name__ == "module": del args[k] From b4eed4eaabd218ab86797008f7fbce9437f94a1c Mon Sep 17 00:00:00 2001 From: Gavin Huttley Date: Tue, 8 Oct 2024 09:27:36 +1100 Subject: [PATCH 08/10] DEV: updated github action --- .github/workflows/testing_develop.yml | 43 ++++++++++++++++----------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/.github/workflows/testing_develop.yml b/.github/workflows/testing_develop.yml index 3bc1d16..6a437ee 100644 --- a/.github/workflows/testing_develop.yml +++ b/.github/workflows/testing_develop.yml @@ -2,49 +2,56 @@ name: CI on: push: - branches: [ "develop" ] + branches-ignore: + - master pull_request: - branches: [ "develop" ] + branches-ignore: + - master jobs: tests: - name: "Python ${{ matrix.python-version }}" + name: "Python ${{ matrix.python-version }} (${{ matrix.os }})" runs-on: ${{ matrix.os }} strategy: matrix: - os: [macos-latest, ubuntu-latest, windows-latest] - python-version: ["3.8", "3.9", "3.10"] + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - uses: "actions/checkout@v2" - - uses: "actions/setup-python@v2" with: - python-version: "${{ matrix.python-version }}" - - name: "Install dependencies" + fetch-depth: 0 + + # Setup env + - uses: "actions/setup-python@v3" + with: + python-version: "${{ matrix.python-version }}" + + - name: "Installs for ${{ matrix.python-version }}" run: | - python -VV - python -m site - python -m pip install --upgrade pip setuptools wheel flit - python -m pip install --upgrade nox + python --version + pip install --upgrade pip wheel setuptools flit + pip install --upgrade nox - name: "Run nox for ${{ matrix.python-version }}" - run: "nox -s test-${{ matrix.python-version }}" + run: "nox -s test-${{ matrix.python-version }} -- --cov-report lcov:lcov-${{matrix.os}}-${{matrix.python-version}}.lcov --cov-report term --cov-append --cov scitrack" - name: Coveralls Parallel - uses: coverallsapp/github-action@master + uses: coverallsapp/github-action@v2 with: parallel: true github-token: ${{ secrets.github_token }} - flag-name: run-${{ matrix.test_number }} - path-to-lcov: "tests/lcov-${{ matrix.python-version }}.info" + flag-name: run-${{matrix.python-version}}-${{matrix.os}} + file: "tests/lcov-${{matrix.os}}-${{matrix.python-version}}.lcov" finish: + name: "Finish Coveralls" needs: tests runs-on: ubuntu-latest steps: - name: Coveralls Finished - uses: coverallsapp/github-action@master + uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.github_token }} - parallel-finished: true + parallel-finished: true \ No newline at end of file From 2fbeca23da93107f361601261438c6acf88cf431 Mon Sep 17 00:00:00 2001 From: Gavin Huttley Date: Tue, 8 Oct 2024 09:27:49 +1100 Subject: [PATCH 09/10] DEV: added dependabot --- .github/dependabot.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..940764d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +version: 2 +updates: + - package-ecosystem: pip + directory: "/" + schedule: + interval: weekly + time: "19:00" + open-pull-requests-limit: 10 + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + time: "19:00" + open-pull-requests-limit: 10 From 669ec43aebcbd2ada0f4fa50528e46bc4c7d9b85 Mon Sep 17 00:00:00 2001 From: Gavin Huttley Date: Tue, 8 Oct 2024 09:29:16 +1100 Subject: [PATCH 10/10] DEV: added release action --- .github/workflows/release.yml | 118 ++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..aae262e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,118 @@ +name: Release + +on: [workflow_dispatch] + +jobs: + test: + name: "Test on Python ${{ matrix.python-version }} (${{ matrix.os }})" + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.9", "3.10", "3.11", "3.12"] + steps: + - uses: "actions/checkout@v4" + with: + fetch-depth: 0 + + - uses: "actions/setup-python@v5" + with: + python-version: "${{ matrix.python-version }}" + + - name: "Installs for ${{ matrix.python-version }}" + run: | + pip install --upgrade pip + pip install nox uv + + - name: "Run nox for Python ${{ matrix.python-version }}" + run: "nox -db uv -s test-${{ matrix.python-version }}" + + docbuild: + name: "Build docs" + runs-on: ubuntu-latest + steps: + - uses: "actions/checkout@v4" + with: + fetch-depth: 0 + + - uses: "actions/setup-python@v5" + with: + python-version: "3.12" + + - name: "Installs for docs" + run: | + pip install --upgrade pip uv + python -m uv pip install ".[doc]" + + - name: "making docs" + run: | + cd doc + make html + + build: + name: Build wheel and sdist + needs: test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install build dependency + run: | + pip install build + pip install --upgrade pip + + - name: Build sdist and wheel + run: python -m build --wheel --sdist + + - name: Upload sdist and wheel + uses: actions/upload-artifact@v4 + with: + name: scitrack-wheel-sdist + path: | + ./dist/*.whl + ./dist/*.tar.gz + + release_test: + name: Release to Test PyPI + needs: [build, docbuild] + environment: release_test + runs-on: ubuntu-latest + permissions: + id-token: write + + steps: + - name: Download sdist and wheel + uses: actions/download-artifact@v4 + with: + name: scitrack-wheel-sdist + path: ./dist + + - name: Publish package distributions to Test PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + + release: + name: Release to PyPI + needs: release_test + environment: release + runs-on: ubuntu-latest + permissions: + id-token: write + + steps: + - name: Download sdist and wheel + uses: actions/download-artifact@v4 + with: + name: scitrack-wheel-sdist + path: ./dist + + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 \ No newline at end of file