diff --git a/.envrc b/.envrc index 4510ba7..5316393 100644 --- a/.envrc +++ b/.envrc @@ -1,10 +1,11 @@ # automatically activate dev environment when in project tree using direnv. -ACT=.nox/dev/bin/activate +ACT=".nox/dev/bin/activate" -if [ -e $ACT ] -then - source $ACT +if [[ -f "${ACT}" ]]; then + source "${ACT}" else - echo "Activation script ${ACT} not found for dev environment. -Run nox -s dev to install dev environment" + cat < str: """ Read a string from command line. @@ -28,7 +28,7 @@ def read(prompt, regex, default): print(f"the project name has to match the regular expression: {regex}") -def git_config_get(attr): +def git_config_get(attr: str) -> str | None: """ Get a git config value. """ @@ -38,11 +38,10 @@ def git_config_get(attr): return None -def main(): +def main() -> None: """ Rename the project. """ - author = git_config_get("user.name") email = git_config_get("user.email") origin = git_config_get("remote.origin.url") @@ -60,14 +59,17 @@ def main(): email = read("email", r".+", email) url = read("url", r".+", url) - replacements = { + replacements: dict[str, str] = { "author@fillname.org": email, "Author Fillname": author, "https://fillname.org/": url, "fillname": project, } - def replace(filepath): + def replace(filepath: str) -> None: + """ + Apply replacements to the given file. + """ with open(filepath, "r", encoding="utf-8") as hnd: content = hnd.read() for key, val in replacements.items(): diff --git a/noxfile.py b/noxfile.py index 3f211b9..05cdc55 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,14 +1,17 @@ +""" +Nox sessions for linting, type checking and testing. +""" + import os -import nox +import nox # type: ignore -nox.options.sessions = "lint_pylint", "typecheck", "test" +nox.options.sessions = "lint", "typecheck", "test" +nox.options.default_venv_backend = "uv|virtualenv" -EDITABLE_TESTS = True PYTHON_VERSIONS = None if "GITHUB_ACTIONS" in os.environ: PYTHON_VERSIONS = ["3.10", "3.14"] - EDITABLE_TESTS = False @nox.session @@ -22,12 +25,14 @@ def dev(session): @nox.session -def lint_pylint(session): +def lint(session): """ - Run pylint. + Run ruff. """ - session.install("-e", ".[lint_pylint]") - session.run("pylint", "fillname", "tests") + if not session.virtualenv._reused: + session.install(".[lint]") + session.run("ruff", "check") + session.run("ruff", "format", "--check") @nox.session @@ -35,8 +40,9 @@ def typecheck(session): """ Typecheck the code using mypy. """ - session.install("-e", ".[typecheck]") - session.run("mypy", "--strict", "-p", "fillname", "-p", "tests") + if not session.virtualenv._reused: + session.install(".[typecheck]") + session.run("ty", "check") @nox.session(python=PYTHON_VERSIONS) @@ -44,16 +50,12 @@ def test(session): """ Run the tests. - Accepts an additional arguments which are passed to the unittest module. - This can for example be used to selectively run test cases. + Accepts additional arguments which are passed to the pytest module. This + can for example be used to selectively run test cases via option `-k`. """ - - args = [".[test]"] - if EDITABLE_TESTS: - args.insert(0, "-e") - session.install(*args) + if not session.virtualenv._reused: + session.install(".[test]") if session.posargs: - session.run("coverage", "run", "-m", "unittest", session.posargs[0], "-v") + session.run("pytest", "-v", *session.posargs) else: - session.run("coverage", "run", "-m", "unittest", "discover", "-v") - session.run("coverage", "report", "-m", "--fail-under=100") + session.run("pytest", "--cov", "-v") diff --git a/pyproject.toml b/pyproject.toml index 075205f..b12a12a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" name = "fillname" authors = [{ name = "Author Fillname", email = "author@fillname.org" }] description = "A template project." -requires-python = ">=3.9" +requires-python = ">=3.10" license = { file = "LICENSE" } dynamic = ["version"] readme = "README.md" @@ -15,12 +15,11 @@ readme = "README.md" Homepage = "https://fillname.org/" [project.optional-dependencies] -format = ["black", "isort", "autoflake"] -lint_pylint = ["pylint"] -typecheck = ["types-setuptools", "mypy"] -test = ["coverage[toml]"] +lint = ["ruff"] +typecheck = ["pytest", "ty"] +test = ["pytest", "pytest-cov", "coverage[toml]"] doc = ["mkdocs", "mkdocs-material", "mkdocstrings[python]", "mkdoclingo"] -dev = ["fillname[test,typecheck,lint_pylint]"] +dev = ["pre[test,typecheck,lint]"] [project.scripts] fillname = "fillname.__main__:main" @@ -39,35 +38,46 @@ line_length = 120 [tool.black] line-length = 120 -[tool.pylint.format] -max-line-length = 120 +[tool.ruff] +line-length = 120 +target-version = "py310" +exclude = [ + "/usr/**", + "**/site-packages/**", + "**/node_modules/**", + "**/typeshed-fallback/**", +] -[tool.pylint.design] -max-args = 10 -max-attributes = 7 -max-bool-expr = 5 -max-branches = 12 -max-locals = 30 -max-parents = 7 -max-public-methods = 20 -max-returns = 10 -max-statements = 50 -min-public-methods = 1 +[tool.ruff.lint] +extend-select = ["I", "E", "F", "B", "C4", "D", "ARG", "SIM", "ANN", "Q"] +ignore = [ + "E741", + "D200", + "D203", + "D212", + "D413", + "B023", # broken lint +] -[tool.pylint.similarities] -ignore-comments = true -ignore-docstrings = true -ignore-imports = true -ignore-signatures = true +[tool.ruff.lint.per-file-ignores] +"noxfile.py" = ["ANN"] -[tool.pylint.basic] -argument-rgx = "^[a-z][a-z0-9]*((_[a-z0-9]+)*_?)?$" -variable-rgx = "^[a-z][a-z0-9]*((_[a-z0-9]+)*_?)?$" -good-names = ["_"] +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = "test_*.py" +python_functions = "test_*" [tool.coverage.run] source = ["fillname", "tests"] -omit = ["*/fillname/__main__.py"] +omit = ["*/fillname/__main__.py", "*/fillname/utils/logging.py"] [tool.coverage.report] -exclude_lines = ["assert", "nocoverage"] +show_missing = true +fail_under = 100 +exclude_lines = [ + "raise NotImplementedError", + "raise TypeError", + "nocoverage", + "assert", + "\\.\\.\\.", +] diff --git a/src/fillname/utils/logging.py b/src/fillname/utils/logging.py index 2ca276e..7680d08 100644 --- a/src/fillname/utils/logging.py +++ b/src/fillname/utils/logging.py @@ -33,12 +33,18 @@ class SingleLevelFilter(logging.Filter): passlevel: int reject: bool - def __init__(self, passlevel: int, reject: bool): + def __init__(self, passlevel: int, reject: bool) -> None: + """ + Initialize the filter. + """ # pylint: disable=super-init-not-called self.passlevel = passlevel self.reject = reject def filter(self, record: logging.LogRecord) -> bool: + """ + Apply custom filter for records. + """ if self.reject: return record.levelno != self.passlevel # nocoverage @@ -63,6 +69,9 @@ def make_handler(level: int, color: str) -> "logging.StreamHandler[TextIO]": handler.setFormatter(formatter) return handler + for h in logging.root.handlers[:]: + logging.root.removeHandler(h) + handlers = [ make_handler(logging.INFO, "GREEN"), make_handler(logging.WARNING, "YELLOW"), diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..b9cf801 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,3 @@ +""" +The tests package contains all the test cases for the project. +"""