diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 7680f05..0000000 --- a/.coveragerc +++ /dev/null @@ -1,13 +0,0 @@ -[run] -omit = - */__init__.py, - *manage.py, - *settings*.py, - .idea/*, - *logs/*, - *migrations/*, - *tests/*, - *test_*.py, - *tests.py, - *conftest.py, - */extra* diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 8b5d752..0000000 --- a/.flake8 +++ /dev/null @@ -1,13 +0,0 @@ -[flake8] -select = B,B9,BLK,C,E,F,I,S,W -# enable docstring check(D) for future testing -ignore = E203,E501,W503 -application-import-names = tests -docstring-convention = google -import-order-style = google -max-complexity = 10 -max-line-length = 80 -per-file-ignores = - tests/tests_*:S101,S106 - src/tests/*:S101,S106 - src/*.py:S101,S106 diff --git a/.github/workflows/nox-lint_multi-arch.yml b/.github/workflows/nox-lint_multi-arch.yml new file mode 100644 index 0000000..64a0719 --- /dev/null +++ b/.github/workflows/nox-lint_multi-arch.yml @@ -0,0 +1,21 @@ +name: nox-lint_multi-arch +on: push +jobs: + tests: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13"] + name: Python ${{ matrix.python-version }} xfce-repocapp + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + allow-prereleases: true + architecture: x64 + - run: pip install nox==2024.10.09 + - run: pip install poetry==1.8.5 + - run: | + nox -s lint-${{ matrix.python-version }} diff --git a/noxfile.py b/noxfile.py index e33b23f..567b8f6 100644 --- a/noxfile.py +++ b/noxfile.py @@ -4,7 +4,8 @@ import nox -nox.options.sessions = "lint", "safety", "tests" +PYTHON_VERSIONS = ["3.10", "3.11", "3.12", "3.13"] +nox.options.sessions = "lint", "tests" locations = ( "src", "./noxfile.py", @@ -41,64 +42,28 @@ def install_with_constraints(session, *args, **kwargs): session.install(f"--requirement={requirements.name}", *args, **kwargs) -@nox.session(python=["3.12", "3.11", "3.10", "3.9", "3.8"]) -def black(session): - """Run black code formatter.""" - args = session.posargs or locations - install_with_constraints(session, "black") - session.run("black", *args) - - -@nox.session(python=["3.12", "3.11", "3.10", "3.9", "3.8"]) +@nox.session(python=PYTHON_VERSIONS) def docs(session): """Build the documentation.""" install_with_constraints(session, "sphinx") session.run("sphinx-build", "docs", "docs/_build") -@nox.session(python=["3.12", "3.11", "3.10", "3.9", "3.8"]) +@nox.session(python=PYTHON_VERSIONS) def lint(session): - """Lint using flake8.""" + """Lint using ruff.""" args = session.posargs or locations install_with_constraints( session, - "flake8", - "flake8-bandit", - "flake8-black", - "flake8-bugbear", - # "flake8-docstrings", - "flake8-import-order", + "ruff", ) - session.run("flake8", *args) - - -@nox.session(python=["3.12", "3.11", "3.10", "3.9", "3.8"]) -def safety(session): - """Scan dependencies for insecure packages.""" - with tempfile.NamedTemporaryFile() as requirements: - session.run( - "poetry", - "export", - "--with", - "dev", - "--format=requirements.txt", - "--without-hashes", - f"--output={requirements.name}", - external=True, - ) - install_with_constraints(session, "safety") - session.run( - "safety", - "check", - f"--file={requirements.name}", - "--full-report", - ) + session.run("ruff", "check", ".", *args) -@nox.session(python=["3.12", "3.11", "3.10", "3.9", "3.8"]) +@nox.session(python=PYTHON_VERSIONS) def tests(session): """Run the test suite.""" - args = session.posargs or ["--cov"] + args = session.posargs with tempfile.NamedTemporaryFile() as requirements: session.run( "poetry", diff --git a/poetry.lock b/poetry.lock index 50f52e8..fdefc66 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "alabaster" @@ -686,6 +686,25 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] +[[package]] +name = "pytest-sugar" +version = "1.0.0" +description = "pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly)." +optional = false +python-versions = "*" +files = [ + {file = "pytest-sugar-1.0.0.tar.gz", hash = "sha256:6422e83258f5b0c04ce7c632176c7732cab5fdb909cb39cca5c9139f81276c0a"}, + {file = "pytest_sugar-1.0.0-py3-none-any.whl", hash = "sha256:70ebcd8fc5795dc457ff8b69d266a4e2e8a74ae0c3edc749381c64b5246c8dfd"}, +] + +[package.dependencies] +packaging = ">=21.3" +pytest = ">=6.2.0" +termcolor = ">=2.1.0" + +[package.extras] +dev = ["black", "flake8", "pre-commit"] + [[package]] name = "pyyaml" version = "6.0.2" @@ -936,6 +955,20 @@ lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] +[[package]] +name = "termcolor" +version = "2.5.0" +description = "ANSI color formatting for output in terminal" +optional = false +python-versions = ">=3.9" +files = [ + {file = "termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8"}, + {file = "termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f"}, +] + +[package.extras] +tests = ["pytest", "pytest-cov"] + [[package]] name = "tomli" version = "2.2.1" @@ -1039,4 +1072,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" python-versions = ">=3.10.0, <3.14.0" -content-hash = "dec73695808271f5809b9cd20c7207bf640058effd0985b5264902448d14a0ba" +content-hash = "e6991f119872b7d7a1dec34cc3e64c8f6dbca0598994dd7812eff46923a68dd9" diff --git a/pyproject.toml b/pyproject.toml index 570c95f..b41f7ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,31 +20,14 @@ ruff = ">=0.8, <1.0" nox = "2024.10.09" pytest = ">=8.0, <8.4" pytest-cov = ">=6.0.0, <7.0" +pytest-sugar = "1.0.0" coverage = { version = ">=7.0.0, <8.0.0", extras = ["toml"] } pylint = "^3.0" sphinx = ">= 8.0, <9.0" -[tool.black] -line-length = 79 -# include = -exclude = ''' -/( - \.git - | __pycache__ - | .pytest_cache - | .idea - | logs - | .venv - | build - | dist -)/ -''' - -[tool.coverage.paths] -source = ["src"] - [tool.coverage.run] branch = true +dynamic_context = "test_function" source = ["src"] omit = [ "*/__init__.py", @@ -61,13 +44,6 @@ precision = 2 show_contexts = true title = "xfce-repocapp coverage report" -[tool.isort] -profile = "black" -multi_line_output = 3 -skip = ["*.gitignore"] -extend_skip = ["*.md", "*.json"] -line_length = 79 - [tool.pytest.ini_options] addopts = [ "--import-mode=importlib", @@ -82,10 +58,10 @@ respect-gitignore = true unsafe-fixes = false # Same as Black. -line-length = 79 +line-length = 88 -# Assume Python 3.10. -target-version = "py312" +# Assume Python 3.13.x +target-version = "py313" # Exclude a variety of commonly ignored directories. exclude = [ @@ -97,12 +73,29 @@ exclude = [ "_build", "build", "dist", + "htmlcov", ] [tool.ruff.lint] # Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default. -select = ["E", "F"] -ignore = [] +select = [ + "B", # flake8-bugbear + "C90", # mccabe + "E", # pycodestyle errors + "F", # pyflakes + "I", # isort + "S", # flake8-bandit + "W", # pycodestyle warnings + "RUF", # ruff checks +] +ignore = [ + "E501", # line too long ({width} > {limit} characters) + # "E203", # slice notation whitespace (not currently supported) + "E402", # module level import not at top of file + "E722", # do not use bare except + # "W503", # (not currently supported) + "ERA", # do not autoremove commented out code +] # Allow autofix for all enabled rules (when `--fix`) is provided. fixable = ["A", "B", "C", "D", "E", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "INP", "ISC", "NPY", "PD", "PGH", "PIE", "PL", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT"] diff --git a/requirements-dev.txt b/requirements-dev.txt index 76b9cbc..6fb691c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -193,9 +193,9 @@ exceptiongroup==1.2.2 ; python_full_version >= "3.10.0" and python_version < "3. filelock==3.16.1 ; python_full_version >= "3.10.0" and python_full_version < "3.14.0" \ --hash=sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0 \ --hash=sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435 -identify==2.6.3 ; python_full_version >= "3.10.0" and python_full_version < "3.14.0" \ - --hash=sha256:62f5dae9b5fef52c84cc188514e9ea4f3f636b1d8799ab5ebc475471f9e47a02 \ - --hash=sha256:9edba65473324c2ea9684b1f944fe3191db3345e50b6d04571d10ed164f8d7bd +identify==2.6.4 ; python_full_version >= "3.10.0" and python_full_version < "3.14.0" \ + --hash=sha256:285a7d27e397652e8cafe537a6cc97dd470a970f48fb2e9d979aa38eae5513ac \ + --hash=sha256:993b0f01b97e0568c179bb9196391ff391bfb88a99099dbf5ce392b68f42d0af idna==3.10 ; python_version >= "3.10" and python_full_version < "3.14.0" \ --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 @@ -306,6 +306,9 @@ pytest==8.3.4 ; python_full_version >= "3.10.0" and python_full_version < "3.14. pytest-cov==6.0.0 ; python_full_version >= "3.10.0" and python_full_version < "3.14.0" \ --hash=sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35 \ --hash=sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0 +pytest-sugar==1.0.0 ; python_full_version >= "3.10.0" and python_full_version < "3.14.0" \ + --hash=sha256:6422e83258f5b0c04ce7c632176c7732cab5fdb909cb39cca5c9139f81276c0a \ + --hash=sha256:70ebcd8fc5795dc457ff8b69d266a4e2e8a74ae0c3edc749381c64b5246c8dfd pyyaml==6.0.2 ; python_full_version >= "3.10.0" and python_full_version < "3.14.0" \ --hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \ --hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \ @@ -406,6 +409,9 @@ sphinxcontrib-qthelp==2.0.0 ; python_version >= "3.10" and python_full_version < sphinxcontrib-serializinghtml==2.0.0 ; python_version >= "3.10" and python_full_version < "3.14.0" \ --hash=sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331 \ --hash=sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d +termcolor==2.5.0 ; python_full_version >= "3.10.0" and python_full_version < "3.14.0" \ + --hash=sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8 \ + --hash=sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f tomli==2.2.1 ; python_version >= "3.10" and python_full_version <= "3.11.0a6" \ --hash=sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6 \ --hash=sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd \ diff --git a/run b/run index 74a9fbf..e8a3492 100755 --- a/run +++ b/run @@ -18,11 +18,16 @@ fi # ----------------------------------------------------------------------------- -function cov:pytest { +function cov:test { # Run pytest coverage run -m pytest "${@}" } +function cov:test-slow { + # Run pytest & show 10 slowest tests + coverage run -m pytest "${@}" --durations 10 +} + function cov:report { # Generate coverage report to terminal coverage report "${@}" @@ -33,46 +38,67 @@ function cov:html { coverage html "${@}" } -function nox:black { +function nox:cov { # Check code for formatting violations - nox -s black-3.11 "${@}" + nox -s coverage-3.13 "${@}" } function nox:docs { # Create Sphinx documentation in ./docs/_build directory - nox -s docs-3.11 "${@}" + nox -s docs-3.13 "${@}" } function nox:lint { # Run a number of flake8 linting tests - nox -s lint-3.11 "${@}" + nox -s lint-3.13 "${@}" } -function nox:safety { - # Check code/packages for vulnerabilites against Safety db - nox -s safety-3.11 "${@}" +function nox:pyright { + # Run pyright typing checks + # cmd nox -s pyright-3.13 "${@}" + nox -s pyright-3.13 "${@}" } -function nox:tests { +function nox:test { # Run several pytest and coverage checks - nox -s tests-3.11 "${@}" - # cmd black . "${@}" + # cmd nox -s tests-3.13 "${@}" + nox -s tests-3.13 "${@}" +} + +function nox:tests { + # Run tests for all currently supported Python versions + nox -s tests-3.10 tests-3.11 tests-3.12 tests-3.13 "${@}" +} + +function nox:re-tests { + # Run tests for all currently supported Python versions + nox -rs tests-3.10 tests-3.11 tests-3.12 tests-3.13 "${@}" +} + +function nox:current { + # Run code quality tests only for latest supported Python version + nox -s lint-3.13 coverage-3.13 safety-3.13 tests-3.13 "${@}" +} + +function nox:re-current { + # Run tests only for latest supported Python version + # DO NOT REBUILD VENVS + nox -s lint-3.13 coverage-3.13 safety-3.13 tests-3.13 "${@}" } function nox:all { - # Perform all nox code quality tests together(except black) - # cmd nox -s black-3.11 "${@}" - nox -s lint-3.11 "${@}" - nox -s safety-3.11 "${@}" - nox -s tests-3.11 "${@}" + # Perform all nox code quality tests together + nox } +function nox:re-all { + # Perform all nox code quality tests together + # DO NOT REBUILD VENVS + nox -rs +} function poe:old { # List any installed packages that are outdated poetry show -ol - # The following command runs twice as fast; but, requires the container - # to be running: - # cmd poetry show -ol } function poe:up { @@ -96,9 +122,14 @@ function reqs:all { poetry export --with=dev --output requirements-dev.txt } -function secret { - # Generate a random secret that can be used for your SECRET_KEY and more - python3 -c 'import secrets; print(secrets.token_urlsafe(38))' +function ruff:check { + # check for linting errors. Do not fix. + ruff check . +} + +function ruff:fix { + # check for linting errors and apply fixes. + ruff check --fix . } function help { diff --git a/src/repocapp.py b/src/repocapp.py index 3ddb3a1..28458d5 100755 --- a/src/repocapp.py +++ b/src/repocapp.py @@ -12,7 +12,7 @@ """ import os -import subprocess +import subprocess # noqa: S404 import sys from pathlib import Path @@ -79,15 +79,13 @@ def main_menu(): """Display selection of available actions to take with repositories.""" - os.system("clear") - main_banner = ( - "\u2248: xfce-repocapp: local " "Xfce repository maintenance :\u2248" - ) + os.system("/usr/bin/clear") # noqa: S605 + main_banner = "\u2248: xfce-repocapp: local Xfce repository maintenance :\u2248" border = "\u2248" * len(main_banner) print(f"{border}\n{main_banner}\n{border}") main_list = list(menus.keys()) selection = range(1, len(main_list) + 1) - for select, m_list in zip(selection, main_list): + for select, m_list in zip(selection, main_list, strict=False): print(f"{select}. {m_list.title()}") print(f"{border}") question = f"Please enter your choice[1-{len(menus)}]: " @@ -110,14 +108,12 @@ def main_menu(): def sub_menus(action): """Display actions to take upon a specific repository.""" - os.system("clear") - banner = ( - f"\u2248: xfce-repocapp: {action} " f"local Xfce repositories :\u2248" - ) + os.system("/usr/bin/clear") # noqa: S605 + banner = f"\u2248: xfce-repocapp: {action} local Xfce repositories :\u2248" border = "\u2248" * len(banner) print(f"{border}\n{banner}\n{border}") selection = list(range(1, len(menus[action]) + 1)) - for select, component in zip(selection, menus[action]): + for select, component in zip(selection, menus[action], strict=False): print(f"{select}. {action.title()} {component}") # Add numbers to selection list for menu options not in action list. selection.append(selection[-1] + 1) @@ -144,7 +140,7 @@ def sub_menus(action): component = component_list[answer - 1] script = action + "_xfce.py" command = f"{path}/{script} -c {component}" - subprocess.run([command], shell=True) + subprocess.run([command], shell=True) # noqa: S602 press_any_key() main_menu() except (ValueError, EOFError): diff --git a/tests/test_build_xfce.py b/tests/test_build_xfce.py index 3c7ecb7..bd9f1a1 100644 --- a/tests/test_build_xfce.py +++ b/tests/test_build_xfce.py @@ -13,7 +13,6 @@ import unittest from build_xfce import build_xfce # noqa: F401 - from cappdata import component_list # noqa: F401 arg = "bindings" diff --git a/tests/test_clean_xfce.py b/tests/test_clean_xfce.py index 868bdf7..c6d02ea 100644 --- a/tests/test_clean_xfce.py +++ b/tests/test_clean_xfce.py @@ -13,7 +13,6 @@ import unittest from cappdata import component_list # noqa: F401 - from clean_xfce import clean_xfce # noqa: F401 args = "bindings" diff --git a/tests/test_clone_xfce.py b/tests/test_clone_xfce.py index a2e0e65..8b88262 100644 --- a/tests/test_clone_xfce.py +++ b/tests/test_clone_xfce.py @@ -13,7 +13,6 @@ import unittest from cappdata import component_list # noqa: F401 - from clone_xfce import clone_xfce # noqa: F401 args = "bindings" diff --git a/tests/test_install_xfce.py b/tests/test_install_xfce.py index 43ad0ab..17d0e1d 100644 --- a/tests/test_install_xfce.py +++ b/tests/test_install_xfce.py @@ -13,7 +13,6 @@ import unittest from cappdata import component_list, query_yes_no # noqa: F401 - from install_xfce import install_xfce # noqa: F401 args = "bindings" diff --git a/tests/test_pull_xfce.py b/tests/test_pull_xfce.py index 53f5f1c..986b49b 100644 --- a/tests/test_pull_xfce.py +++ b/tests/test_pull_xfce.py @@ -13,7 +13,6 @@ import unittest from cappdata import component_list # noqa: F401 - from pull_xfce import pull_xfce # noqa: F401 args = "bindings" diff --git a/tests/test_purge_xfce.py b/tests/test_purge_xfce.py index 2840b06..eaf567c 100644 --- a/tests/test_purge_xfce.py +++ b/tests/test_purge_xfce.py @@ -13,7 +13,6 @@ import unittest from cappdata import component_list, query_yes_no # noqa: F401 - from purge_xfce import purge_xfce # noqa: F401 args = "bindings"