diff --git a/.github/workflows/docs-ci.yml b/.github/workflows/docs-ci.yml index ada779b..8d8aa55 100644 --- a/.github/workflows/docs-ci.yml +++ b/.github/workflows/docs-ci.yml @@ -4,31 +4,29 @@ on: [push, pull_request] jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 strategy: max-parallel: 4 matrix: - python-version: [3.9] + python-version: [3.13] steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install Dependencies - run: pip install -e .[docs] + run: ./configure --dev - - name: Check Sphinx Documentation build minimally - working-directory: ./docs - run: sphinx-build -E -W source build + - name: Check documentation and HTML for errors and dead links + run: make docs-check - - name: Check for documentation style errors - working-directory: ./docs - run: ./scripts/doc8_style_check.sh + - name: Check documentation for style errors + run: make doc8 diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index 9585730..d41fbf2 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -21,23 +21,26 @@ on: jobs: build-pypi-distribs: name: Build and publish library to PyPI - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: - python-version: 3.9 + python-version: 3.12 - - name: Install pypa/build - run: python -m pip install build --user + - name: Install pypa/build and twine + run: python -m pip install --user --upgrade build twine pkginfo - name: Build a binary wheel and a source tarball - run: python -m build --sdist --wheel --outdir dist/ + run: python -m build --wheel --sdist --outdir dist/ + + - name: Validate wheel and sdis for Pypi + run: python -m twine check dist/* - name: Upload built archives - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: pypi_archives path: dist/* @@ -47,17 +50,17 @@ jobs: name: Create GH release needs: - build-pypi-distribs - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - name: Download built archives - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: pypi_archives path: dist - name: Create GH release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: draft: true files: dist/* @@ -67,11 +70,11 @@ jobs: name: Create PyPI release needs: - create-gh-release - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - name: Download built archives - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: pypi_archives path: dist diff --git a/.gitignore b/.gitignore index 2d48196..4818bb3 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,5 @@ tcl # Ignore Jupyter Notebook related temp files .ipynb_checkpoints/ +/.ruff_cache/ +.env \ No newline at end of file diff --git a/.readthedocs.yml b/.readthedocs.yml index 8ab2368..683f3a8 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -26,4 +26,4 @@ python: - method: pip path: . extra_requirements: - - docs + - dev diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7f44d0a..705b850 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,11 @@ Changelog ========= +v31.1.1 - 2025-10-01 +------------------------ + +- Add ~ to valid characters in ``_is_valid_version`` +- Use latest skeleton files v31.1.0 - 2024-02-01 ------------------------ diff --git a/MANIFEST.in b/MANIFEST.in index 8424cbe..8bd7c69 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,6 @@ graft src +graft docs +graft etc include *.LICENSE include NOTICE @@ -6,10 +8,18 @@ include *.ABOUT include *.toml include *.yml include *.rst +include *.png include setup.* include configure* include requirements* include .giti* +include .dockerignore +include .readthedocs.yml +include manage.py +include Dockerfile* +include Makefile +include MANIFEST.in -global-exclude *.py[co] __pycache__ *.*~ +include .VERSION +global-exclude *.py[co] __pycache__ *.*~ diff --git a/Makefile b/Makefile index cc36c35..3041547 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ # ScanCode is a trademark of nexB Inc. # SPDX-License-Identifier: Apache-2.0 # See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/skeleton for support or download. +# See https://github.com/aboutcode-org/skeleton for support or download. # See https://aboutcode.org for more information about nexB OSS projects. # @@ -13,31 +13,33 @@ PYTHON_EXE?=python3 VENV=venv ACTIVATE?=. ${VENV}/bin/activate; -dev: - @echo "-> Configure the development envt." - ./configure --dev -isort: - @echo "-> Apply isort changes to ensure proper imports ordering" - ${VENV}/bin/isort --sl -l 100 src tests setup.py +conf: + @echo "-> Install dependencies" + ./configure -black: - @echo "-> Apply black code formatter" - ${VENV}/bin/black -l 100 src tests setup.py +dev: + @echo "-> Configure and install development dependencies" + ./configure --dev doc8: @echo "-> Run doc8 validation" - @${ACTIVATE} doc8 --max-line-length 100 --ignore-path docs/_build/ --quiet docs/ + @${ACTIVATE} doc8 --quiet docs/ *.rst -valid: isort black +valid: + @echo "-> Run Ruff format" + @${ACTIVATE} ruff format + @echo "-> Run Ruff linter" + @${ACTIVATE} ruff check --fix check: - @echo "-> Run pycodestyle (PEP8) validation" - @${ACTIVATE} pycodestyle --max-line-length=100 --exclude=.eggs,venv,lib,thirdparty,docs,migrations,settings.py,.cache . - @echo "-> Run isort imports ordering validation" - @${ACTIVATE} isort --sl --check-only -l 100 setup.py src tests . - @echo "-> Run black validation" - @${ACTIVATE} black --check --check -l 100 src tests setup.py + @echo "-> Run Ruff linter validation (pycodestyle, bandit, isort, and more)" + @${ACTIVATE} ruff check + @echo "-> Run Ruff format validation" + @${ACTIVATE} ruff format --check + @$(MAKE) doc8 + @echo "-> Run ABOUT files validation" + @${ACTIVATE} about check etc/ clean: @echo "-> Clean the Python env" @@ -49,6 +51,10 @@ test: docs: rm -rf docs/_build/ - @${ACTIVATE} sphinx-build docs/ docs/_build/ + @${ACTIVATE} sphinx-build docs/source docs/_build/ + +docs-check: + @${ACTIVATE} sphinx-build -E -W -b html docs/source docs/_build/ + @${ACTIVATE} sphinx-build -E -W -b linkcheck docs/source docs/_build/ -.PHONY: conf dev check valid black isort clean test docs +.PHONY: conf dev check valid clean test docs docs-check diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b07b0f9..98579ec 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -9,74 +9,66 @@ jobs: # These jobs are using VMs and Azure-provided Pythons 3.8 ################################################################################ - - template: etc/ci/azure-posix.yml - parameters: - job_name: ubuntu20_cpython - image_name: ubuntu-20.04 - python_versions: ["3.7", "3.8", "3.9", "3.10", "3.11"] - test_suites: - all: venv/bin/pytest -n 2 -vvs + - template: etc/ci/azure-posix.yml + parameters: + job_name: run_code_checks + image_name: ubuntu-24.04 + python_versions: ['3.13'] + test_suites: + all: make check - - template: etc/ci/azure-posix.yml - parameters: - job_name: ubuntu22_cpython - image_name: ubuntu-22.04 - python_versions: ["3.7", "3.8", "3.9", "3.10", "3.11"] - test_suites: - all: venv/bin/pytest -n 2 -vvs + - template: etc/ci/azure-posix.yml + parameters: + job_name: ubuntu22_cpython + image_name: ubuntu-22.04 + python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] + test_suites: + all: venv/bin/pytest -n 2 -vvs - - template: etc/ci/azure-posix.yml - parameters: - job_name: macos12_cpython - image_name: macOS-12 - python_versions: ["3.7", "3.8", "3.9", "3.10", "3.11"] - test_suites: - all: venv/bin/pytest -n 2 -vvs + - template: etc/ci/azure-posix.yml + parameters: + job_name: ubuntu24_cpython + image_name: ubuntu-24.04 + python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] + test_suites: + all: venv/bin/pytest -n 2 -vvs - - template: etc/ci/azure-posix.yml - parameters: - job_name: macos13_cpython - image_name: macOS-13 - python_versions: ["3.7", "3.8", "3.9", "3.10", "3.11"] - test_suites: - all: venv/bin/pytest -n 2 -vvs + - template: etc/ci/azure-posix.yml + parameters: + job_name: macos13_cpython + image_name: macOS-13 + python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] + test_suites: + all: venv/bin/pytest -n 2 -vvs - - template: etc/ci/azure-win.yml - parameters: - job_name: win2019_cpython - image_name: windows-2019 - python_versions: ["3.7", "3.8", "3.9", "3.10", "3.11"] - test_suites: - all: venv\Scripts\pytest -n 2 -vvs + - template: etc/ci/azure-posix.yml + parameters: + job_name: macos14_cpython + image_name: macOS-14 + python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] + test_suites: + all: venv/bin/pytest -n 2 -vvs - - template: etc/ci/azure-win.yml - parameters: - job_name: win2022_cpython - image_name: windows-2022 - python_versions: ["3.7", "3.8", "3.9", "3.10", "3.11"] - test_suites: - all: venv\Scripts\pytest -n 2 -vvs + - template: etc/ci/azure-posix.yml + parameters: + job_name: macos15_cpython + image_name: macOS-15 + python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] + test_suites: + all: venv/bin/pytest -n 2 -vvs - ################################################################################ - # Tests using a plain pip install to get the latest of all wheels - ################################################################################ - - - template: etc/ci/azure-posix.yml - parameters: - job_name: ubuntu20_cpython_latest_from_pip - image_name: ubuntu-20.04 - python_versions: ["3.7", "3.8", "3.9", "3.10"] - test_suites: - all: - venv/bin/pip install --upgrade-strategy eager --force-reinstall - --upgrade -e . && venv/bin/pytest -n 2 -vvs + - template: etc/ci/azure-win.yml + parameters: + job_name: win2022_cpython + image_name: windows-2022 + python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] + test_suites: + all: venv\Scripts\pytest -n 2 -vvs - - template: etc/ci/azure-win.yml - parameters: - job_name: win2019_cpython_latest_from_pip - image_name: windows-2019 - python_versions: ["3.7", "3.8", "3.9", "3.10"] - test_suites: - all: - venv\Scripts\pip install --upgrade-strategy eager --force-reinstall - --upgrade -e . && venv\Scripts\pytest -n 2 -vvs + - template: etc/ci/azure-win.yml + parameters: + job_name: win2025_cpython + image_name: windows-2025 + python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] + test_suites: + all: venv\Scripts\pytest -n 2 -vvs diff --git a/configure b/configure index 926a894..6d317d4 100755 --- a/configure +++ b/configure @@ -3,7 +3,7 @@ # Copyright (c) nexB Inc. and others. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/ for support or download. +# See https://github.com/aboutcode-org/ for support or download. # See https://aboutcode.org for more information about nexB OSS projects. # @@ -29,14 +29,13 @@ CLI_ARGS=$1 # Requirement arguments passed to pip and used by default or with --dev. REQUIREMENTS="--editable . --constraint requirements.txt" -DEV_REQUIREMENTS="--editable .[testing] --constraint requirements.txt --constraint requirements-dev.txt" -DOCS_REQUIREMENTS="--editable .[docs] --constraint requirements.txt" +DEV_REQUIREMENTS="--editable .[dev] --constraint requirements.txt --constraint requirements-dev.txt" # where we create a virtualenv VIRTUALENV_DIR=venv # Cleanable files and directories to delete with the --clean option -CLEANABLE="build dist venv .cache .eggs" +CLEANABLE="build dist venv .cache .eggs *.egg-info docs/_build/ pip-selfcheck.json" # extra arguments passed to pip PIP_EXTRA_ARGS=" " @@ -111,7 +110,7 @@ create_virtualenv() { fi $PYTHON_EXECUTABLE "$VIRTUALENV_PYZ" \ - --wheel embed --pip embed --setuptools embed \ + --pip embed --setuptools embed \ --seeder pip \ --never-download \ --no-periodic-update \ @@ -168,6 +167,7 @@ clean() { for cln in $CLEANABLE; do rm -rf "${CFG_ROOT_DIR:?}/${cln:?}"; done + find . -type f -name '*.py[co]' -delete -o -type d -name __pycache__ -delete set +e exit } @@ -185,7 +185,6 @@ while getopts :-: optchar; do help ) cli_help;; clean ) find_python && clean;; dev ) CFG_REQUIREMENTS="$DEV_REQUIREMENTS";; - docs ) CFG_REQUIREMENTS="$DOCS_REQUIREMENTS";; esac;; esac done diff --git a/configure.bat b/configure.bat index 5e95b31..15ab701 100644 --- a/configure.bat +++ b/configure.bat @@ -4,7 +4,7 @@ @rem Copyright (c) nexB Inc. and others. All rights reserved. @rem SPDX-License-Identifier: Apache-2.0 @rem See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -@rem See https://github.com/nexB/ for support or download. +@rem See https://github.com/aboutcode-org/ for support or download. @rem See https://aboutcode.org for more information about nexB OSS projects. @@ -27,8 +27,7 @@ @rem # Requirement arguments passed to pip and used by default or with --dev. set "REQUIREMENTS=--editable . --constraint requirements.txt" -set "DEV_REQUIREMENTS=--editable .[testing] --constraint requirements.txt --constraint requirements-dev.txt" -set "DOCS_REQUIREMENTS=--editable .[docs] --constraint requirements.txt" +set "DEV_REQUIREMENTS=--editable .[dev] --constraint requirements.txt --constraint requirements-dev.txt" @rem # where we create a virtualenv set "VIRTUALENV_DIR=venv" @@ -76,9 +75,6 @@ if not "%1" == "" ( if "%1" EQU "--dev" ( set "CFG_REQUIREMENTS=%DEV_REQUIREMENTS%" ) - if "%1" EQU "--docs" ( - set "CFG_REQUIREMENTS=%DOCS_REQUIREMENTS%" - ) shift goto again ) @@ -114,7 +110,7 @@ if not exist "%CFG_BIN_DIR%\python.exe" ( if exist "%CFG_ROOT_DIR%\etc\thirdparty\virtualenv.pyz" ( %PYTHON_EXECUTABLE% "%CFG_ROOT_DIR%\etc\thirdparty\virtualenv.pyz" ^ - --wheel embed --pip embed --setuptools embed ^ + --pip embed --setuptools embed ^ --seeder pip ^ --never-download ^ --no-periodic-update ^ @@ -130,7 +126,7 @@ if not exist "%CFG_BIN_DIR%\python.exe" ( ) ) %PYTHON_EXECUTABLE% "%CFG_ROOT_DIR%\%VIRTUALENV_DIR%\virtualenv.pyz" ^ - --wheel embed --pip embed --setuptools embed ^ + --pip embed --setuptools embed ^ --seeder pip ^ --never-download ^ --no-periodic-update ^ diff --git a/docs/Makefile b/docs/Makefile index 788b039..94f686b 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -7,7 +7,7 @@ SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SPHINXAUTOBUILD = sphinx-autobuild SOURCEDIR = source -BUILDDIR = build +BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: diff --git a/docs/scripts/doc8_style_check.sh b/docs/scripts/doc8_style_check.sh deleted file mode 100755 index 9416323..0000000 --- a/docs/scripts/doc8_style_check.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -# halt script on error -set -e -# Check for Style Code Violations -doc8 --max-line-length 100 source --ignore D000 --quiet \ No newline at end of file diff --git a/docs/scripts/sphinx_build_link_check.sh b/docs/scripts/sphinx_build_link_check.sh deleted file mode 100644 index c542686..0000000 --- a/docs/scripts/sphinx_build_link_check.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -# halt script on error -set -e -# Build locally, and then check links -sphinx-build -E -W -b linkcheck source build \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index 4494b80..ce1ff55 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -12,7 +12,7 @@ import pathlib import sys -srcdir = pathlib.Path(__file__).resolve().parents[2].joinpath('src') +srcdir = pathlib.Path(__file__).resolve().parents[2].joinpath("src") sys.path.insert(0, srcdir.as_posix()) @@ -36,13 +36,13 @@ "sphinx_rtd_theme", "sphinx_rtd_dark_mode", "sphinx.ext.extlinks", - "sphinx_copybutton" + "sphinx_copybutton", ] # Setting for sphinxcontrib.apidoc to automatically create API documentation. -apidoc_module_dir = srcdir.joinpath('debian_inspector').as_posix() -apidoc_separate_modules=True +apidoc_module_dir = srcdir.joinpath("debian_inspector").as_posix() +apidoc_separate_modules = True apidoc_module_first = True @@ -52,7 +52,7 @@ # This points to aboutcode.readthedocs.io # In case of "undefined label" ERRORS check docs on intersphinx to troubleshoot -# Link was created at commit - https://github.com/nexB/aboutcode/commit/faea9fcf3248f8f198844fe34d43833224ac4a83 +# Link was created at commit - https://github.com/aboutcode-org/aboutcode/commit/faea9fcf3248f8f198844fe34d43833224ac4a83 # Reference to other Sphinx documentations intersphinx_mapping = { @@ -104,7 +104,8 @@ html_show_sphinx = True # Define CSS and HTML abbreviations used in .rst files. These are examples. -# .. role:: is used to refer to styles defined in _static/theme_overrides.css and is used like this: :red:`text` +# .. role:: is used to refer to styles defined in _static/theme_overrides.css +# and is used like this: :red:`text` rst_prolog = """ .. |psf| replace:: Python Software Foundation diff --git a/etc/ci/azure-container-deb.yml b/etc/ci/azure-container-deb.yml index 85b611d..d80e8df 100644 --- a/etc/ci/azure-container-deb.yml +++ b/etc/ci/azure-container-deb.yml @@ -21,7 +21,7 @@ jobs: - job: ${{ parameters.job_name }} pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-22.04' container: image: ${{ parameters.container }} diff --git a/etc/ci/azure-container-rpm.yml b/etc/ci/azure-container-rpm.yml index 1e6657d..a64138c 100644 --- a/etc/ci/azure-container-rpm.yml +++ b/etc/ci/azure-container-rpm.yml @@ -1,6 +1,6 @@ parameters: job_name: '' - image_name: 'ubuntu-16.04' + image_name: 'ubuntu-22.04' container: '' python_path: '' python_version: '' diff --git a/etc/scripts/check_thirdparty.py b/etc/scripts/check_thirdparty.py index b052f25..65ae595 100644 --- a/etc/scripts/check_thirdparty.py +++ b/etc/scripts/check_thirdparty.py @@ -1,11 +1,10 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # # Copyright (c) nexB Inc. and others. All rights reserved. # ScanCode is a trademark of nexB Inc. # SPDX-License-Identifier: Apache-2.0 # See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/skeleton for support or download. +# See https://github.com/aboutcode-org/skeleton for support or download. # See https://aboutcode.org for more information about nexB OSS projects. # import click @@ -42,8 +41,7 @@ def check_thirdparty_dir( """ Check a thirdparty directory for problems and print these on screen. """ - # check for problems - print(f"==> CHECK FOR PROBLEMS") + print("==> CHECK FOR PROBLEMS") utils_thirdparty.find_problems( dest_dir=dest, report_missing_sources=sdists, diff --git a/etc/scripts/fetch_thirdparty.py b/etc/scripts/fetch_thirdparty.py index eedf05c..76a19a6 100644 --- a/etc/scripts/fetch_thirdparty.py +++ b/etc/scripts/fetch_thirdparty.py @@ -1,23 +1,21 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # # Copyright (c) nexB Inc. and others. All rights reserved. # ScanCode is a trademark of nexB Inc. # SPDX-License-Identifier: Apache-2.0 # See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/skeleton for support or download. +# See https://github.com/aboutcode-org/skeleton for support or download. # See https://aboutcode.org for more information about nexB OSS projects. # import itertools -import os import sys from collections import defaultdict import click -import utils_thirdparty import utils_requirements +import utils_thirdparty TRACE = False TRACE_DEEP = False @@ -109,7 +107,8 @@ @click.option( "--use-cached-index", is_flag=True, - help="Use on disk cached PyPI indexes list of packages and versions and do not refetch if present.", + help="Use on disk cached PyPI indexes list of packages and versions and " + "do not refetch if present.", ) @click.option( "--sdist-only", @@ -120,7 +119,7 @@ show_default=False, multiple=True, help="Package name(s) that come only in sdist format (no wheels). " - "The command will not fail and exit if no wheel exists for these names", + "The command will not fail and exit if no wheel exists for these names", ) @click.option( "--wheel-only", @@ -131,7 +130,7 @@ show_default=False, multiple=True, help="Package name(s) that come only in wheel format (no sdist). " - "The command will not fail and exit if no sdist exists for these names", + "The command will not fail and exit if no sdist exists for these names", ) @click.option( "--no-dist", @@ -142,7 +141,7 @@ show_default=False, multiple=True, help="Package name(s) that do not come either in wheel or sdist format. " - "The command will not fail and exit if no distribution exists for these names", + "The command will not fail and exit if no distribution exists for these names", ) @click.help_option("-h", "--help") def fetch_thirdparty( @@ -248,7 +247,6 @@ def fetch_thirdparty( print(f"Processing: {name} @ {version}") if wheels: for environment in environments: - if TRACE: print(f" ==> Fetching wheel for envt: {environment}") @@ -262,11 +260,9 @@ def fetch_thirdparty( if not fetched: wheels_or_sdist_not_found[f"{name}=={version}"].append(environment) if TRACE: - print(f" NOT FOUND") + print(" NOT FOUND") - if (sdists or - (f"{name}=={version}" in wheels_or_sdist_not_found and name in sdist_only) - ): + if sdists or (f"{name}=={version}" in wheels_or_sdist_not_found and name in sdist_only): if TRACE: print(f" ==> Fetching sdist: {name}=={version}") @@ -279,17 +275,17 @@ def fetch_thirdparty( if not fetched: wheels_or_sdist_not_found[f"{name}=={version}"].append("sdist") if TRACE: - print(f" NOT FOUND") + print(" NOT FOUND") mia = [] for nv, dists in wheels_or_sdist_not_found.items(): name, _, version = nv.partition("==") if name in no_dist: continue - sdist_missing = sdists and "sdist" in dists and not name in wheel_only + sdist_missing = sdists and "sdist" in dists and name not in wheel_only if sdist_missing: mia.append(f"SDist missing: {nv} {dists}") - wheels_missing = wheels and any(d for d in dists if d != "sdist") and not name in sdist_only + wheels_missing = wheels and any(d for d in dists if d != "sdist") and name not in sdist_only if wheels_missing: mia.append(f"Wheels missing: {nv} {dists}") @@ -298,12 +294,12 @@ def fetch_thirdparty( print(m) raise Exception(mia) - print(f"==> FETCHING OR CREATING ABOUT AND LICENSE FILES") + print("==> FETCHING OR CREATING ABOUT AND LICENSE FILES") utils_thirdparty.fetch_abouts_and_licenses(dest_dir=dest_dir, use_cached_index=use_cached_index) utils_thirdparty.clean_about_files(dest_dir=dest_dir) # check for problems - print(f"==> CHECK FOR PROBLEMS") + print("==> CHECK FOR PROBLEMS") utils_thirdparty.find_problems( dest_dir=dest_dir, report_missing_sources=sdists, diff --git a/etc/scripts/gen_pypi_simple.py b/etc/scripts/gen_pypi_simple.py index 214d90d..89d0626 100644 --- a/etc/scripts/gen_pypi_simple.py +++ b/etc/scripts/gen_pypi_simple.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # SPDX-License-Identifier: BSD-2-Clause-Views AND MIT # Copyright (c) 2010 David Wolever . All rights reserved. @@ -69,7 +68,6 @@ def get_package_name_from_filename(filename): raise InvalidDistributionFilename(filename) elif filename.endswith(wheel_ext): - wheel_info = get_wheel_from_filename(filename) if not wheel_info: @@ -133,7 +131,7 @@ def build_links_package_index(packages_by_package_name, base_url): Return an HTML document as string which is a links index of all packages """ document = [] - header = f""" + header = """ Links for all packages @@ -178,13 +176,13 @@ def simple_index_entry(self, base_url): def build_pypi_index(directory, base_url="https://thirdparty.aboutcode.org/pypi"): """ - Using a ``directory`` directory of wheels and sdists, create the a PyPI - simple directory index at ``directory``/simple/ populated with the proper - PyPI simple index directory structure crafted using symlinks. + Create the a PyPI simple directory index using a ``directory`` directory of wheels and sdists in + the direvctory at ``directory``/simple/ populated with the proper PyPI simple index directory + structure crafted using symlinks. - WARNING: The ``directory``/simple/ directory is removed if it exists. - NOTE: in addition to the a PyPI simple index.html there is also a links.html - index file generated which is suitable to use with pip's --find-links + WARNING: The ``directory``/simple/ directory is removed if it exists. NOTE: in addition to the a + PyPI simple index.html there is also a links.html index file generated which is suitable to use + with pip's --find-links """ directory = Path(directory) @@ -200,11 +198,10 @@ def build_pypi_index(directory, base_url="https://thirdparty.aboutcode.org/pypi" simple_html_index = [ "", "PyPI Simple Index", - '' '', + '', ] for pkg_file in directory.iterdir(): - pkg_filename = pkg_file.name if ( diff --git a/etc/scripts/gen_requirements.py b/etc/scripts/gen_requirements.py index 07e26f7..1b87944 100644 --- a/etc/scripts/gen_requirements.py +++ b/etc/scripts/gen_requirements.py @@ -1,11 +1,10 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # # Copyright (c) nexB Inc. and others. All rights reserved. # ScanCode is a trademark of nexB Inc. # SPDX-License-Identifier: Apache-2.0 # See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/skeleton for support or download. +# See https://github.com/aboutcode-org/skeleton for support or download. # See https://aboutcode.org for more information about nexB OSS projects. # import argparse @@ -34,7 +33,8 @@ def gen_requirements(): type=pathlib.Path, required=True, metavar="DIR", - help="Path to the 'site-packages' directory where wheels are installed such as lib/python3.6/site-packages", + help="Path to the 'site-packages' directory where wheels are installed " + "such as lib/python3.12/site-packages", ) parser.add_argument( "-r", diff --git a/etc/scripts/gen_requirements_dev.py b/etc/scripts/gen_requirements_dev.py index 12cc06d..8548205 100644 --- a/etc/scripts/gen_requirements_dev.py +++ b/etc/scripts/gen_requirements_dev.py @@ -1,11 +1,10 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # # Copyright (c) nexB Inc. and others. All rights reserved. # ScanCode is a trademark of nexB Inc. # SPDX-License-Identifier: Apache-2.0 # See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/skeleton for support or download. +# See https://github.com/aboutcode-org/skeleton for support or download. # See https://aboutcode.org for more information about nexB OSS projects. # import argparse @@ -36,7 +35,8 @@ def gen_dev_requirements(): type=pathlib.Path, required=True, metavar="DIR", - help='Path to the "site-packages" directory where wheels are installed such as lib/python3.6/site-packages', + help="Path to the 'site-packages' directory where wheels are installed " + "such as lib/python3.12/site-packages", ) parser.add_argument( "-d", diff --git a/etc/scripts/test_utils_pip_compatibility_tags.py b/etc/scripts/test_utils_pip_compatibility_tags.py index 98187c5..0e9c360 100644 --- a/etc/scripts/test_utils_pip_compatibility_tags.py +++ b/etc/scripts/test_utils_pip_compatibility_tags.py @@ -1,4 +1,5 @@ -"""Generate and work with PEP 425 Compatibility Tags. +""" +Generate and work with PEP 425 Compatibility Tags. copied from pip-20.3.1 pip/tests/unit/test_utils_compatibility_tags.py download_url: https://raw.githubusercontent.com/pypa/pip/20.3.1/tests/unit/test_utils_compatibility_tags.py @@ -25,8 +26,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from unittest.mock import patch import sysconfig +from unittest.mock import patch import pytest @@ -51,7 +52,7 @@ def test_version_info_to_nodot(version_info, expected): assert actual == expected -class Testcompatibility_tags(object): +class Testcompatibility_tags: def mock_get_config_var(self, **kwd): """ Patch sysconfig.get_config_var for arbitrary keys. @@ -82,7 +83,7 @@ def test_no_hyphen_tag(self): assert "-" not in tag.platform -class TestManylinux2010Tags(object): +class TestManylinux2010Tags: @pytest.mark.parametrize( "manylinux2010,manylinux1", [ @@ -105,7 +106,7 @@ def test_manylinux2010_implies_manylinux1(self, manylinux2010, manylinux1): assert arches[:2] == [manylinux2010, manylinux1] -class TestManylinux2014Tags(object): +class TestManylinux2014Tags: @pytest.mark.parametrize( "manylinuxA,manylinuxB", [ diff --git a/etc/scripts/update_skeleton.py b/etc/scripts/update_skeleton.py new file mode 100644 index 0000000..374c06f --- /dev/null +++ b/etc/scripts/update_skeleton.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python +# +# Copyright (c) nexB Inc. AboutCode, and others. All rights reserved. +# ScanCode is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/aboutcode-org/skeleton for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + +from pathlib import Path +import os +import subprocess + +import click + + +ABOUTCODE_PUBLIC_REPO_NAMES = [ + "aboutcode-toolkit", + "ahocode", + "bitcode", + "clearcode-toolkit", + "commoncode", + "container-inspector", + "debian-inspector", + "deltacode", + "elf-inspector", + "extractcode", + "fetchcode", + "gemfileparser2", + "gh-issue-sandbox", + "go-inspector", + "heritedcode", + "license-expression", + "license_copyright_pipeline", + "nuget-inspector", + "pip-requirements-parser", + "plugincode", + "purldb", + "pygmars", + "python-inspector", + "sanexml", + "saneyaml", + "scancode-analyzer", + "scancode-toolkit-contrib", + "scancode-toolkit-reference-scans", + "thirdparty-toolkit", + "tracecode-toolkit", + "tracecode-toolkit-strace", + "turbo-spdx", + "typecode", + "univers", +] + + +@click.command() +@click.help_option("-h", "--help") +def update_skeleton_files(repo_names=ABOUTCODE_PUBLIC_REPO_NAMES): + """ + Update project files of AboutCode projects that use the skeleton + + This script will: + - Clone the repo + - Add the skeleton repo as a new origin + - Create a new branch named "update-skeleton-files" + - Merge in the new skeleton files into the "update-skeleton-files" branch + + The user will need to save merge commit messages that pop up when running + this script in addition to resolving the merge conflicts on repos that have + them. + """ + + # Create working directory + work_dir_path = Path("/tmp/update_skeleton/") + if not os.path.exists(work_dir_path): + os.makedirs(work_dir_path, exist_ok=True) + + for repo_name in repo_names: + # Move to work directory + os.chdir(work_dir_path) + + # Clone repo + repo_git = f"git@github.com:aboutcode-org/{repo_name}.git" + subprocess.run(["git", "clone", repo_git]) + + # Go into cloned repo + os.chdir(work_dir_path / repo_name) + + # Add skeleton as an origin + subprocess.run( + ["git", "remote", "add", "skeleton", "git@github.com:aboutcode-org/skeleton.git"] + ) + + # Fetch skeleton files + subprocess.run(["git", "fetch", "skeleton"]) + + # Create and checkout new branch + subprocess.run(["git", "checkout", "-b", "update-skeleton-files"]) + + # Merge skeleton files into the repo + subprocess.run(["git", "merge", "skeleton/main", "--allow-unrelated-histories"]) + + +if __name__ == "__main__": + update_skeleton_files() diff --git a/etc/scripts/utils_dejacode.py b/etc/scripts/utils_dejacode.py index c42e6c9..b6bff51 100644 --- a/etc/scripts/utils_dejacode.py +++ b/etc/scripts/utils_dejacode.py @@ -1,11 +1,10 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # # Copyright (c) nexB Inc. and others. All rights reserved. # ScanCode is a trademark of nexB Inc. # SPDX-License-Identifier: Apache-2.0 # See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/skeleton for support or download. +# See https://github.com/aboutcode-org/skeleton for support or download. # See https://aboutcode.org for more information about nexB OSS projects. # import io @@ -14,7 +13,6 @@ import requests import saneyaml - from packvers import version as packaging_version """ @@ -26,7 +24,7 @@ DEJACODE_API_URL_PACKAGES = f"{DEJACODE_API_URL}packages/" DEJACODE_API_HEADERS = { - "Authorization": "Token {}".format(DEJACODE_API_KEY), + "Authorization": f"Token {DEJACODE_API_KEY}", "Accept": "application/json; indent=4", } @@ -51,6 +49,7 @@ def fetch_dejacode_packages(params): DEJACODE_API_URL_PACKAGES, params=params, headers=DEJACODE_API_HEADERS, + timeout=10, ) return response.json()["results"] @@ -94,7 +93,7 @@ def update_with_dejacode_about_data(distribution): if package_data: package_api_url = package_data["api_url"] about_url = f"{package_api_url}about" - response = requests.get(about_url, headers=DEJACODE_API_HEADERS) + response = requests.get(about_url, headers=DEJACODE_API_HEADERS, timeout=10) # note that this is YAML-formatted about_text = response.json()["about_data"] about_data = saneyaml.load(about_text) @@ -114,7 +113,7 @@ def fetch_and_save_about_files(distribution, dest_dir="thirdparty"): if package_data: package_api_url = package_data["api_url"] about_url = f"{package_api_url}about_files" - response = requests.get(about_url, headers=DEJACODE_API_HEADERS) + response = requests.get(about_url, headers=DEJACODE_API_HEADERS, timeout=10) about_zip = response.content with io.BytesIO(about_zip) as zf: with zipfile.ZipFile(zf) as zi: @@ -153,7 +152,7 @@ def find_latest_dejacode_package(distribution): with_versions = sorted(with_versions) latest_version, latest_package_version = sorted(with_versions)[-1] print( - f"Found DejaCode latest version: {latest_version} " f"for dist: {distribution.package_url}", + f"Found DejaCode latest version: {latest_version} for dist: {distribution.package_url}", ) return latest_package_version @@ -179,7 +178,7 @@ def create_dejacode_package(distribution): } fields_to_carry_over = [ - "download_url" "type", + "download_urltype", "namespace", "name", "version", @@ -202,10 +201,11 @@ def create_dejacode_package(distribution): DEJACODE_API_URL_PACKAGES, data=new_package_payload, headers=DEJACODE_API_HEADERS, + timeout=10, ) new_package_data = response.json() if response.status_code != 201: raise Exception(f"Error, cannot create package for: {distribution}") - print(f'New Package created at: {new_package_data["absolute_url"]}') + print(f"New Package created at: {new_package_data['absolute_url']}") return new_package_data diff --git a/etc/scripts/utils_pip_compatibility_tags.py b/etc/scripts/utils_pip_compatibility_tags.py index af42a0c..dd954bc 100644 --- a/etc/scripts/utils_pip_compatibility_tags.py +++ b/etc/scripts/utils_pip_compatibility_tags.py @@ -1,4 +1,5 @@ -"""Generate and work with PEP 425 Compatibility Tags. +""" +Generate and work with PEP 425 Compatibility Tags. copied from pip-20.3.1 pip/_internal/utils/compatibility_tags.py download_url: https://github.com/pypa/pip/blob/20.3.1/src/pip/_internal/utils/compatibility_tags.py @@ -27,14 +28,12 @@ import re -from packvers.tags import ( - compatible_tags, - cpython_tags, - generic_tags, - interpreter_name, - interpreter_version, - mac_platforms, -) +from packvers.tags import compatible_tags +from packvers.tags import cpython_tags +from packvers.tags import generic_tags +from packvers.tags import interpreter_name +from packvers.tags import interpreter_version +from packvers.tags import mac_platforms _osx_arch_pat = re.compile(r"(.+)_(\d+)_(\d+)_(.+)") @@ -132,7 +131,7 @@ def _get_custom_interpreter(implementation=None, version=None): implementation = interpreter_name() if version is None: version = interpreter_version() - return "{}{}".format(implementation, version) + return f"{implementation}{version}" def get_supported( @@ -142,7 +141,8 @@ def get_supported( abis=None, # type: Optional[List[str]] ): # type: (...) -> List[Tag] - """Return a list of supported tags for each version specified in + """ + Return a list of supported tags for each version specified in `versions`. :param version: a string version, of the form "33" or "32", diff --git a/etc/scripts/utils_requirements.py b/etc/scripts/utils_requirements.py index 0fc25a3..424bed2 100644 --- a/etc/scripts/utils_requirements.py +++ b/etc/scripts/utils_requirements.py @@ -1,11 +1,10 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # # Copyright (c) nexB Inc. and others. All rights reserved. # ScanCode is a trademark of nexB Inc. # SPDX-License-Identifier: Apache-2.0 # See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/skeleton for support or download. +# See https://github.com/aboutcode-org/skeleton for support or download. # See https://aboutcode.org for more information about nexB OSS projects. # @@ -40,7 +39,7 @@ def get_required_name_versions(requirement_lines, with_unpinned=False): req_line = req_line.strip() if not req_line or req_line.startswith("#"): continue - if req_line.startswith("-") or (not with_unpinned and not "==" in req_line): + if req_line.startswith("-") or (not with_unpinned and "==" not in req_line): print(f"Requirement line is not supported: ignored: {req_line}") continue yield get_required_name_version(requirement=req_line, with_unpinned=with_unpinned) @@ -57,21 +56,25 @@ def get_required_name_version(requirement, with_unpinned=False): >>> assert get_required_name_version("fooA==1.2.3.DEV1") == ("fooa", "1.2.3.dev1") >>> assert get_required_name_version("foo==1.2.3", with_unpinned=False) == ("foo", "1.2.3") >>> assert get_required_name_version("foo", with_unpinned=True) == ("foo", "") - >>> assert get_required_name_version("foo>=1.2", with_unpinned=True) == ("foo", ""), get_required_name_version("foo>=1.2") + >>> expected = ("foo", ""), get_required_name_version("foo>=1.2") + >>> assert get_required_name_version("foo>=1.2", with_unpinned=True) == expected >>> try: ... assert not get_required_name_version("foo", with_unpinned=False) ... except Exception as e: ... assert "Requirement version must be pinned" in str(e) """ requirement = requirement and "".join(requirement.lower().split()) - assert requirement, f"specifier is required is empty:{requirement!r}" + if not requirement: + raise ValueError(f"specifier is required is empty:{requirement!r}") name, operator, version = split_req(requirement) - assert name, f"Name is required: {requirement}" + if not name: + raise ValueError(f"Name is required: {requirement}") is_pinned = operator == "==" if with_unpinned: version = "" else: - assert is_pinned and version, f"Requirement version must be pinned: {requirement}" + if not is_pinned and version: + raise ValueError(f"Requirement version must be pinned: {requirement}") return name, version @@ -117,7 +120,7 @@ def get_installed_reqs(site_packages_dir): # Also include these packages in the output with --all: wheel, distribute, # setuptools, pip args = ["pip", "freeze", "--exclude-editable", "--all", "--path", site_packages_dir] - return subprocess.check_output(args, encoding="utf-8") + return subprocess.check_output(args, encoding="utf-8") # noqa: S603 comparators = ( @@ -147,9 +150,11 @@ def split_req(req): >>> assert split_req("foo >= 1.2.3 ") == ("foo", ">=", "1.2.3"), split_req("foo >= 1.2.3 ") >>> assert split_req("foo>=1.2") == ("foo", ">=", "1.2"), split_req("foo>=1.2") """ - assert req + if not req: + raise ValueError("req is required") # do not allow multiple constraints and tags - assert not any(c in req for c in ",;") + if any(c in req for c in ",;"): + raise Exception(f"complex requirements with : or ; not supported: {req}") req = "".join(req.split()) if not any(c in req for c in comparators): return req, "", "" diff --git a/etc/scripts/utils_thirdparty.py b/etc/scripts/utils_thirdparty.py index addf8e5..6f812f0 100644 --- a/etc/scripts/utils_thirdparty.py +++ b/etc/scripts/utils_thirdparty.py @@ -5,7 +5,7 @@ # ScanCode is a trademark of nexB Inc. # SPDX-License-Identifier: Apache-2.0 # See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/skeleton for support or download. +# See https://github.com/aboutcode-org/skeleton for support or download. # See https://aboutcode.org for more information about nexB OSS projects. # import email @@ -115,13 +115,14 @@ TRACE_ULTRA_DEEP = False # Supported environments -PYTHON_VERSIONS = "37", "38", "39", "310" +PYTHON_VERSIONS = "39", "310", "311", "312", "313" PYTHON_DOT_VERSIONS_BY_VER = { - "37": "3.7", - "38": "3.8", "39": "3.9", "310": "3.10", + "311": "3.11", + "312": "3.12", + "313": "3.13", } @@ -133,10 +134,11 @@ def get_python_dot_version(version): ABIS_BY_PYTHON_VERSION = { - "37": ["cp37", "cp37m", "abi3"], - "38": ["cp38", "cp38m", "abi3"], "39": ["cp39", "cp39m", "abi3"], "310": ["cp310", "cp310m", "abi3"], + "311": ["cp311", "cp311m", "abi3"], + "312": ["cp312", "cp312m", "abi3"], + "313": ["cp313", "cp313m", "abi3"], } PLATFORMS_BY_OS = { @@ -355,7 +357,6 @@ def sorted(cls, namevers): @attr.attributes class Distribution(NameVer): - # field names that can be updated from another Distribution or mapping updatable_fields = [ "license_expression", @@ -1091,7 +1092,6 @@ def get_sdist_name_ver_ext(filename): @attr.attributes class Sdist(Distribution): - extension = attr.ib( repr=False, type=str, @@ -1129,7 +1129,6 @@ def to_filename(self): @attr.attributes class Wheel(Distribution): - """ Represents a wheel file. @@ -2137,7 +2136,6 @@ def call(args, verbose=TRACE): with subprocess.Popen( args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8" ) as process: - stdouts = [] while True: line = process.stdout.readline() diff --git a/pyproject.toml b/pyproject.toml index cde7907..d79574e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,6 @@ norecursedirs = [ "dist", "build", "_build", - "dist", "etc", "local", "ci", @@ -34,7 +33,9 @@ norecursedirs = [ "thirdparty", "tmp", "venv", + ".venv", "tests/data", + "*/tests/test_data", ".eggs", "src/*/data", "tests/*/data" @@ -50,3 +51,79 @@ addopts = [ "--strict-markers", "--doctest-modules" ] + +[tool.ruff] +line-length = 100 +extend-exclude = [] +target-version = "py310" +include = [ + "pyproject.toml", + "src/**/*.py", + "etc/**/*.py", + "test/**/*.py", + "tests/**/*.py", + "doc/**/*.py", + "docs/**/*.py", + "*.py", + "." + +] +# ignore test data and testfiles: they should never be linted nor formatted +exclude = [ +# main style + "**/tests/data/**/*", +# scancode-toolkit + "**/tests/*/data/**/*", +# dejacode, purldb + "**/tests/testfiles/**/*", +# vulnerablecode, fetchcode + "**/tests/*/test_data/**/*", + "**/tests/test_data/**/*", +# django migrations + "**/migrations/**/*" +] + +[tool.ruff.lint] +# Rules: https://docs.astral.sh/ruff/rules/ +select = [ +# "E", # pycodestyle +# "W", # pycodestyle warnings + "D", # pydocstyle +# "F", # Pyflakes +# "UP", # pyupgrade +# "S", # flake8-bandit + "I", # isort +# "C9", # McCabe complexity +] +ignore = ["D1", "D200", "D202", "D203", "D205", "D212", "D400", "D415", "I001"] + + +[tool.ruff.lint.isort] +force-single-line = true +lines-after-imports = 1 +default-section = "first-party" +known-first-party = ["src", "tests", "etc/scripts/**/*.py"] +known-third-party = ["click", "pytest"] + +sections = { django = ["django"] } +section-order = [ + "future", + "standard-library", + "django", + "third-party", + "first-party", + "local-folder", +] + +[tool.ruff.lint.mccabe] +max-complexity = 10 + +[tool.ruff.lint.per-file-ignores] +# Place paths of files to be ignored by ruff here +"tests/*" = ["S101"] +"test_*.py" = ["S101"] + + +[tool.doc8] +ignore-path = ["docs/build", "doc/build", "docs/_build", "doc/_build"] +max-line-length=100 diff --git a/requirements-dev.txt b/requirements-dev.txt index bd08a7b..3d6486d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,31 +1,85 @@ -aboutcode-toolkit==7.0.2 -black==22.6.0 -bleach==5.0.1 -build==0.7.0 -commonmark==0.9.1 -docutils==0.19 -et-xmlfile==1.1.0 -execnet==1.9.0 -iniconfig==1.1.1 -isort==5.10.1 -jeepney==0.8.0 -keyring==23.7.0 -mypy-extensions==0.4.3 -openpyxl==3.0.10 -pathspec==0.9.0 -pep517==0.12.0 -pkginfo==1.8.3 -platformdirs==2.5.2 -py==1.11.0 -pytest==7.1.2 -pytest-forked==1.4.0 -pytest-xdist==2.5.0 -readme-renderer==35.0 -requests-toolbelt==0.9.1 +aboutcode-toolkit==11.1.1 +alabaster==0.7.16 +anyio==4.11.0 +babel==2.17.0 +backports.tarfile==1.2.0 +beautifulsoup4==4.14.2 +boolean.py==5.0 +certifi==2025.8.3 +cffi==2.0.0 +charset-normalizer==3.4.3 +click==8.2.0;python_version>='3.10' +click==8.1.8;python_version<'3.10' +colorama==0.4.6 +commoncode==32.3.0 +cryptography==46.0.2 +doc8==1.1.2 +docutils==0.21.2 +et_xmlfile==2.0.0 +exceptiongroup==1.3.0 +execnet==2.1.1 +h11==0.16.0 +id==1.5.0 +idna==3.10 +imagesize==1.4.1 +importlib_metadata==8.7.0 +iniconfig==2.1.0 +jaraco.classes==3.4.0 +jaraco.context==6.0.1 +jaraco.functools==4.3.0 +jeepney==0.9.0 +jinja2==3.1.6 +keyring==25.6.0 +license-expression==30.4.4 +markdown-it-py==3.0.0 +markupsafe==3.0.3 +mdurl==0.1.2 +more-itertools==10.8.0 +nh3==0.3.0 +openpyxl==3.1.5 +packageurl-python==0.17.5 +packaging==25.0 +pbr==7.0.1 +pluggy==1.6.0 +pycparser==2.23 +pygments==2.19.2 +pytest==8.4.2 +pytest-xdist==3.8.0 +pyyaml==6.0.3 +readme_renderer==44.0 +requests==2.32.5 +requests-toolbelt==1.0.0 +restructuredtext_lint==1.4.0 rfc3986==2.0.0 -rich==12.5.1 -secretstorage==3.3.2 -tomli==2.0.1 -tqdm==4.64.0 -twine==4.0.1 -typing_extensions==4.3.0 +rich==14.1.0 +ruff==0.13.2 +saneyaml==0.6.1 +secretstorage==3.3.3 +sniffio==1.3.1 +snowballstemmer==3.0.1 +soupsieve==2.8 +sphinx==7.4.7 +sphinx-autobuild==2024.10.3 +sphinx-copybutton==0.5.2 +sphinx-rtd-dark-mode==1.3.0 +sphinx-rtd-theme==3.0.2 +sphinx_reredirects==0.1.6 +sphinxcontrib-apidoc==0.6.0 +sphinxcontrib-applehelp==2.0.0 +sphinxcontrib-devhelp==2.0.0 +sphinxcontrib-htmlhelp==2.1.0 +sphinxcontrib-jquery==4.1 +sphinxcontrib-jsmath==1.0.1 +sphinxcontrib-qthelp==2.0.0 +sphinxcontrib-serializinghtml==2.0.0 +starlette==0.48.0 +stevedore==5.5.0 +text-unidecode==1.3 +tomli==2.2.1 +twine==6.2.0 +typing_extensions==4.15.0 +urllib3==2.5.0 +uvicorn==0.37.0 +watchfiles==1.1.0 +websockets==15.0.1 +zipp==3.23.0 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 005212c..2b765a1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,76 +1,4 @@ -attrs==21.4.0 -banal==1.0.6 -beautifulsoup4==4.11.1 -binaryornot==0.4.4 -boolean.py==4.0 -certifi==2022.6.15 -cffi==1.15.1 -chardet==5.0.0 -charset-normalizer==2.1.0 -click==8.1.3 -colorama==0.4.5 -commoncode==31.0.0 -construct==2.10.68 -container-inspector==31.1.0 -cryptography==37.0.4 -dockerfile-parse==1.2.0 -dparse2==0.6.1 -extractcode==31.0.0 -extractcode-7z==16.5.210531 -extractcode-libarchive==3.5.1.210531 -fasteners==0.17.3 -fingerprints==1.0.3 -ftfy==6.1.1 -future==0.18.2 -gemfileparser==0.8.0 -html5lib==1.1 -idna==3.3 -importlib-metadata==4.12.0 -inflection==0.5.1 -intbitset==3.0.1 -isodate==0.6.1 -jaraco.functools==3.5.1 -javaproperties==0.8.1 -Jinja2==3.1.2 -jsonstreams==0.6.0 -license-expression==30.0.0 -lxml==4.9.1 -MarkupSafe==2.1.1 -more-itertools==8.13.0 -normality==2.3.3 -packageurl-python==0.10.0 -packaging==21.3 -parameter-expansion-patched==0.3.1 -pdfminer.six==20220524 -pefile==2022.5.30 -pip-requirements-parser==31.2.0 -pkginfo2==30.0.0 -pluggy==1.0.0 -plugincode==31.0.0b1 -ply==3.11 -publicsuffix2==2.20191221 -pyahocorasick==2.0.0b1 -pycparser==2.21 -pygmars==0.7.0 -Pygments==2.12.0 -pymaven-patch==0.3.0 -pyparsing==3.0.9 -pytz==2022.1 -PyYAML==6.0 -rdflib==6.2.0 -requests==2.28.1 -saneyaml==0.5.2 -six==1.16.0 -soupsieve==2.3.2.post1 -spdx-tools==0.7.0a3 -text-unidecode==1.3 -toml==0.10.2 -typecode==30.0.0 -typecode-libmagic==5.39.210531 -typing-extensions==4.3.0 -urllib3==1.26.11 -urlpy==0.5 -wcwidth==0.2.5 -webencodings==0.5.1 -xmltodict==0.13.0 -zipp==3.8.1 +attrs==25.3.0 +chardet==5.2.0 +pip==25.2 +setuptools==80.9.0 diff --git a/setup.cfg b/setup.cfg index 520ace8..7a3ba91 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = debian_inspector -version = 31.1.0 +version = 31.1.1 license = Apache-2.0 AND BSD-3-Clause AND MIT # description must be on ONE line https://github.com/pypa/setuptools/issues/1390 @@ -46,8 +46,11 @@ license_files = AUTHORS.rst CHANGELOG.rst CODE_OF_CONDUCT.rst + README.rst [options] +python_requires = >=3.9 + package_dir = =src packages = find: @@ -56,7 +59,6 @@ zip_safe = false setup_requires = setuptools_scm[toml] >= 4 -python_requires = >=3.7 install_requires = chardet >= 3.0.0 @@ -68,18 +70,13 @@ where = src [options.extras_require] -testing = - pytest >= 6, != 7.0.0 +dev = + pytest >= 7.0.1 pytest-xdist >= 2 aboutcode-toolkit >= 7.0.2 - pycodestyle >= 2.8.0 twine - black commoncode - isort - - -docs = + ruff Sphinx>=5.0.2 sphinx-rtd-theme>=1.0.0 sphinx-reredirects >= 0.1.2 @@ -88,4 +85,3 @@ docs = sphinx-autobuild sphinx-rtd-dark-mode>=1.3.0 sphinx-copybutton - diff --git a/src/debian_inspector/contents.py b/src/debian_inspector/contents.py index 456454c..415f27b 100644 --- a/src/debian_inspector/contents.py +++ b/src/debian_inspector/contents.py @@ -37,10 +37,10 @@ def parse_contents(location, has_header=True): See https://wiki.debian.org/DebianRepository/Format#A.22Contents.22_indices for format details. """ - if location.endswith('.gz'): - opener, mode = gzip.GzipFile, 'rb' + if location.endswith(".gz"): + opener, mode = gzip.GzipFile, "rb" else: - opener, mode = open, 'r' + opener, mode = open, "r" packages_by_path = defaultdict(list) paths_by_package = defaultdict(list) @@ -56,15 +56,15 @@ def parse_contents(location, has_header=True): for line in lines: if isinstance(line, bytes): - line = line.decode('utf-8') - left, _, right = line.strip().rpartition(' ') + line = line.decode("utf-8") + left, _, right = line.strip().rpartition(" ") left = left.strip() right = right.strip() - if left == 'FILE' and right == 'LOCATION': + if left == "FILE" and right == "LOCATION": if not has_header: raise Exception( - 'Invalid Contents file with a FILE/LOCATION header: ' - 'call with has_header=True.' + "Invalid Contents file with a FILE/LOCATION header: " + "call with has_header=True." ) if not in_table: @@ -77,7 +77,7 @@ def parse_contents(location, has_header=True): continue path = left packages = right - package_names = packages.split(',') + package_names = packages.split(",") for archsec_name in package_names: # "A list of qualified package names, separated by comma. A # qualified package name has the form @@ -86,33 +86,34 @@ def parse_contents(location, has_header=True): # package." # NOTE: we ignore the arch and section for now - archsec, _, package_name = archsec_name.rpartition('/') - arch, _, section = archsec.rpartition('/') + archsec, _, package_name = archsec_name.rpartition("/") + arch, _, section = archsec.rpartition("/") packages_by_path[path].append(package_name) paths_by_package[package_name].append(path) if not in_table: - raise Exception('Invalid Content files without FILE/LOCATION header.') + raise Exception("Invalid Content files without FILE/LOCATION header.") return packages_by_path, paths_by_package -if __name__ == '__main__': - +if __name__ == "__main__": import sys import time + try: location = sys.argv[1] start = time.time() packages_by_path, paths_by_package = parse_contents(location, has_header=False) duration = time.time() - start - print(f'Parsing completed in {duration} seconds.') + print(f"Parsing completed in {duration} seconds.") names_count = len(paths_by_package) paths_count = len(packages_by_path) - print(f'Found {names_count} package names with {paths_count} paths.') + print(f"Found {names_count} package names with {paths_count} paths.") except Exception as e: - print('Parse a Debian Contents files and print stats.') - print('Usage: contents ') - print('For example, download this file: http://ftp.de.debian.org/debian/dists/Debian10.6/main/Contents-amd64.gz') + print("Parse a Debian Contents files and print stats.") + print("Usage: contents ") + print( + "For example, download this file: http://ftp.de.debian.org/debian/dists/Debian10.6/main/Contents-amd64.gz" + ) raise - diff --git a/src/debian_inspector/copyright.py b/src/debian_inspector/copyright.py index 5cec17b..309bc19 100644 --- a/src/debian_inspector/copyright.py +++ b/src/debian_inspector/copyright.py @@ -52,7 +52,7 @@ def has_doc_reference(self): Return True if this license contains a reference to a Debian shared license file in the /usr/share/common-licenses directory. """ - return self.text and '/usr/share/common-licenses' in self.text + return self.text and "/usr/share/common-licenses" in self.text @attrs @@ -63,6 +63,7 @@ class CopyrightStatementField(debcon.FieldMixin): contains all text. This field represents one line, e.g. one statememt. """ + # TODO: add line tracking holder = attrib() year_range = attrib(default=None) @@ -71,11 +72,11 @@ class CopyrightStatementField(debcon.FieldMixin): def from_value(cls, value): if isinstance(value, cls): return value - value = value or '' + value = value or "" if isinstance(value, bytes): - value = value.decode('utf-8') - value = ' '.join(value.split()) - year_range, _, holder = value.partition(' ') + value = value.decode("utf-8") + value = " ".join(value.split()) + year_range, _, holder = value.partition(" ") year_range = year_range.strip() holder = holder.strip() if not is_year_range(year_range): @@ -86,7 +87,7 @@ def from_value(cls, value): def dumps(self, **kwargs): cop = self.holder if self.year_range: - cop = '{} {}'.format(self.year_range, cop) + cop = "{} {}".format(self.year_range, cop) return cop.strip() @@ -99,7 +100,7 @@ def is_year_range(text): if all(c.isdigit() for c in text): return True - digit_punct = set('''!"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ 1234567890''') + digit_punct = set("""!"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ 1234567890""") if all(c in digit_punct for c in text) and any(c.isdigit() for c in text): return True @@ -107,9 +108,10 @@ def is_year_range(text): @attrs class CopyrightField(debcon.FieldMixin): """ - This represents a single "Copyright:" field which is a plain formatted text + CopyrightField represents a single "Copyright:" field which is a plain formatted text but is conventionally a list of copyrights statements one per line """ + statements = attrib(default=Factory(list)) @classmethod @@ -119,16 +121,13 @@ def from_value(cls, value): statements = [] if value: statements = [ - CopyrightStatementField.from_value(v) - for v in debcon.line_separated(value)] + CopyrightStatementField.from_value(v) for v in debcon.line_separated(value) + ] return cls(statements=statements) def dumps(self, **kwargs): - dumped = [ - s.dumps(**kwargs) if hasattr(s, 'dumps') else str(s) - for s in self.statements - ] - return '\n '.join(dumped).strip() + dumped = [s.dumps(**kwargs) if hasattr(s, "dumps") else str(s) for s in self.statements] + return "\n ".join(dumped).strip() @attrs @@ -137,6 +136,7 @@ class MaintainerField(debcon.FieldMixin): https://www.debian.org/doc/debian-policy/ch-controlfields#s-f-maintainer 5.6.2. Maintainer """ + name = attrib() email_address = attrib(default=None) @@ -156,7 +156,7 @@ def from_value(cls, value): def dumps(self, **kwargs): name = self.name if self.email_address: - name = '{} <{}>'.format(name, self.email_address) + name = "{} <{}>".format(name, self.email_address) return name.strip() @@ -210,8 +210,8 @@ def from_fields(cls, fields, all_extra=False): known_names = set(fields_dict(cls)) para_data = {} - para_data['extra_data'] = extra_data = {} - para_data['line_numbers_by_field'] = line_numbers_by_field = {} + para_data["extra_data"] = extra_data = {} + para_data["line_numbers_by_field"] = line_numbers_by_field = {} duplicated_field_name_suffix = 1 seen_names = set() @@ -221,12 +221,12 @@ def from_fields(cls, fields, all_extra=False): if not value and not value.strip(): continue - name = field.name.replace('-', '_') + name = field.name.replace("-", "_") # If there are duplicated fields, we keep them all, but rename them # with a number suffix; they will go in the extra_data mapping. if name in seen_names: - name = f'{name}_{duplicated_field_name_suffix}' + name = f"{name}_{duplicated_field_name_suffix}" duplicated_field_name_suffix += 1 seen_names.add(name) @@ -240,9 +240,12 @@ def from_fields(cls, fields, all_extra=False): mapping[name] = value.lstrip() start_line = field.start_line - if value.startswith('\n'): + if value.startswith("\n"): start_line += 1 - line_numbers_by_field[name] = (start_line, field.end_line,) + line_numbers_by_field[name] = ( + start_line, + field.end_line, + ) try: return cls(**para_data) @@ -254,12 +257,12 @@ def from_dict(cls, data): assert isinstance(data, dict) known_names = set(fields_dict(cls)) known_data = {} - known_data['extra_data'] = extra_data = {} + known_data["extra_data"] = extra_data = {} for key, value in data.items(): - key = key.replace('-', '_') + key = key.replace("-", "_") if value: if isinstance(value, list): - value = '\n'.join(value) + value = "\n".join(value) if key in known_names: known_data[key] = value else: @@ -267,29 +270,28 @@ def from_dict(cls, data): return cls(**known_data) - def to_dict(self, with_extra_data=True, with_lines=False): data = {} for name in fields_dict(self.__class__): - if name in ('extra_data' , 'line_numbers_by_field'): + if name in ("extra_data", "line_numbers_by_field"): continue value = getattr(self, name) if value: - if hasattr(value, 'dumps'): + if hasattr(value, "dumps"): value = value.dumps() data[name] = value if with_extra_data: - for name, value in getattr(self, 'extra_data', {}).items(): + for name, value in getattr(self, "extra_data", {}).items(): if value: # always treat these extra values as formatted value = value and debcon.as_formatted_text(value) data[name] = value if with_lines: - data['line_numbers_by_field'] = self.line_numbers_by_field + data["line_numbers_by_field"] = self.line_numbers_by_field return data @@ -297,12 +299,12 @@ def dumps(self, **kwargs): text = [] for name, value in self.to_dict().items(): if value and value.strip(): - name = name.replace('_', '-') + name = name.replace("_", "-") name = debcon.normalize_control_field_name(name) - if value.startswith(' '): + if value.startswith(" "): value = value[1:] - text.append('{}: {}'.format(name, value)) - return '\n'.join(text).strip() + text.append("{}: {}".format(name, value)) + return "\n".join(text).strip() def is_empty(self): """ @@ -311,7 +313,7 @@ def is_empty(self): return not any(self.to_dict().values()) def has_extra_data(self): - return bool(getattr(self, 'extra_data', False)) + return bool(getattr(self, "extra_data", False)) @attrs @@ -320,6 +322,7 @@ class CatchAllParagraph(BaseParagraph): A catch-all paragraph: everything is fed to the extra_data. Every field is treated as formatted text. """ + extra_data = attrib(default=Factory(dict)) @classmethod @@ -334,7 +337,7 @@ def is_all_unknown(self): Return True if this is an "unknown" field. We use the "unknown" field name for things that do not have a name. """ - return all(k.startswith('unknown') for k in self.to_dict()) + return all(k.startswith("unknown") for k in self.to_dict()) def is_valid(self, strict=False): if strict: @@ -347,8 +350,9 @@ class CopyrightHeaderParagraph(BaseParagraph): """ The header paragraph. - https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#header-paragraph + https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#header-stanza """ + # Default should be: # https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ # but we do not know yet if this a structured machine-readable format @@ -382,10 +386,12 @@ def is_machine_readable_copyright(text): """ Return True if a text is for a machine-readable copyright format. """ - return text and text[:100].lower().startswith(( - 'format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0', - 'format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0', - )) + return text and text[:100].lower().startswith( + ( + "format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0", + "format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0", + ) + ) @attrs @@ -393,8 +399,9 @@ class CopyrightFilesParagraph(BaseParagraph): """ A "files" paragraph with files, copyright, license and comment fields. - https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#files-paragraph + https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#files-stanza """ + files = debcon.AnyWhiteSpaceSeparatedField.attrib(default=None) copyright = CopyrightField.attrib(default=None) license = LicenseField.attrib(default=None) @@ -405,7 +412,7 @@ class CopyrightFilesParagraph(BaseParagraph): def dumps(self, **kwargs): if self.is_empty(): - return 'Files: ' + return "Files: " else: return BaseParagraph.dumps(self) @@ -413,20 +420,24 @@ def is_empty(self): """ Return True if this is empty. """ - return not any([ - self.files.values, - self.license.name, - self.license.text, - self.comment.text, - self.copyright.statements, - self.extra_data, - ]) + return not any( + [ + self.files.values, + self.license.name, + self.license.text, + self.comment.text, + self.copyright.statements, + self.extra_data, + ] + ) def is_valid(self, strict=False): valid = ( self.files.values and self.copyright.statements - and self.license.name or self.license.text) + and self.license.name + or self.license.text + ) if strict: valid = valid and not self.has_extra_data() return valid @@ -437,8 +448,9 @@ class CopyrightLicenseParagraph(BaseParagraph): """ A standalone license paragraph with license and comment fields, but no files. - https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#stand-alone-license-paragraph + https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#stand-alone-license-stanza """ + license = LicenseField.attrib(default=None) comment = debcon.FormattedTextField.attrib(default=None) @@ -450,16 +462,18 @@ def is_empty(self): Return True if this is empty (e.g. was crated only because of a 'License:' empty field. """ - return not any([ - self.extra_data, - self.comment.text, - self.license.name, - self.license.text, - ]) + return not any( + [ + self.extra_data, + self.comment.text, + self.license.name, + self.license.text, + ] + ) def dumps(self, **kwargs): if self.is_empty(): - return 'License: ' + return "License: " else: return BaseParagraph.dumps(self) @@ -476,6 +490,7 @@ class DebianCopyright(object): A machine-readable debian copyright file. See https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ """ + paragraphs = attrib(default=Factory(list)) def __attrs_post_init__(self, *args, **kwargs): @@ -502,16 +517,16 @@ def from_fields_groups(cls, fields_groups): for fields in fields_groups: field_names = set([hf.name for hf in fields]) - if 'format'in field_names or 'format-specification' in field_names: + if "format" in field_names or "format-specification" in field_names: # let's be flexible and assume that we have a copyright file # header if some format field is there cp = CopyrightHeaderParagraph.from_fields(fields) - elif 'files' in field_names: + elif "files" in field_names: # do we have a "files"? this is a file fields cp = CopyrightFilesParagraph.from_fields(fields) - elif 'license' in field_names: + elif "license" in field_names: cp = CopyrightLicenseParagraph.from_fields(fields) else: @@ -524,21 +539,17 @@ def from_fields_groups(cls, fields_groups): def dumps(self, **kwargs): dumped = [p.dumps(**kwargs) for p in self.paragraphs] - dumped = '\n\n'.join(dumped) - return dumped + '\n' + dumped = "\n\n".join(dumped) + return dumped + "\n" def to_dict(self, with_lines=False): - return { - 'paragraphs': [p.to_dict(with_lines=with_lines) for p in self.paragraphs] - } + return {"paragraphs": [p.to_dict(with_lines=with_lines) for p in self.paragraphs]} def get_header(self): """ Return the header paragraph or None. """ - headers = [ - p for p in self.paragraphs - if isinstance(p, CopyrightHeaderParagraph)] + headers = [p for p in self.paragraphs if isinstance(p, CopyrightHeaderParagraph)] if headers: return headers[0] @@ -574,8 +585,13 @@ def merge_contiguous_unknown_paragraphs(self): paragraphs.append( CatchAllParagraph( - extra_data={'unknown': debcon.from_formatted_lines(values)}, - line_numbers_by_field={'unknown':(start_line, end_line,)}, + extra_data={"unknown": debcon.from_formatted_lines(values)}, + line_numbers_by_field={ + "unknown": ( + start_line, + end_line, + ) + }, ) ) @@ -598,20 +614,24 @@ def fold_contiguous_empty_license_followed_by_unknown(self): folded_previous = False continue - if (isinstance(para1, CopyrightLicenseParagraph) + if ( + isinstance(para1, CopyrightLicenseParagraph) and para1.is_empty() and isinstance(para2, CatchAllParagraph) and para2.is_all_unknown() ): - para1.license.name = '' - para1.license.text = para2.to_dict().get('unknown', '') + para1.license.name = "" + para1.license.text = para2.to_dict().get("unknown", "") # The updated CopyrightLicenseParagraph paragraph lines extend # from its original start line to the end line of the # CatchAllParagraph - start_line, _end_line = para1.line_numbers_by_field.get('license', (1, 1)) - _start_line, end_line = para2.line_numbers_by_field.get('unknown', (1, 1)) - para1.line_numbers_by_field['license'] = (start_line, end_line,) + start_line, _end_line = para1.line_numbers_by_field.get("license", (1, 1)) + _start_line, end_line = para2.line_numbers_by_field.get("unknown", (1, 1)) + para1.line_numbers_by_field["license"] = ( + start_line, + end_line, + ) folded_previous = True paragraphs.append(para1) @@ -639,8 +659,7 @@ def is_valid(self, strict=False): if not strict: has_header = True - elif (len(paras) == 1 and paras[0].is_valid(strict) - and paras[0] == first): + elif len(paras) == 1 and paras[0].is_valid(strict) and paras[0] == first: has_header = True elif typ == CopyrightFilesParagraph: diff --git a/src/debian_inspector/coverage.py b/src/debian_inspector/coverage.py index ee583c7..d2ccf54 100644 --- a/src/debian_inspector/coverage.py +++ b/src/debian_inspector/coverage.py @@ -36,8 +36,7 @@ def compute(self): """ Compute the coverage and update self. """ - paragraphs = [p for p in self.paragraphs - if isinstance(p, CopyrightFilesParagraph)] + paragraphs = [p for p in self.paragraphs if isinstance(p, CopyrightFilesParagraph)] for root, _dirs, files in os.walk(self.directory, topdown=True): root = path.relpath(root, self.directory) diff --git a/src/debian_inspector/deb822.py b/src/debian_inspector/deb822.py index aef71c7..ef2046f 100644 --- a/src/debian_inspector/deb822.py +++ b/src/debian_inspector/deb822.py @@ -63,7 +63,6 @@ def get_paragraphs_as_field_groups_from_lines(numbered_lines): fields_group = [] current_field = None for idx, line in enumerate(numbered_lines): - # blank line: One or more blank line should terminates paragraph (e.g. a # fields_group) and starts a new one. There is one exception # though which is when the next line is not a new field declaration. @@ -98,7 +97,7 @@ def get_paragraphs_as_field_groups_from_lines(numbered_lines): elif line.is_field_declaration(): current_field = Deb822Field.from_line(line) if not current_field: - raise Exception(f'Invalid field line: {line}') + raise Exception(f"Invalid field line: {line}") fields_group.append(current_field) # an unknown line: we yield the curremt group and then yield this as @@ -109,7 +108,7 @@ def get_paragraphs_as_field_groups_from_lines(numbered_lines): yield fields_group # craft a synthetic header with name "unknown" - yield [Deb822Field(name='unknown', lines=[line])] + yield [Deb822Field(name="unknown", lines=[line])] fields_group = [] current_field = None @@ -123,13 +122,13 @@ def clean_fields(fields): """ Clean and return a ``fields`` list of Deb822Field. """ - for hf in (fields or []): + for hf in fields or []: hf.rstrip() return fields -is_field_declaration = re.compile(r'^[a-z]+[a-z0-9\-]*:.*$', re.IGNORECASE).match -is_field_continuation = re.compile(r'^[ \t]+[\S]+.*$', re.IGNORECASE).match +is_field_declaration = re.compile(r"^[a-z]+[a-z0-9\-]*:.*$", re.IGNORECASE).match +is_field_continuation = re.compile(r"^[ \t]+[\S]+.*$", re.IGNORECASE).match @attr.s(slots=True) @@ -137,6 +136,7 @@ class NumberedLine: """ A text line that tracks its absolute line number. Numbers start at 1. """ + number = attr.ib() value = attr.ib() @@ -180,12 +180,12 @@ def is_field_continuation(self): >>> NumberedLine(1, 'foo').is_field_continuation() False - >>> NumberedLine(1, '').is_field_continuation() - False - >>> NumberedLine(1, ' ').is_field_continuation() - False - >>> NumberedLine(1, ' ').is_field_continuation() - False + >>> NumberedLine(1, '').is_field_continuation() + False + >>> NumberedLine(1, ' ').is_field_continuation() + False + >>> NumberedLine(1, ' ').is_field_continuation() + False >>> NumberedLine(1, ' foo').is_field_continuation() True >>> NumberedLine(1, ' .').is_field_continuation() @@ -218,6 +218,7 @@ class Deb822Field: """ A Deb822Field field with a name and a list of NumberedLines. """ + # field name, normalized as stripped and lowercase name = attr.ib(default=None) @@ -225,7 +226,7 @@ class Deb822Field: @property def text(self): - return '\n'.join(l.value for l in self.lines) + return "\n".join(l.value for l in self.lines) @property def start_line(self): @@ -249,9 +250,7 @@ def rstrip(self): return self def add_continuation_line(self, line): - self.lines.append( - NumberedLine(number=line.number, value=line.value.rstrip()) - ) + self.lines.append(NumberedLine(number=line.number, value=line.value.rstrip())) @classmethod def from_line(cls, line): @@ -263,7 +262,7 @@ def from_line(cls, line): if not line or not line.is_field_declaration(): return - name, _colon, value = line.value.partition(':') + name, _colon, value = line.value.partition(":") name = name.strip().lower() if not name: return @@ -272,8 +271,8 @@ def from_line(cls, line): # license field of a paragraph as "licence". We must correct this # otherwise the license information will be stored under "licence" # instead of "license". - if name == 'licence': - name = 'license' + if name == "licence": + name = "license" value = value.strip() first_line = NumberedLine(number=line.number, value=value) diff --git a/src/debian_inspector/debcon.py b/src/debian_inspector/debcon.py index 62a8881..84c8d7a 100644 --- a/src/debian_inspector/debcon.py +++ b/src/debian_inspector/debcon.py @@ -76,6 +76,7 @@ class SingleLineField(FieldMixin): """ https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#single-line """ + value = attrib() @classmethod @@ -83,7 +84,7 @@ def from_value(cls, value): return cls(value=value and value.strip()) def dumps(self): - return self.value or '' + return self.value or "" @attrs @@ -91,6 +92,7 @@ class LineSeparatedField(FieldMixin): """ https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#line-based-lists """ + values = attrib() @classmethod @@ -102,14 +104,15 @@ def from_value(cls, value): return cls(values=values) def dumps(self, **kwargs): - return '\n '.join(self.values or []) + return "\n ".join(self.values or []) @attrs class LineAndSpaceSeparatedField(FieldMixin): """ - This is a list of values where each item is itself a space-separated list. + LineAndSpaceSeparatedField is a list of values where each item is itself a space-separated list. """ + values = attrib() @classmethod @@ -121,7 +124,7 @@ def from_value(cls, value): return cls(values=values) def dumps(self, **kwargs): - return '\n '.join(' '.join(v) for v in self.values or []) + return "\n ".join(" ".join(v) for v in self.values or []) @attrs @@ -130,6 +133,7 @@ class AnyWhiteSpaceSeparatedField(FieldMixin): https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#white-space-lists This is a list of values where each item is itself a space-separated list. """ + values = attrib() @classmethod @@ -140,7 +144,7 @@ def from_value(cls, value): return cls(values=values) def dumps(self, **kwargs): - return '\n '.join(self.values or []) + return "\n ".join(self.values or []) @attrs @@ -149,6 +153,7 @@ class FormattedTextField(FieldMixin): https://www.debian.org/doc/debian-policy/ch-controlfields#description Like Description, but there is no special meaning for the first line. """ + text = attrib() @classmethod @@ -160,7 +165,7 @@ def from_value(cls, value): def dumps(self, **kwargs): lines = line_separated(self.text) if not lines: - return '' + return "" return as_formatted_lines(lines) @@ -170,15 +175,15 @@ def as_formatted_lines(lines): continuation for multilines. """ if not lines: - return '' + return "" formatted = [] for line in lines: is_blank = not line.strip() if is_blank: - formatted.append('.') + formatted.append(".") else: - formatted.append(f'{line}') - return '\n '.join(formatted) + formatted.append(f"{line}") + return "\n ".join(formatted) def as_formatted_text(text): @@ -214,25 +219,25 @@ def from_formatted_lines(lines): text = [lines[0].strip()] for line in lines[1:]: line = line.rstrip() - if line.startswith(' '): + if line.startswith(" "): # starting with two or more spaces: displayed verbatim. text.append(line[1:]) - elif line == (' .'): + elif line == (" ."): # containing a single space followed by a single full stop # character: rendered as blank lines. - text.append('') - elif line.startswith(' .'): + text.append("") + elif line.startswith(" ."): # containing a space, a full stop and some more characters: for # future expansion.... but we keep them for now text.append(line[2:]) - elif line.startswith(' '): + elif line.startswith(" "): # starting with a single space. kept stripped text.append(line.strip()) else: # this should never happen!!! # but we keep it too text.append(line.strip()) - return '\n'.join(text) + return "\n".join(text) @attrs @@ -241,33 +246,34 @@ class DescriptionField(FieldMixin): https://www.debian.org/doc/debian-policy/ch-controlfields#description 5.6.13. Description """ + synopsis = attrib(default=None) text = attrib(default=None) @classmethod def from_value(cls, value): - value = value or '' + value = value or "" lines = line_separated(value) if lines: synopsis = lines[0].strip() text = from_formatted_lines(lines[1:]) return cls(synopsis=synopsis, text=text) else: - return cls(synopsis='') + return cls(synopsis="") def dumps(self, **kwargs): """ Return a string representation of self. """ - syn = self.synopsis or '' + syn = self.synopsis or "" syn = syn.strip() dumped = [syn] - text = self.text or '' + text = self.text or "" if text: - if text.startswith(' '): + if text.startswith(" "): text = text[1:] dumped.append(as_formatted_text(text)) - return '\n '.join(dumped) + return "\n ".join(dumped) @attrs @@ -290,18 +296,19 @@ class FileField(FieldMixin): def from_value(cls, value): checksum = size = name = None if value: - checksum, size , name = space_separated(value) - return cls(checksum=checksum, size=size , name=name) + checksum, size, name = space_separated(value) + return cls(checksum=checksum, size=size, name=name) def dumps(self, **kwargs): - return '{} {} {}'.format(self.checksum, self.size , self.name) + return "{} {} {}".format(self.checksum, self.size, self.name) @attrs class FilesField(FieldMixin): """ - This is a list of FileField + FilesField is a list of FileField """ + values = attrib() @classmethod @@ -313,7 +320,7 @@ def from_value(cls, value): return cls(values=values) def dumps(self, **kwargs): - return '\n '.join(v.dumps(**kwargs) for v in self.values or []) + return "\n ".join(v.dumps(**kwargs) for v in self.values or []) def collect_files(data): @@ -324,21 +331,21 @@ def collect_files(data): contain redundant data. """ files = {} - for name, size, md5 in collect_file(data.get('files', [])): - f = File(md5, size , name) + for name, size, md5 in collect_file(data.get("files", [])): + f = File(md5, size, name) files[name] = f - for name, size, sha1 in collect_file(data.get('checksums-sha1', [])): + for name, size, sha1 in collect_file(data.get("checksums-sha1", [])): f = files[name] assert f.size == size f.sha1 = sha1 - for name, size, sha256 in collect_file(data.get('checksums-sha256', [])): + for name, size, sha256 in collect_file(data.get("checksums-sha256", [])): f = files[name] assert f.size == size f.sha256 = sha256 - for name, size, sha512 in collect_file(data.get('checksums-v', [])): + for name, size, sha512 in collect_file(data.get("checksums-v", [])): f = files[name] assert f.size == size f.sha512 = sha512 @@ -352,7 +359,7 @@ def collect_file(value): which contains digest, size and name. """ for line in line_separated(value): - digest, size , name = space_separated(line) + digest, size, name = space_separated(line) yield name, size, digest @@ -362,6 +369,7 @@ class MaintainerField(FieldMixin): https://www.debian.org/doc/debian-policy/ch-controlfields#s-f-maintainer 5.6.2. Maintainer """ + name = attrib() email_address = attrib(default=None) @@ -379,7 +387,7 @@ def from_value(cls, value): def dumps(self, **kwargs): name = self.name if self.email_address: - name = '{} <{}>'.format(name, self.email_address) + name = "{} <{}>".format(name, self.email_address) return name.strip() @@ -399,7 +407,7 @@ def split_in_paragraphs(text): Yield paragraphs from a `text` string that contains one or more paragraph separated by empty lines. Each paragraph is a string. """ - for p in re.split(r'\n\n(?:[ \t]*\n)*', text or ''): + for p in re.split(r"\n\n(?:[ \t]*\n)*", text or ""): if p: yield p @@ -409,7 +417,7 @@ def get_paragraphs_data(text): Yield paragraph data mappings from the Debian control `text` string that contains multiple paragraphs (e.g. Package, status, copyright file, etc.). """ - for para in split_in_paragraphs(text or ''): + for para in split_in_paragraphs(text or ""): yield get_paragraph_data(para) @@ -444,7 +452,7 @@ def get_paragraph_data(text, remove_pgp_signature=False): True. """ if not text: - return {'unknown': text} + return {"unknown": text} if remove_pgp_signature: text = unsign.remove_signature(text) @@ -452,19 +460,19 @@ def get_paragraph_data(text, remove_pgp_signature=False): try: mls = email.message_from_string(text) except UnicodeEncodeError: - t = text.encode('utf-8') + t = text.encode("utf-8") mls = email.message_from_string(t) items = list(mls.items()) if not items or mls.defects: - return {'unknown': text} + return {"unknown": text} # in a header-only email we should not have a payload. Yet when this happens # we should no ignore it either, so let's treat this as "unknown" payload = mls.get_payload() if payload: - items.append(('unknown', payload)) + items.append(("unknown", payload)) data = {} for name, value in items: @@ -473,9 +481,9 @@ def get_paragraph_data(text, remove_pgp_signature=False): name = name.lower().strip() value = value.strip() if name in data: - existing_values = data.get(name, '').splitlines() + existing_values = data.get(name, "").splitlines() if value not in existing_values: - value = '\n'.join(existing_values + [value]) + value = "\n".join(existing_values + [value]) data[name] = value return data @@ -501,11 +509,11 @@ def _splitter(value, separator): def comma_separated(value): - return _splitter(value, ',') + return _splitter(value, ",") def comma_space_separated(value): - return _splitter(value, ', ') + return _splitter(value, ", ") def space_separated(value): @@ -525,12 +533,12 @@ def read_text_file(location): if not location: return try: - with io.open(location, 'r', encoding='utf-8') as tc: + with io.open(location, "r", encoding="utf-8") as tc: return tc.read() except UnicodeDecodeError: - with open(location, 'rb') as tc: + with open(location, "rb") as tc: content = tc.read() - enc = chardet.detect(content)['encoding'] + enc = chardet.detect(content)["encoding"] return content.decode(enc) @@ -554,7 +562,7 @@ def __init__(self, data=None): elif isinstance(data, str): text = data - elif hasattr(data, 'read'): + elif hasattr(data, "read"): text = data.read() elif isinstance(data, Sequence): @@ -563,7 +571,7 @@ def __init__(self, data=None): seq = list(data) first = seq[0] if isinstance(first, str): - seq = (s.partition(': ') for s in seq) + seq = (s.partition(": ") for s in seq) paragraph = {k.lower(): v for k, _, v in seq} else: # seq of (k, v) items @@ -571,9 +579,10 @@ def __init__(self, data=None): else: raise TypeError( - 'Invalid argument type. Should be one of a file-like object, ' - 'a text string, a sequence of items or a mapping but is ' - 'instead:'.format(type(data))) + "Invalid argument type. Should be one of a file-like object, " + "a text string, a sequence of items or a mapping but is " + "instead:".format(type(data)) + ) if text: # we parse in a sequence of items paragraph = get_paragraph_data(text, remove_pgp_signature=True) @@ -600,9 +609,10 @@ def __len__(self): @classmethod def from_file(cls, location, remove_pgp_signature=True): data = get_paragraph_data_from_file( - location=location, remove_pgp_signature=remove_pgp_signature) + location=location, remove_pgp_signature=remove_pgp_signature + ) if not data: - raise ValueError('Location has no parsable data: {}'.format(location)) + raise ValueError("Location has no parsable data: {}".format(location)) return Debian822(data) @classmethod @@ -611,8 +621,7 @@ def from_string(cls, text): def to_dict(self, normalize_names=False): if normalize_names: - return {normalize_control_field_name(key): value - for key, value in self.data.items()} + return {normalize_control_field_name(key): value for key, value in self.data.items()} else: return dict(self.data) @@ -630,22 +639,22 @@ def dumps(self, **kwargs): lines = [] for key, value in items: key = normalize_control_field_name(key) - lines.append('{}: {}'.format(key, value)) - text = '\n'.join(lines) + '\n' + lines.append("{}: {}".format(key, value)) + text = "\n".join(lines) + "\n" return text def dump(self, file_like=None, **kwargs): text = self.dumps(**kwargs) if file_like: - file_like.write(text.encode('utf-8')) + file_like.write(text.encode("utf-8")) else: return text DEFAULT_CONTROL_FIELDS = { - 'Architecture': 'all', - 'Priority': 'optional', - 'Section': 'misc', + "Architecture": "all", + "Priority": "optional", + "Section": "misc", } @@ -660,27 +669,28 @@ def load_control_file(control_file): return parse_control_fields(Debian822(inp)) -DEPS_FIELDS = frozenset([ - # Binary control file fields. - 'Breaks', - 'Conflicts', - 'Depends', - 'Enhances', - 'Pre-Depends', - 'Provides', - 'Recommends', - 'Replaces', - 'Suggests', - - # Source control file fields. - 'Build-Conflicts', - 'Build-Conflicts-Arch', - 'Build-Conflicts-Indep', - 'Build-Depends', - 'Build-Depends-Arch', - 'Build-Depends-Indep', - 'Built-Using', -]) +DEPS_FIELDS = frozenset( + [ + # Binary control file fields. + "Breaks", + "Conflicts", + "Depends", + "Enhances", + "Pre-Depends", + "Provides", + "Recommends", + "Replaces", + "Suggests", + # Source control file fields. + "Build-Conflicts", + "Build-Conflicts-Arch", + "Build-Conflicts-Indep", + "Build-Depends", + "Build-Depends-Arch", + "Build-Depends-Indep", + "Built-Using", + ] +) def parse_control_fields(input_fields, deps_fields=DEPS_FIELDS): @@ -695,12 +705,13 @@ def parse_control_fields(input_fields, deps_fields=DEPS_FIELDS): native type (here an integer). """ from debian_inspector import deps + output_fields = {} for name, unparsed_value in input_fields.items(): name = normalize_control_field_name(name) if name in deps_fields: parsed_value = deps.parse_depends(unparsed_value) - elif name == 'Installed-Size': + elif name == "Installed-Size": parsed_value = int(unparsed_value) else: parsed_value = unparsed_value @@ -722,8 +733,5 @@ def normalize_control_field_name(name): http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-controlsyntax """ - special_cases = dict(md5sum='MD5sum', sha1='SHA1', sha256='SHA256') - return '-'.join(special_cases.get( - w.lower(), w.capitalize()) - for w in name.split('-') - ) + special_cases = dict(md5sum="MD5sum", sha1="SHA1", sha256="SHA256") + return "-".join(special_cases.get(w.lower(), w.capitalize()) for w in name.split("-")) diff --git a/src/debian_inspector/deps.py b/src/debian_inspector/deps.py index bde0ecc..053f45c 100644 --- a/src/debian_inspector/deps.py +++ b/src/debian_inspector/deps.py @@ -27,7 +27,8 @@ # Define a compiled regular expression pattern that we will use to match # package relationship expressions consisting of a package name followed by # optional version and architecture restrictions. -parse_package_relationship_expression = re.compile(r''' +parse_package_relationship_expression = re.compile( + r""" # Capture all leading characters up to (but not including) # the first parenthesis, bracket or space. (?P [^\(\[ ]+ ) @@ -39,7 +40,9 @@ \s* # Optionally capture architecture restriction inside brackets. ( \[ (?P [^\]]+ ) \] )? -''', re.VERBOSE).match +""", + re.VERBOSE, +).match ARCHITECTURE_RESTRICTIONS_MESSAGE = "Architecture constraint is not implemented." @@ -60,7 +63,7 @@ def parse_depends(relationships): """ if isinstance(relationships, str): - relationships = (r.strip() for r in relationships.split(',') if r.strip()) + relationships = (r.strip() for r in relationships.split(",") if r.strip()) return AndRelationships.from_relationships(*(map(parse_alternatives, relationships))) @@ -76,8 +79,8 @@ def parse_alternatives(expression): Each pipe-separated sub-expression is parsed with `parse_relationship()` """ - if '|' in expression: - alternatives = (a.strip() for a in expression.split('|') if a.strip()) + if "|" in expression: + alternatives = (a.strip() for a in expression.split("|") if a.strip()) alternatives = (parse_relationship(a) for a in alternatives) return OrRelationships.from_relationships(*alternatives) else: @@ -85,7 +88,7 @@ def parse_alternatives(expression): # split on operators -split_on_ops = re.compile('([<>=]+)').split +split_on_ops = re.compile("([<>=]+)").split def parse_relationship(expression): @@ -105,15 +108,15 @@ def parse_relationship(expression): """ try: pre = parse_package_relationship_expression(expression) - name = pre.group('name') - version = pre.group('version') + name = pre.group("name") + version = pre.group("version") except AttributeError: - print('gd:', pre.groupdict()) - print('this:', repr(expression)) + print("gd:", pre.groupdict()) + print("this:", repr(expression)) raise # Split the architecture restrictions into a tuple of strings. - architectures = tuple((pre.group('architectures') or '').split()) + architectures = tuple((pre.group("architectures") or "").split()) if name and not version: # A package name (and optional architecture restrictions) without @@ -127,20 +130,17 @@ def parse_relationship(expression): if len(tokens) != 2: # Encountered something unexpected! raise ValueError( - 'Corrupt package relationship expression: Splitting operator ' - 'from version resulted in more than two tokens! ' - '(expression: {e}, tokens: {t})'.format(e=expression, t=tokens) + "Corrupt package relationship expression: Splitting operator " + "from version resulted in more than two tokens! " + "(expression: {e}, tokens: {t})".format(e=expression, t=tokens) ) return VersionedRelationship( - name=name, - architectures=architectures, - operator=tokens[0], - version=tokens[1]) + name=name, architectures=architectures, operator=tokens[0], version=tokens[1] + ) @attrs class AbstractRelationship(object): - @property def names(self): """ @@ -190,9 +190,8 @@ def __str__(self, *args, **kwargs): if not self.architectures: return self.name - return '{name} {arches}'.format( - name=self.name, - arches='[{}]'.format(' '.join(self.architectures)) + return "{name} {arches}".format( + name=self.name, arches="[{}]".format(" ".join(self.architectures)) ) def to_dict(self): @@ -227,15 +226,14 @@ def matches(self, name, version=None, architecture=None): return None def __str__(self, *args, **kwargs): - s = f'{self.name} ({self.operator} {self.version})' + s = f"{self.name} ({self.operator} {self.version})" if self.architectures: - s += ' [{}]'.format(' '.join(self.architectures)) + s += " [{}]".format(" ".join(self.architectures)) return s @attrs class MultipleRelationship(AbstractRelationship): - relationships = attrib(default=tuple()) @classmethod @@ -282,7 +280,7 @@ def matches(self, name, version=None, architecture=None): return matches def __str__(self, *args, **kwargs): - return ' | '.join(str(r) for r in self.relationships) + return " | ".join(str(r) for r in self.relationships) @attrs @@ -307,5 +305,4 @@ def matches(self, name, version=None, architecture=None): return None def __str__(self, *args, **kwargs): - return ', '.join(str(r) for r in self.relationships) - + return ", ".join(str(r) for r in self.relationships) diff --git a/src/debian_inspector/package.py b/src/debian_inspector/package.py index 50de34f..39b0cc3 100644 --- a/src/debian_inspector/package.py +++ b/src/debian_inspector/package.py @@ -28,6 +28,7 @@ class DebArchive(object): """ A .deb binary package archive. """ + name = attrib() version = attrib() architecture = attrib() @@ -44,17 +45,15 @@ def from_filename(cls, filename): name, version, architecture = get_nva(path.basename(filename)) return cls( - name=name, - version=version, - architecture=architecture, - original_filename=filename) + name=name, version=version, architecture=architecture, original_filename=filename + ) def to_dict(self): data = {} - data['name'] = self.name - data['version'] = self.version - data['architecture'] = self.architecture - data['original_filename'] = self.original_filename + data["name"] = self.name + data["version"] = self.version + data["architecture"] = self.architecture + data["original_filename"] = self.original_filename return data def to_tuple(self): @@ -62,7 +61,7 @@ def to_tuple(self): Return a tuple of name, Version, architecture suitable for sorting. This tuple does not contain the original_filename value. """ - return tuple(v for v in self.to_dict().values() if v != 'original_filename') + return tuple(v for v in self.to_dict().values() if v != "original_filename") @attrs @@ -76,6 +75,7 @@ class CodeArchive(object): - apr-util_1.6.1-4.debian.tar.xz that contains the Debian patches and control files """ + name = attrib() version = attrib() original_filename = attrib(default=None) @@ -90,17 +90,13 @@ def from_filename(cls, filename): return filename name, version, _architecture = get_nva(path.basename(filename)) - return cls( - name=name, - version=version, - original_filename=filename - ) + return cls(name=name, version=version, original_filename=filename) def to_dict(self): data = {} - data['name'] = self.name - data['version'] = self.version - data['original_filename'] = self.original_filename + data["name"] = self.name + data["version"] = self.version + data["original_filename"] = self.original_filename return data def to_tuple(self): @@ -108,7 +104,7 @@ def to_tuple(self): Return a tuple of name, Vresion, architecture suitable for sorting. This tuple does not contain the original_filename values. """ - return tuple(v for v in self.to_dict().values() if v != 'original_filename') + return tuple(v for v in self.to_dict().values() if v != "original_filename") @attrs @@ -133,38 +129,36 @@ def get_nva(filename): None) parsed from the `filename` of .deb, .udeb, .orig or .debian archive.. """ is_known = False - if filename.endswith(('.deb', '.udeb', '.dsc')): + if filename.endswith((".deb", ".udeb", ".dsc")): basename, _extension = path.splitext(filename) is_known = True - elif filename.endswith(('_changelog', '_copyright')): + elif filename.endswith(("_changelog", "_copyright")): basename, _, _ = filename.rpartition("_") is_known = True - elif filename.endswith(('.tar.gz', '.tar.xz', '.tar.bz2', '.tar.lzma')): + elif filename.endswith((".tar.gz", ".tar.xz", ".tar.bz2", ".tar.lzma")): # A Format: 3.0 archive. # Note that we ignore the legacy .diff.gz files for Format: 1.0 - basename, _, _ = filename.rpartition('.tar.') + basename, _, _ = filename.rpartition(".tar.") # remove the .orig or .debian basename, pkgtype = path.splitext(basename) - if pkgtype in ('.orig', '.debian'): + if pkgtype in (".orig", ".debian"): is_known = True if not is_known: - raise ValueError( - 'Unknown Debian archive filename format: {}'.format(filename)) + raise ValueError("Unknown Debian archive filename format: {}".format(filename)) - parts = basename.split('_') + parts = basename.split("_") if len(parts) == 2: arch = None name, evr = parts elif len(parts) == 3: - name, evr , arch = parts + name, evr, arch = parts else: - raise ValueError( - 'Unknown Debian archive filename format: {}'.format(filename)) + raise ValueError("Unknown Debian archive filename format: {}".format(filename)) return name, Version.from_string(evr), arch @@ -205,8 +199,8 @@ def find_latest_version(packages): packages = sorted(packages, key=lambda p: p.to_tuple()) names = set(p.name for p in packages) if len(names) > 1: - msg = 'Cannot compare versions of different package names' - raise ValueError(msg.format(' '.join(sorted(names)))) + msg = "Cannot compare versions of different package names" + raise ValueError(msg.format(" ".join(sorted(names)))) return packages[-1] diff --git a/src/debian_inspector/unsign.py b/src/debian_inspector/unsign.py index e113b7d..1a1a5d4 100644 --- a/src/debian_inspector/unsign.py +++ b/src/debian_inspector/unsign.py @@ -23,8 +23,10 @@ def is_signed(text): """ if text and isinstance(text, str): text = text.strip() - return text and (text.startswith('-----BEGIN PGP SIGNED MESSAGE-----') - and text.endswith('-----END PGP SIGNATURE-----')) + return text and ( + text.startswith("-----BEGIN PGP SIGNED MESSAGE-----") + and text.endswith("-----END PGP SIGNATURE-----") + ) return False @@ -39,9 +41,10 @@ def remove_signature(text): signed = pgp_signed(text) if not signed: return text - unsigned = signed.groupdict().get('cleartext') + unsigned = signed.groupdict().get("cleartext") return unsigned + # A re.VERBOSE regular expression to parse a PGP signed message in its parts. # the re.VERBOSE flag allows for: # - whitespace is ignored except when in a character class or escaped @@ -49,7 +52,8 @@ def remove_signature(text): # ignored, allowing for comments -pgp_signed = re.compile(r""" +pgp_signed = re.compile( + r""" # This capture group is optional because it will only be present in signed # cleartext messages @@ -81,4 +85,6 @@ def remove_signature(text): ^-{5}END\ PGP\ (?P=magic)-{5}(?:\r?\n)? - """, flags=re.MULTILINE | re.VERBOSE).search # NOQA + """, + flags=re.MULTILINE | re.VERBOSE, +).search # NOQA diff --git a/src/debian_inspector/utils.py b/src/debian_inspector/utils.py index 81e6c66..fa5a16b 100644 --- a/src/debian_inspector/utils.py +++ b/src/debian_inspector/utils.py @@ -44,15 +44,15 @@ def find_debian_architecture(): ``dpkg-architecture`` program is not available or reports an error. - .. _machine architecture labels: https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Architecture + .. _machine architecture labels: https://www.debian.org/doc/debian-policy/ch-controlfields.html#architecture .. _more architectures: https://www.debian.org/ports/index.en.html#portlist-released """ _sysname, _nodename, _release, _version, machine = os.uname() - if machine == 'i686': - return 'i386' - elif machine == 'x86_64': - return 'amd64' - elif machine == 'armv6l': - return 'armhf' + if machine == "i686": + return "i386" + elif machine == "x86_64": + return "amd64" + elif machine == "armv6l": + return "armhf" else: - raise Exception('unknown machine') + raise Exception("unknown machine") diff --git a/src/debian_inspector/version.py b/src/debian_inspector/version.py index f070b96..519d7d1 100644 --- a/src/debian_inspector/version.py +++ b/src/debian_inspector/version.py @@ -26,7 +26,7 @@ This module is an implementation of the version comparison and sorting algorithm described at -https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version +https://www.debian.org/doc/debian-policy/ch-controlfields.html#version This has been substantially modified and enhanced from the original python-dpkg Dpkg class by Nathan J. Meh and team from The Climate Corporation and @@ -57,14 +57,7 @@ """ -@attrs( - eq=False, - order=False, - frozen=True, - hash=False, - slots=True, - str=False -) +@attrs(eq=False, order=False, frozen=True, hash=False, slots=True, str=False) class Version(object): """ Rich comparison of Debian package versions as first-class Python objects. @@ -83,18 +76,19 @@ class Version(object): demonstrate that this version sorting order is different from regular sorting and 'natural order sorting'. """ + epoch = attrib(default=0) upstream = attrib(default=None) - revision = attrib(default='0') + revision = attrib(default="0") def __str__(self, *args, **kwargs): if self.epoch: - version = f'{self.epoch}:{self.upstream}' + version = f"{self.epoch}:{self.upstream}" else: - version = f'{self.upstream}' + version = f"{self.upstream}" - if self.revision not in (None, '0'): - version += f'-{self.revision}' + if self.revision not in (None, "0"): + version += f"-{self.revision}" return version @@ -112,45 +106,45 @@ def __ne__(self, other): def __lt__(self, other): if type(self) is type(other): - return eval_constraint(self, '<<', other) + return eval_constraint(self, "<<", other) return NotImplemented def __le__(self, other): if type(self) is type(other): - return eval_constraint(self, '<=', other) + return eval_constraint(self, "<=", other) return NotImplemented def __gt__(self, other): if type(self) is type(other): - return eval_constraint(self, '>>', other) + return eval_constraint(self, ">>", other) return NotImplemented def __ge__(self, other): if type(self) is type(other): - return eval_constraint(self, '>=', other) + return eval_constraint(self, ">=", other) return NotImplemented @classmethod def from_string(cls, version): - if not version and not isinstance(version , str): + if not version and not isinstance(version, str): raise ValueError('Invalid version string: "{}"'.format(version)) version = version.strip() if not version: raise ValueError('Invalid version string: "{}"'.format(version)) - if not _is_valid_version(version): + if not _is_valid_version(version): raise ValueError('Invalid version string: "{}"'.format(version)) if ":" in version: - epoch, _, version = version.partition(':') + epoch, _, version = version.partition(":") epoch = int(epoch) else: epoch = 0 if "-" in version: - upstream, _, revision = version.rpartition('-') + upstream, _, revision = version.rpartition("-") else: upstream = version - revision = '0' + revision = "0" return cls(epoch=epoch, upstream=upstream, revision=revision) def compare(self, other_version): @@ -164,22 +158,23 @@ def tuple(self): _is_valid_version = re.compile( - r'^' + r"^" # epoch must start with a digit - r'(\d+:)?' + r"(\d+:)?" # upstream must start with a digit - r'\d' - r'(' - # upstream can contain only alphanumerics and the characters . + - - # ~ (full stop, plus, hyphen, tilde) - # we are adding the extra check that it must end with alphanum - r'[A-Za-z0-9\.\+\-\~]*[A-Za-z0-9]' - r'|' - # If there is no debian_revision then hyphens are not allowed. - # we are adding the extra check that it must end with alphanum - r'[A-Za-z0-9\.\+\~]*[A-Za-z0-9]-[A-Za-z0-9\+\.\~]*[A-Za-z0-9\~]' - r')?' - r'$').match + r"\d" + r"(" + # upstream can contain only alphanumerics and the characters . + - + # ~ (full stop, plus, hyphen, tilde) + # we are adding the extra check that it must end with alphanum + r"[A-Za-z0-9\.\+\-\~]*[A-Za-z0-9]" + r"|" + # If there is no debian_revision then hyphens are not allowed. + # we are adding the extra check that it must end with alphanum + r"[A-Za-z0-9\.\+\~]*[A-Za-z0-9]-[A-Za-z0-9\+\.\~]*[A-Za-z0-9\~]" + r")?" + r"$" +).match def eval_constraint(version1, operator, version2): @@ -195,24 +190,21 @@ def eval_constraint(version1, operator, version2): result = compare_versions(version1, version2) # See https://www.debian.org/doc/debian-policy/ch-relationships.html#syntax-of-relationship-fields operators = { - '<=': operator_module.le, + "<=": operator_module.le, # legacy for compat - '<': operator_module.le, - - '>=': operator_module.ge, + "<": operator_module.le, + ">=": operator_module.ge, # legacy for compat - '>': operator_module.ge, - - '<<': operator_module.lt, - '>>': operator_module.gt, - - '=': operator_module.eq, + ">": operator_module.ge, + "<<": operator_module.lt, + ">>": operator_module.gt, + "=": operator_module.eq, } try: operator = operators[operator] except KeyError: - msg = f'Unsupported Debian version constraint comparison operator: {version1} {operator} {version2}' + msg = f"Unsupported Debian version constraint comparison operator: {version1} {operator} {version2}" raise ValueError(msg) return operator(result, 0) @@ -257,14 +249,24 @@ def compare_strings(version1, version2): if p1 != p2: logger.debug("Comparing non-digit prefixes %r and %r ..", p1, p2) for c1, c2 in zip_longest(p1, p2, fillvalue=""): - logger.debug("Performing lexical comparison between characters %r and %r ..", c1, c2) + logger.debug( + "Performing lexical comparison between characters %r and %r ..", c1, c2 + ) o1 = mapping.get(c1) o2 = mapping.get(c2) if o1 < o2: - logger.debug("Determined that %r sorts before %r (based on lexical comparison).", version1, version2) + logger.debug( + "Determined that %r sorts before %r (based on lexical comparison).", + version1, + version2, + ) return -1 elif o1 > o2: - logger.debug("Determined that %r sorts after %r (based on lexical comparison).", version1, version2) + logger.debug( + "Determined that %r sorts after %r (based on lexical comparison).", + version1, + version2, + ) return 1 elif p1: logger.debug("Skipping matching non-digit prefix %r ..", p1) @@ -278,10 +280,18 @@ def compare_strings(version1, version2): d2 = get_digit_prefix(v2) logger.debug("Comparing numeric prefixes %i and %i ..", d1, d2) if d1 < d2: - logger.debug("Determined that %r sorts before %r (based on numeric comparison).", version1, version2) + logger.debug( + "Determined that %r sorts before %r (based on numeric comparison).", + version1, + version2, + ) return -1 elif d1 > d2: - logger.debug("Determined that %r sorts after %r (based on numeric comparison).", version1, version2) + logger.debug( + "Determined that %r sorts after %r (based on numeric comparison).", + version1, + version2, + ) return 1 else: logger.debug("Determined that numeric prefixes match.") @@ -359,64 +369,64 @@ def get_non_digit_prefix(characters): # a mapping of characters to integers representing the Debian sort order. characters_order = { # The tilde sorts before everything. - '~': 0, + "~": 0, # The empty string sort before everything except a tilde. - '': 1, + "": 1, # Letters sort before everything but a tilde or empty string, in their regular lexical sort order. - 'A': 2, - 'B': 3, - 'C': 4, - 'D': 5, - 'E': 6, - 'F': 7, - 'G': 8, - 'H': 9, - 'I': 10, - 'J': 11, - 'K': 12, - 'L': 13, - 'M': 14, - 'N': 15, - 'O': 16, - 'P': 17, - 'Q': 18, - 'R': 19, - 'S': 20, - 'T': 21, - 'U': 22, - 'V': 23, - 'W': 24, - 'X': 25, - 'Y': 26, - 'Z': 27, - 'a': 28, - 'b': 29, - 'c': 30, - 'd': 31, - 'e': 32, - 'f': 33, - 'g': 34, - 'h': 35, - 'i': 36, - 'j': 37, - 'k': 38, - 'l': 39, - 'm': 40, - 'n': 41, - 'o': 42, - 'p': 43, - 'q': 44, - 'r': 45, - 's': 46, - 't': 47, - 'u': 48, - 'v': 49, - 'w': 50, - 'x': 51, - 'y': 52, - 'z': 53, + "A": 2, + "B": 3, + "C": 4, + "D": 5, + "E": 6, + "F": 7, + "G": 8, + "H": 9, + "I": 10, + "J": 11, + "K": 12, + "L": 13, + "M": 14, + "N": 15, + "O": 16, + "P": 17, + "Q": 18, + "R": 19, + "S": 20, + "T": 21, + "U": 22, + "V": 23, + "W": 24, + "X": 25, + "Y": 26, + "Z": 27, + "a": 28, + "b": 29, + "c": 30, + "d": 31, + "e": 32, + "f": 33, + "g": 34, + "h": 35, + "i": 36, + "j": 37, + "k": 38, + "l": 39, + "m": 40, + "n": 41, + "o": 42, + "p": 43, + "q": 44, + "r": 45, + "s": 46, + "t": 47, + "u": 48, + "v": 49, + "w": 50, + "x": 51, + "y": 52, + "z": 53, # Punctuation characters follow in their regular lexical sort order. - '+': 54, - '-': 55, - '.': 56, + "+": 54, + "-": 55, + ".": 56, } diff --git a/tests/dpkg-tests/test_version_dpkg.py b/tests/dpkg-tests/test_version_dpkg.py index a4cc839..3766233 100644 --- a/tests/dpkg-tests/test_version_dpkg.py +++ b/tests/dpkg-tests/test_version_dpkg.py @@ -34,7 +34,6 @@ class VersionTests(TestCase): - def test_version_compare(self): a = DPKG_VERSION_OBJECT(0, "1", "1") b = DPKG_VERSION_OBJECT(0, "2", "1") @@ -76,30 +75,29 @@ def test_version_compare(self): # FIXME: Complete. def test_version_relate(self): - a = DPKG_VERSION_OBJECT(0, "1", "1") b = DPKG_VERSION_OBJECT(0, "1", "1") - assert dpkg_version_relate(a, '=', b) - assert not dpkg_version_relate(a, '<<', b) - assert dpkg_version_relate(a, '<=', b) - assert not dpkg_version_relate(a, '>>', b) - assert dpkg_version_relate(a, '>=', b) + assert dpkg_version_relate(a, "=", b) + assert not dpkg_version_relate(a, "<<", b) + assert dpkg_version_relate(a, "<=", b) + assert not dpkg_version_relate(a, ">>", b) + assert dpkg_version_relate(a, ">=", b) a = DPKG_VERSION_OBJECT(0, "1", "1") b = DPKG_VERSION_OBJECT(0, "2", "1") - assert not dpkg_version_relate(a, '=', b) - assert dpkg_version_relate(a, '<<', b) - assert dpkg_version_relate(a, '<=', b) - assert not dpkg_version_relate(a, '>>', b) - assert not dpkg_version_relate(a, '>=', b) + assert not dpkg_version_relate(a, "=", b) + assert dpkg_version_relate(a, "<<", b) + assert dpkg_version_relate(a, "<=", b) + assert not dpkg_version_relate(a, ">>", b) + assert not dpkg_version_relate(a, ">=", b) a = DPKG_VERSION_OBJECT(0, "2", "1") b = DPKG_VERSION_OBJECT(0, "1", "1") - assert not dpkg_version_relate(a, '=', b) - assert not dpkg_version_relate(a, '<<', b) - assert not dpkg_version_relate(a, '<=', b) - assert dpkg_version_relate(a, '>>', b) - assert dpkg_version_relate(a, '>=', b) + assert not dpkg_version_relate(a, "=", b) + assert not dpkg_version_relate(a, "<<", b) + assert not dpkg_version_relate(a, "<=", b) + assert dpkg_version_relate(a, ">>", b) + assert dpkg_version_relate(a, ">=", b) def test_version_parse(self): b = DPKG_VERSION_OBJECT(0, "0", "") @@ -201,7 +199,6 @@ def test_version_parse_raise_exception_on_multiple_colons(self): self.assertRaises(ValueError, parseversion, "0:0-azAZ09.+~") def test_version_parse_exceptions(self): - # Test empty version. self.assertRaises(ValueError, parseversion, "") self.assertRaises(ValueError, parseversion, " ") @@ -237,12 +234,12 @@ def test_version_parse_exceptions(self): # Test invalid characters in upstream version. for p in "!#@$%&/|\\<>()[]{},_=*^'": - verstr = '0:0a' + p + '-0' + verstr = "0:0a" + p + "-0" self.assertRaises(ValueError, parseversion, verstr) # Test invalid characters in revision. self.assertRaises(ValueError, parseversion, "0:0-0:0") for p in "!#@$%&/|\\<>()[]{}:,_=*^'": - verstr = '0:0-0' + p + verstr = "0:0-0" + p self.assertRaises(ValueError, parseversion, verstr) diff --git a/tests/dpkg-tests/test_version_python_debian.py b/tests/dpkg-tests/test_version_python_debian.py index 01d6326..c683cde 100644 --- a/tests/dpkg-tests/test_version_python_debian.py +++ b/tests/dpkg-tests/test_version_python_debian.py @@ -37,9 +37,9 @@ def check_Version_from_string(version_string, epoch, upstream, revision): def check_compare_versions(version1, operator, version2): expected_ops = { - '==': 0, - '>': 1, - '<':-1, + "==": 0, + ">": 1, + "<": -1, } expected = expected_ops[operator] compared = version.compare_versions(version1, version2) @@ -47,46 +47,47 @@ def check_compare_versions(version1, operator, version2): class VersionTests(TestCase): - def test_Version_from_string(self): - check_Version_from_string('1:1.4.1-1', 1, '1.4.1', '1') - check_Version_from_string('7.1.ds-1', 0, '7.1.ds', '1') - check_Version_from_string('10.11.1.3-2', 0, '10.11.1.3', '2') - check_Version_from_string('4.0.1.3.dfsg.1-2', 0, '4.0.1.3.dfsg.1', '2') - check_Version_from_string('0.4.23debian1', 0, '0.4.23debian1', '0') - check_Version_from_string('1.2.10+cvs20060429-1', 0, '1.2.10+cvs20060429', '1') - check_Version_from_string('0.2.0-1+b1', 0, '0.2.0', '1+b1') - check_Version_from_string('4.3.90.1svn-r21976-1', 0, '4.3.90.1svn-r21976', '1') - check_Version_from_string('1.5+E-14', 0, '1.5+E', '14') - check_Version_from_string('20060611-0.0', 0, '20060611', '0.0') - check_Version_from_string('0.52.2-5.1', 0, '0.52.2', '5.1') - check_Version_from_string('7.0-035+1', 0, '7.0', '035+1') - check_Version_from_string('1.1.0+cvs20060620-1+2.6.15-8', 0, '1.1.0+cvs20060620-1+2.6.15', '8') - check_Version_from_string('1.1.0+cvs20060620-1+1.0', 0, '1.1.0+cvs20060620', '1+1.0') - check_Version_from_string('4.2.0a+stable-2sarge1', 0, '4.2.0a+stable', '2sarge1') - check_Version_from_string('1.8RC4b', 0, '1.8RC4b', '0') - check_Version_from_string('0.9~rc1-1', 0, '0.9~rc1', '1') - check_Version_from_string('2:1.0.4+svn26-1ubuntu1', 2, '1.0.4+svn26', '1ubuntu1') - check_Version_from_string('2:1.0.4~rc2-1', 2, '1.0.4~rc2', '1') - self.assertRaises(ValueError, version.Version.from_string, 'a1:1.8.8-070403-1~priv1') + check_Version_from_string("1:1.4.1-1", 1, "1.4.1", "1") + check_Version_from_string("7.1.ds-1", 0, "7.1.ds", "1") + check_Version_from_string("10.11.1.3-2", 0, "10.11.1.3", "2") + check_Version_from_string("4.0.1.3.dfsg.1-2", 0, "4.0.1.3.dfsg.1", "2") + check_Version_from_string("0.4.23debian1", 0, "0.4.23debian1", "0") + check_Version_from_string("1.2.10+cvs20060429-1", 0, "1.2.10+cvs20060429", "1") + check_Version_from_string("0.2.0-1+b1", 0, "0.2.0", "1+b1") + check_Version_from_string("4.3.90.1svn-r21976-1", 0, "4.3.90.1svn-r21976", "1") + check_Version_from_string("1.5+E-14", 0, "1.5+E", "14") + check_Version_from_string("20060611-0.0", 0, "20060611", "0.0") + check_Version_from_string("0.52.2-5.1", 0, "0.52.2", "5.1") + check_Version_from_string("7.0-035+1", 0, "7.0", "035+1") + check_Version_from_string( + "1.1.0+cvs20060620-1+2.6.15-8", 0, "1.1.0+cvs20060620-1+2.6.15", "8" + ) + check_Version_from_string("1.1.0+cvs20060620-1+1.0", 0, "1.1.0+cvs20060620", "1+1.0") + check_Version_from_string("4.2.0a+stable-2sarge1", 0, "4.2.0a+stable", "2sarge1") + check_Version_from_string("1.8RC4b", 0, "1.8RC4b", "0") + check_Version_from_string("0.9~rc1-1", 0, "0.9~rc1", "1") + check_Version_from_string("2:1.0.4+svn26-1ubuntu1", 2, "1.0.4+svn26", "1ubuntu1") + check_Version_from_string("2:1.0.4~rc2-1", 2, "1.0.4~rc2", "1") + self.assertRaises(ValueError, version.Version.from_string, "a1:1.8.8-070403-1~priv1") def test_compare_versions(self): - check_compare_versions('1.0', '<', '1.1') - check_compare_versions('1.2', '<', '1.11') - check_compare_versions('1.0-0.1', '<', '1.1') - check_compare_versions('1.0-0.1', '<', '1.0-1') - check_compare_versions('1.0', '==', '1.0') - check_compare_versions('1.0-0.1', '==', '1.0-0.1') - check_compare_versions('1:1.0-0.1', '==', '1:1.0-0.1') - check_compare_versions('1:1.0', '==', '1:1.0') - check_compare_versions('1.0-0.1', '<', '1.0-1') - check_compare_versions('1.0final-5sarge1', '>', '1.0final-5') - check_compare_versions('1.0final-5', '>', '1.0a7-2') - check_compare_versions('0.9.2-5', '<', '0.9.2+cvs.1.0.dev.2004.07.28-1.5') - check_compare_versions('1:500', '<', '1:5000') - check_compare_versions('100:500', '>', '11:5000') - check_compare_versions('1.0.4-2', '>', '1.0pre7-2') - check_compare_versions('1.5~rc1', '<', '1.5') - check_compare_versions('1.5~rc1', '<', '1.5+b1') - check_compare_versions('1.5~rc1', '<', '1.5~rc2') - check_compare_versions('1.5~rc1', '>', '1.5~dev0') + check_compare_versions("1.0", "<", "1.1") + check_compare_versions("1.2", "<", "1.11") + check_compare_versions("1.0-0.1", "<", "1.1") + check_compare_versions("1.0-0.1", "<", "1.0-1") + check_compare_versions("1.0", "==", "1.0") + check_compare_versions("1.0-0.1", "==", "1.0-0.1") + check_compare_versions("1:1.0-0.1", "==", "1:1.0-0.1") + check_compare_versions("1:1.0", "==", "1:1.0") + check_compare_versions("1.0-0.1", "<", "1.0-1") + check_compare_versions("1.0final-5sarge1", ">", "1.0final-5") + check_compare_versions("1.0final-5", ">", "1.0a7-2") + check_compare_versions("0.9.2-5", "<", "0.9.2+cvs.1.0.dev.2004.07.28-1.5") + check_compare_versions("1:500", "<", "1:5000") + check_compare_versions("100:500", ">", "11:5000") + check_compare_versions("1.0.4-2", ">", "1.0pre7-2") + check_compare_versions("1.5~rc1", "<", "1.5") + check_compare_versions("1.5~rc1", "<", "1.5+b1") + check_compare_versions("1.5~rc1", "<", "1.5~rc2") + check_compare_versions("1.5~rc1", ">", "1.5~dev0") diff --git a/tests/test_contents.py b/tests/test_contents.py index e751ce5..7c4d17a 100644 --- a/tests/test_contents.py +++ b/tests/test_contents.py @@ -15,25 +15,25 @@ class TestContentsParse(JsonTester): - test_data_dir = path.join(path.dirname(__file__), 'data') + test_data_dir = path.join(path.dirname(__file__), "data") def test_parse_contents_ubuntu_with_header_plain(self): - test_file = self.get_test_loc('contents/ubuntu_Contents-i386') - expected_loc = 'contents/ubuntu_Contents-i386-expected.json' + test_file = self.get_test_loc("contents/ubuntu_Contents-i386") + expected_loc = "contents/ubuntu_Contents-i386-expected.json" results = contents.parse_contents(test_file, has_header=True) self.check_json(results, expected_loc, regen=False) def test_parse_contents_debian_no_header_gzipped(self): - test_file = self.get_test_loc('contents/debian_Contents-amd64.gz') - expected_loc = 'contents/debian_Contents-amd64.gz-expected.json' + test_file = self.get_test_loc("contents/debian_Contents-amd64.gz") + expected_loc = "contents/debian_Contents-amd64.gz-expected.json" results = contents.parse_contents(test_file, has_header=False) self.check_json(results, expected_loc, regen=False) def test_parse_contents_debian_is_same_gzipped_or_not(self): - test_file = self.get_test_loc('contents/debian_Contents-amd64.gz') + test_file = self.get_test_loc("contents/debian_Contents-amd64.gz") results = contents.parse_contents(test_file, has_header=False) - test_file2 = self.get_test_loc('contents/debian_Contents-amd64') + test_file2 = self.get_test_loc("contents/debian_Contents-amd64") results2 = contents.parse_contents(test_file2, has_header=False) assert results == results2 diff --git a/tests/test_copyright.py b/tests/test_copyright.py index 80e9f4d..db8a668 100644 --- a/tests/test_copyright.py +++ b/tests/test_copyright.py @@ -14,136 +14,135 @@ class TestCopyrightFields(JsonTester): - test_data_dir = path.join(path.dirname(__file__), 'data') + test_data_dir = path.join(path.dirname(__file__), "data") def test_CopyrightStatementField(self): - test = ' 2012-12 MyCom inc. ' + test = " 2012-12 MyCom inc. " results = copyright.CopyrightStatementField.from_value(test) - assert results.year_range == '2012-12' - assert results.holder == 'MyCom inc.' - assert results.dumps() == '2012-12 MyCom inc.' + assert results.year_range == "2012-12" + assert results.holder == "MyCom inc." + assert results.dumps() == "2012-12 MyCom inc." def test_CopyrightStatementField_no_year(self): - test = ' MyCom inc. ' + test = " MyCom inc. " results = copyright.CopyrightStatementField.from_value(test) - assert results.holder == 'MyCom inc.' + assert results.holder == "MyCom inc." assert not results.year_range - assert results.dumps() == 'MyCom inc.' + assert results.dumps() == "MyCom inc." def test_is_year_range(self): - assert copyright.is_year_range('2012') - assert copyright.is_year_range('2012-1999') - assert not copyright.is_year_range('2012-now') - assert not copyright.is_year_range(' MyCom ') - assert not copyright.is_year_range(' ') - assert not copyright.is_year_range('') + assert copyright.is_year_range("2012") + assert copyright.is_year_range("2012-1999") + assert not copyright.is_year_range("2012-now") + assert not copyright.is_year_range(" MyCom ") + assert not copyright.is_year_range(" ") + assert not copyright.is_year_range("") assert not copyright.is_year_range(None) def test_LicenseField(self): - test = ' sim ple ' + test = " sim ple " results = copyright.LicenseField.from_value(test) - assert results.name == 'sim ple' + assert results.name == "sim ple" assert not results.text - assert results.dumps() == 'sim ple' + assert results.dumps() == "sim ple" def test_LicenseField_with_text(self): - test = ''' GPL 2.0 + test = """ GPL 2.0 licensed under the gpl . attribution . -''' +""" results = copyright.LicenseField.from_value(test) - assert results.name == 'GPL 2.0' - assert results.text == 'licensed under the gpl\n\n attribution\n' - assert results.dumps() == 'GPL 2.0\n licensed under the gpl\n .\n attribution' + assert results.name == "GPL 2.0" + assert results.text == "licensed under the gpl\n\n attribution\n" + assert results.dumps() == "GPL 2.0\n licensed under the gpl\n .\n attribution" class TestDebianCopyright(JsonTester): - test_data_dir = path.join(path.dirname(__file__), 'data') + test_data_dir = path.join(path.dirname(__file__), "data") def test_DebianCopyright_from_file__from_copyrights_dep5_1(self): - test_file = self.get_test_loc('copyright/dep5-b43-fwcutter.copyright') - expected_loc = 'copyright/dep5-b43-fwcutter.copyright-expected-DebianCopyright.json' + test_file = self.get_test_loc("copyright/dep5-b43-fwcutter.copyright") + expected_loc = "copyright/dep5-b43-fwcutter.copyright-expected-DebianCopyright.json" results = copyright.DebianCopyright.from_file(test_file) self.check_json(results.to_dict(with_lines=True), expected_loc, regen=False) def test_DebianCopyright_from_file__from_copyrights_dep5_3(self): - test_file = self.get_test_loc('copyright/dep5-rpm.copyright') - expected_loc = 'copyright/dep5-rpm.copyright-expected-DebianCopyright.json' + test_file = self.get_test_loc("copyright/dep5-rpm.copyright") + expected_loc = "copyright/dep5-rpm.copyright-expected-DebianCopyright.json" results = copyright.DebianCopyright.from_file(test_file) self.check_json(results.to_dict(with_lines=True), expected_loc, regen=False) def test_DebianCopyright_from_file__from_copyrights_dep5_dropbear(self): - test_file = self.get_test_loc('copyright/dropbear.copyright') - expected_loc = 'copyright/dropbear.copyright-expected-DebianCopyright.json' + test_file = self.get_test_loc("copyright/dropbear.copyright") + expected_loc = "copyright/dropbear.copyright-expected-DebianCopyright.json" results = copyright.DebianCopyright.from_file(test_file) self.check_json(results.to_dict(with_lines=True), expected_loc, regen=False) def test_DebianCopyright_from_file__from_copyrights_with_duplicated_fields(self): - test_file = self.get_test_loc('copyright/dupe-field.copyright') - expected_loc = 'copyright/dupe-field.copyright-expected-DebianCopyright.json' + test_file = self.get_test_loc("copyright/dupe-field.copyright") + expected_loc = "copyright/dupe-field.copyright-expected-DebianCopyright.json" results = copyright.DebianCopyright.from_file(test_file) self.check_json(results.to_dict(with_lines=True), expected_loc, regen=False) def test_DebianCopyright_from_file__from_copyrights_dep5_1_dumps(self): - test_file = self.get_test_loc('copyright/dep5-b43-fwcutter.copyright') - expected_loc = 'copyright/dep5-b43-fwcutter.copyright-expected.dumps' + test_file = self.get_test_loc("copyright/dep5-b43-fwcutter.copyright") + expected_loc = "copyright/dep5-b43-fwcutter.copyright-expected.dumps" results = copyright.DebianCopyright.from_file(test_file).dumps() self.check_file(results, expected_loc, regen=False) def test_DebianCopyright_from_file__from_copyrights_dep5_3_dumps(self): - test_file = self.get_test_loc('copyright/dep5-rpm.copyright') - expected_loc = 'copyright/dep5-rpm.copyright-expected.dumps' + test_file = self.get_test_loc("copyright/dep5-rpm.copyright") + expected_loc = "copyright/dep5-rpm.copyright-expected.dumps" results = copyright.DebianCopyright.from_file(test_file).dumps() self.check_file(results, expected_loc, regen=False) def test_DebianCopyright_from_file__from_copyrights_dep5_dropbear_dumps(self): - test_file = self.get_test_loc('copyright/dropbear.copyright') - expected_loc = 'copyright/dropbear.copyright-expected.dumps' + test_file = self.get_test_loc("copyright/dropbear.copyright") + expected_loc = "copyright/dropbear.copyright-expected.dumps" results = copyright.DebianCopyright.from_file(test_file).dumps() self.check_file(results, expected_loc, regen=False) def test_DebianCopyright_from_text__from_copyrights_dep5_dropbear_dumps(self): - test_file = self.get_test_loc('copyright/dropbear.copyright') + test_file = self.get_test_loc("copyright/dropbear.copyright") import io - with io.open(test_file, encoding='utf-8') as td: + + with io.open(test_file, encoding="utf-8") as td: test_data = td.read() - expected_loc = 'copyright/dropbear.copyright-expected.dumps' + expected_loc = "copyright/dropbear.copyright-expected.dumps" results = copyright.DebianCopyright.from_text(test_data).dumps() self.check_file(results, expected_loc, regen=False) def test_DebianCopyright_from_file_split_paragraphs_correctly_multiple_lines(self): - test_file = self.get_test_loc('copyright/debian-slim-gpgv.copyright') - expected_loc = 'copyright/debian-slim-gpgv.copyright-expected-DebianCopyright.json' + test_file = self.get_test_loc("copyright/debian-slim-gpgv.copyright") + expected_loc = "copyright/debian-slim-gpgv.copyright-expected-DebianCopyright.json" results = copyright.DebianCopyright.from_file(test_file) self.check_json(results.to_dict(with_lines=True), expected_loc, regen=False) def test_DebianCopyright_from_file_licence_to_license(self): - test_file = self.get_test_loc('copyright/test-licence-license.copyright') - expected_loc = 'copyright/test-licence-license.copyright-expected-DebianCopyright.json' + test_file = self.get_test_loc("copyright/test-licence-license.copyright") + expected_loc = "copyright/test-licence-license.copyright-expected-DebianCopyright.json" results = copyright.DebianCopyright.from_file(test_file) self.check_json(results.to_dict(with_lines=True), expected_loc, regen=True) - class TestCopyright(JsonTester): - test_data_dir = path.join(path.dirname(__file__), 'data') + test_data_dir = path.join(path.dirname(__file__), "data") def test_is_machine_readable_copyright(self): - text = '''format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ + text = """format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: b43-fwcutter -Source: http://linuxwireless.org/en/users/Drivers/b43''' +Source: http://linuxwireless.org/en/users/Drivers/b43""" assert copyright.is_machine_readable_copyright(text) def test_is_machine_readable_copyright_ignore_case(self): - text = '''Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ + text = """Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: b43-fwcutter -Source: http://linuxwireless.org/en/users/Drivers/b43''' +Source: http://linuxwireless.org/en/users/Drivers/b43""" assert copyright.is_machine_readable_copyright(text) def test_is_machine_readable_copyright_fasle(self): - text = '''homepage: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ - ''' + text = """homepage: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ + """ assert not copyright.is_machine_readable_copyright(text) - diff --git a/tests/test_deb822.py b/tests/test_deb822.py index 209ac16..d666959 100644 --- a/tests/test_deb822.py +++ b/tests/test_deb822.py @@ -17,13 +17,12 @@ def get_paras_data(test_file): return [ - [h.to_dict() for h in p] - for p in deb822.get_paragraphs_as_field_groups_from_file(test_file) + [h.to_dict() for h in p] for p in deb822.get_paragraphs_as_field_groups_from_file(test_file) ] class TestGetParagraphsData(JsonTester): - test_data_dir = path.join(path.dirname(__file__), 'data') + test_data_dir = path.join(path.dirname(__file__), "data") def test_get_paragraphs_as_field_groups__splits_paragraphs_with_multiple_lines_correctly(self): test = """Upstream-Name: GnuPG @@ -50,84 +49,112 @@ def test_get_paragraphs_as_field_groups__splits_paragraphs_with_multiple_lines_c """ results = list(deb822.get_paragraphs_as_field_groups(test)) expected = [ - [Deb822Field(lines=[NumberedLine(number=1, value='GnuPG')], name='upstream-name')], + [Deb822Field(lines=[NumberedLine(number=1, value="GnuPG")], name="upstream-name")], [ - Deb822Field(lines=[NumberedLine(number=3, value='*')], name='files'), - Deb822Field(lines=[NumberedLine(number=4, value='Free Software Foundation, Inc')], name='copyright'), - Deb822Field(lines=[NumberedLine(number=5, value='GPL-3+')], name='license') + Deb822Field(lines=[NumberedLine(number=3, value="*")], name="files"), + Deb822Field( + lines=[NumberedLine(number=4, value="Free Software Foundation, Inc")], + name="copyright", + ), + Deb822Field(lines=[NumberedLine(number=5, value="GPL-3+")], name="license"), + ], + [ + Deb822Field( + lines=[ + NumberedLine(number=7, value="TinySCHEME"), + NumberedLine(number=8, value=" Redistribution"), + ], + name="license", + ) + ], + [ + Deb822Field( + lines=[ + NumberedLine(number=11, value="permissive"), + NumberedLine(number=12, value=" This file is free software."), + ], + name="license", + ) + ], + [ + Deb822Field( + lines=[ + NumberedLine(number=14, value="RFC-Reference"), + NumberedLine(number=15, value=" doc/OpenPGP"), + ], + name="license", + ) + ], + [ + Deb822Field( + name="license", + lines=[ + NumberedLine(number=18, value="GPL-3+"), + NumberedLine(number=19, value=" GnuPG"), + NumberedLine(number=20, value=" . formatted"), + NumberedLine(number=21, value=" also formatted"), + ], + ) ], - [Deb822Field(lines=[ - NumberedLine(number=7, value='TinySCHEME'), - NumberedLine(number=8, value=' Redistribution') - ], name='license')], - [Deb822Field(lines=[ - NumberedLine(number=11, value='permissive'), - NumberedLine(number=12, value=' This file is free software.') - ], name='license')], - [Deb822Field(lines=[ - NumberedLine(number=14, value='RFC-Reference'), - NumberedLine(number=15, value=' doc/OpenPGP') - ], name='license')], - [Deb822Field(name='license', lines=[ - NumberedLine(number=18, value='GPL-3+'), - NumberedLine(number=19, value=' GnuPG'), - NumberedLine(number=20, value=' . formatted'), - NumberedLine(number=21, value=' also formatted') - ])], ] assert results == expected def test_get_paragraphs_as_field_groups__splits_paragraphs_correctly(self): - test = 'para1: test1\n\npara2: test2' + test = "para1: test1\n\npara2: test2" results = list(deb822.get_paragraphs_as_field_groups(test)) expected = [ - [Deb822Field(lines=[NumberedLine(number=1, value='test1')], name='para1')], - [Deb822Field(lines=[NumberedLine(number=3, value='test2')], name='para2')], + [Deb822Field(lines=[NumberedLine(number=1, value="test1")], name="para1")], + [Deb822Field(lines=[NumberedLine(number=3, value="test2")], name="para2")], ] assert results == expected def test_get_paragraphs_as_field_groups__handles_more_than_two_empty_lines(self): - test = 'para1: test1\n\n\n\n\npara2: test2\n test3' + test = "para1: test1\n\n\n\n\npara2: test2\n test3" results = list(deb822.get_paragraphs_as_field_groups(test)) expected = [ - [Deb822Field(name='para1', lines=[NumberedLine(number=1, value='test1')])], - [Deb822Field(name='para2', lines=[ - NumberedLine(number=6, value='test2'), - NumberedLine(number=7, value=' test3') - ])], + [Deb822Field(name="para1", lines=[NumberedLine(number=1, value="test1")])], + [ + Deb822Field( + name="para2", + lines=[ + NumberedLine(number=6, value="test2"), + NumberedLine(number=7, value=" test3"), + ], + ) + ], ] assert results == expected def test_get_paragraphs_as_field_groups__handles_empty_lines_with_spaces(self): - test = '\n\npara1: test1\n\n \t \n \npara2: test2' + test = "\n\npara1: test1\n\n \t \n \npara2: test2" results = list(deb822.get_paragraphs_as_field_groups(test)) expected = [ - [Deb822Field(name='para1', lines=[NumberedLine(number=3, value='test1')])], - [Deb822Field(name='para2', lines=[NumberedLine(number=7, value='test2')])], + [Deb822Field(name="para1", lines=[NumberedLine(number=3, value="test1")])], + [Deb822Field(name="para2", lines=[NumberedLine(number=7, value="test2")])], ] assert results == expected def test_get_paragraphs_as_field_groups_from_file__from_copyrights_dep5_1(self): - test_file = self.get_test_loc('deb822/dep5-b43-fwcutter.copyright') - expected_loc = 'deb822/dep5-b43-fwcutter.copyright-expected.json' + test_file = self.get_test_loc("deb822/dep5-b43-fwcutter.copyright") + expected_loc = "deb822/dep5-b43-fwcutter.copyright-expected.json" results = get_paras_data(test_file) self.check_json(results, expected_loc, regen=False) def test_get_paragraphs_as_field_groups_from_file__from_copyrights_dep5_3(self): - test_file = self.get_test_loc('deb822/dep5-rpm.copyright') - expected_loc = 'deb822/dep5-rpm.copyright-expected.json' + test_file = self.get_test_loc("deb822/dep5-rpm.copyright") + expected_loc = "deb822/dep5-rpm.copyright-expected.json" results = get_paras_data(test_file) self.check_json(results, expected_loc, regen=False) def test_get_paragraphs_as_field_groups_from_file__from_copyrights_dep5_dropbear(self): - test_file = self.get_test_loc('deb822/dropbear.copyright') - expected_loc = 'deb822/dropbear.copyright-expected.json' + test_file = self.get_test_loc("deb822/dropbear.copyright") + expected_loc = "deb822/dropbear.copyright-expected.json" results = get_paras_data(test_file) self.check_json(results, expected_loc, regen=False) def test_get_paragraphs_as_field_groups_from_file__from_copyrights_with_spaces(self): - test_file = self.get_test_loc('deb822/empty_lines.copyright') - expected_loc = 'deb822/empty_lines.copyright-expected.json' + test_file = self.get_test_loc("deb822/empty_lines.copyright") + expected_loc = "deb822/empty_lines.copyright-expected.json" results = get_paras_data(test_file) self.check_json(results, expected_loc, regen=False) diff --git a/tests/test_debcon.py b/tests/test_debcon.py index 975741e..4f1dbd1 100644 --- a/tests/test_debcon.py +++ b/tests/test_debcon.py @@ -14,97 +14,96 @@ class TestGetParagraphData(JsonTester): - test_data_dir = path.join(path.dirname(__file__), 'data') + test_data_dir = path.join(path.dirname(__file__), "data") def test_get_paragraph_data_from_file_does_not_crash_on_None(self): results = list(debcon.get_paragraph_data_from_file(None)) assert results == [] def test_get_paragraph_data_from_file_from_single_status(self): - test_file = self.get_test_loc('debcon/status/one_status') - expected_loc = 'debcon/status/one_status-expected.json' + test_file = self.get_test_loc("debcon/status/one_status") + expected_loc = "debcon/status/one_status-expected.json" results = debcon.get_paragraph_data_from_file(test_file) self.check_json(results, expected_loc, regen=False) def test_get_paragraph_data_from_file_from_status_with_junk_uses_unknown(self): - test_file = self.get_test_loc('debcon/status/one_status_junk', must_exist=False) - expected_loc = 'debcon/status/one_status_junk-expected.json' + test_file = self.get_test_loc("debcon/status/one_status_junk", must_exist=False) + expected_loc = "debcon/status/one_status_junk-expected.json" results = debcon.get_paragraph_data_from_file(test_file) self.check_json(results, expected_loc, regen=False) def test_get_paragraph_data_from_file__signed_from_dsc_can_remove_signature(self): - test_file = self.get_test_loc('debcon/dsc/zlib_1.2.11.dfsg-1.dsc') - expected_loc = 'debcon/dsc/zlib_1.2.11.dfsg-1.dsc-expected.json' + test_file = self.get_test_loc("debcon/dsc/zlib_1.2.11.dfsg-1.dsc") + expected_loc = "debcon/dsc/zlib_1.2.11.dfsg-1.dsc-expected.json" results = debcon.get_paragraph_data_from_file(test_file, remove_pgp_signature=True) self.check_json(results, expected_loc, regen=False) - def test_get_paragraph_data_from_file__signed_from_dsc_does_not_crash_if_signature_not_removed(self): - test_file = self.get_test_loc('debcon/dsc/zlib_1.2.11.dfsg-1.dsc') - expected_loc = 'debcon/dsc/zlib_1.2.11.dfsg-1.dsc-expected-no-desig.json' + def test_get_paragraph_data_from_file__signed_from_dsc_does_not_crash_if_signature_not_removed( + self, + ): + test_file = self.get_test_loc("debcon/dsc/zlib_1.2.11.dfsg-1.dsc") + expected_loc = "debcon/dsc/zlib_1.2.11.dfsg-1.dsc-expected-no-desig.json" results = debcon.get_paragraph_data_from_file(test_file, remove_pgp_signature=False) self.check_json(results, expected_loc, regen=False) def test_get_paragraph_data_from_file_from_status_can_handle_perl_status(self): - test_file = self.get_test_loc('debcon/status/perl_status') - expected_loc = 'debcon/status/perl_status-expected.json' + test_file = self.get_test_loc("debcon/status/perl_status") + expected_loc = "debcon/status/perl_status-expected.json" results = debcon.get_paragraph_data_from_file(test_file) self.check_json(results, expected_loc, regen=False) def test_get_paragraph_data__invalid_format_returns_none(self): - text = '''Some text that is not RFC 822 - Compliant''' + text = """Some text that is not RFC 822 + Compliant""" results = debcon.get_paragraph_data(text) - assert results == {'unknown': text} + assert results == {"unknown": text} def test_get_paragraph_data_does_not_preserve_keys_case_by_default(self): - key = 'Some-text' - value = '''RFC 822 - Compliant''' - test = '{}: {}'.format(key, value) + key = "Some-text" + value = """RFC 822 + Compliant""" + test = "{}: {}".format(key, value) results = debcon.get_paragraph_data(test) assert results == {key.lower(): value} def test_get_paragraph_data__merge_duplicate_keys(self): - o1 = 'some: val' - kv1 = 'key: value1' - o2 = 'other: val' - kv2 = 'Key: value2' - test = '{}\n{}\n{}\n{}'.format(o1, kv1, o2, kv2) + o1 = "some: val" + kv1 = "key: value1" + o2 = "other: val" + kv2 = "Key: value2" + test = "{}\n{}\n{}\n{}".format(o1, kv1, o2, kv2) results = debcon.get_paragraph_data(test) - expected = [ - ('some', 'val'), - ('key', 'value1\nvalue2'), - ('other', 'val')] + expected = [("some", "val"), ("key", "value1\nvalue2"), ("other", "val")] assert list(results.items()) == expected def test_test_get_paragraph_data__simple(self): - items = 'A: b\nc: d' + items = "A: b\nc: d" results = debcon.get_paragraph_data(items) - expected = {'a': 'b', 'c': 'd'} + expected = {"a": "b", "c": "d"} assert results == expected def test_test_get_paragraph_data_lowers_only_keys(self): - items = 'A: B\nDF: D' + items = "A: B\nDF: D" results = debcon.get_paragraph_data(items) - expected = {'a': 'B', 'df': 'D'} + expected = {"a": "B", "df": "D"} assert results == expected def test_test_get_paragraph_data_merge_dupes(self): - items = 'A: B\nDF: D\ndf: x' + items = "A: B\nDF: D\ndf: x" results = debcon.get_paragraph_data(items) - expected = {'a': 'B', 'df': 'D\nx'} + expected = {"a": "B", "df": "D\nx"} assert results == expected def test_get_paragraphs_data__splits_paragraphs_correctly(self): - test = 'para1: test1\n\npara2: test2' + test = "para1: test1\n\npara2: test2" results = list(debcon.get_paragraphs_data(test)) - expected = [{'para1': 'test1'}, {'para2': 'test2'}] + expected = [{"para1": "test1"}, {"para2": "test2"}] assert results == expected def test_split_in_paragraphs__splits_paragraphs_correctly(self): - test = 'para1: test1\n\npara2: test2' + test = "para1: test1\n\npara2: test2" results = list(debcon.split_in_paragraphs(test)) - expected = ['para1: test1', 'para2: test2'] + expected = ["para1: test1", "para2: test2"] assert results == expected def test_split_in_paragraphs__splits_paragraphs_with_multiple_lines_correctly(self): @@ -131,226 +130,226 @@ def test_split_in_paragraphs__splits_paragraphs_with_multiple_lines_correctly(se results = list(debcon.split_in_paragraphs(test_text_split)) expected = [ - 'Upstream-Name: GnuPG', - 'Files: *\nCopyright: Free Software Foundation, Inc\nLicense: GPL-3+', - 'License: TinySCHEME\n Redistribution', - 'License: permissive\n This file is free software.', - 'License: RFC-Reference\n doc/OpenPGP', - 'License: GPL-3+\n GnuPG\n', + "Upstream-Name: GnuPG", + "Files: *\nCopyright: Free Software Foundation, Inc\nLicense: GPL-3+", + "License: TinySCHEME\n Redistribution", + "License: permissive\n This file is free software.", + "License: RFC-Reference\n doc/OpenPGP", + "License: GPL-3+\n GnuPG\n", ] assert results == expected def test_split_in_paragraphs__handles_more_than_two_empty_lines(self): - test = 'para1: test1\n\n\n\n\npara2: test2' + test = "para1: test1\n\n\n\n\npara2: test2" results = list(debcon.split_in_paragraphs(test)) - expected = ['para1: test1', 'para2: test2'] + expected = ["para1: test1", "para2: test2"] assert results == expected def test_split_in_paragraphs__handles_empty_lines_with_spaces(self): - test = 'para1: test1\n\n \t \n \npara2: test2' + test = "para1: test1\n\n \t \n \npara2: test2" results = list(debcon.split_in_paragraphs(test)) - expected = ['para1: test1', 'para2: test2'] + expected = ["para1: test1", "para2: test2"] assert results == expected def test_get_paragraphs_data_from_text__from_status_file(self): - test_file = self.get_test_loc('debcon/status/simple_status') + test_file = self.get_test_loc("debcon/status/simple_status") with open(test_file) as tf: test_file = tf.read() - expected_loc = 'debcon/status/simple_status-expected.json' + expected_loc = "debcon/status/simple_status-expected.json" results = list(debcon.get_paragraphs_data(test_file)) self.check_json(results, expected_loc, regen=False) def test_get_paragraphs_data_from_file__from_copyrights_dep5_1(self): - test_file = self.get_test_loc('debcon/copyright/dep5-b43-fwcutter.copyright') - expected_loc = 'debcon/copyright/dep5-b43-fwcutter.copyright-expected.json' + test_file = self.get_test_loc("debcon/copyright/dep5-b43-fwcutter.copyright") + expected_loc = "debcon/copyright/dep5-b43-fwcutter.copyright-expected.json" results = list(debcon.get_paragraphs_data_from_file(test_file)) self.check_json(results, expected_loc, regen=False) def test_get_paragraphs_data_from_file__from_copyrights_dep5_3(self): - test_file = self.get_test_loc('debcon/copyright/dep5-rpm.copyright') - expected_loc = 'debcon/copyright/dep5-rpm.copyright-expected.json' + test_file = self.get_test_loc("debcon/copyright/dep5-rpm.copyright") + expected_loc = "debcon/copyright/dep5-rpm.copyright-expected.json" results = list(debcon.get_paragraphs_data_from_file(test_file)) self.check_json(results, expected_loc, regen=False) def test_get_paragraphs_data_from_file__from_copyrights_dep5_dropbear(self): - test_file = self.get_test_loc('debcon/copyright/dropbear.copyright') - expected_loc = 'debcon/copyright/dropbear.copyright-expected.json' + test_file = self.get_test_loc("debcon/copyright/dropbear.copyright") + expected_loc = "debcon/copyright/dropbear.copyright-expected.json" results = list(debcon.get_paragraphs_data_from_file(test_file)) self.check_json(results, expected_loc, regen=False) def test_get_paragraphs_data_from_file__from_packages(self): - test_file = self.get_test_loc('debcon/packages/simple_packages') - expected_loc = 'debcon/packages/simple_packages-expected.json' + test_file = self.get_test_loc("debcon/packages/simple_packages") + expected_loc = "debcon/packages/simple_packages-expected.json" results = list(debcon.get_paragraphs_data_from_file(test_file)) self.check_json(results, expected_loc, regen=False) def test_get_paragraphs_data_from_file__from_sources(self): - test_file = self.get_test_loc('debcon/sources/simple_sources') - expected_loc = 'debcon/sources/simple_sources-expected.json' + test_file = self.get_test_loc("debcon/sources/simple_sources") + expected_loc = "debcon/sources/simple_sources-expected.json" results = list(debcon.get_paragraphs_data_from_file(test_file)) self.check_json(results, expected_loc, regen=False) def test_get_paragraphs_data_from_file__from_status(self): - test_file = self.get_test_loc('debcon/status/simple_status') - expected_loc = 'debcon/status/simple_status-expected.json' + test_file = self.get_test_loc("debcon/status/simple_status") + expected_loc = "debcon/status/simple_status-expected.json" results = list(debcon.get_paragraphs_data_from_file(test_file)) self.check_json(results, expected_loc, regen=False) def test_get_paragraph_data__return_unknow_if_we_have_payload(self): # we were skipping email payloads: a payload means this is not a # header only file and therefore something we cannot process normally - test = 'Foo: home\n\nBar: baz' + test = "Foo: home\n\nBar: baz" results = debcon.get_paragraph_data(test) - expected = {'foo': 'home', 'unknown': 'Bar: baz'} + expected = {"foo": "home", "unknown": "Bar: baz"} assert results == expected class TestDebian822(JsonTester): - test_data_dir = path.join(path.dirname(__file__), 'data') + test_data_dir = path.join(path.dirname(__file__), "data") def test_Debian822_from_file__from_one_status(self): - test_file = self.get_test_loc('debcon/deb822/one_status') - expected_loc = 'debcon/deb822/one_status-expected-deb822.json' + test_file = self.get_test_loc("debcon/deb822/one_status") + expected_loc = "debcon/deb822/one_status-expected-deb822.json" results = debcon.Debian822.from_file(test_file).to_dict() self.check_json(results, expected_loc, regen=False) def test_Debian822_from_file__from_one_status_keeps_unknown_junk(self): - test_file = self.get_test_loc('debcon/deb822/one_status_junk') - expected_loc = 'debcon/deb822/one_status_junk-expected-deb822.json' + test_file = self.get_test_loc("debcon/deb822/one_status_junk") + expected_loc = "debcon/deb822/one_status_junk-expected-deb822.json" results = debcon.Debian822.from_file(test_file).to_dict() self.check_json(results, expected_loc, regen=False) def test_Debian822_from_string__from_one_status(self): - test_file = self.get_test_loc('debcon/deb822/one_status') + test_file = self.get_test_loc("debcon/deb822/one_status") with open(test_file) as tf: test_file = tf.read() - expected_loc = 'debcon/deb822/one_status-expected-deb822.json' + expected_loc = "debcon/deb822/one_status-expected-deb822.json" results = debcon.Debian822.from_string(test_file).to_dict() self.check_json(results, expected_loc, regen=False) def test_Debian822_from_file__signed_from_dsc(self): - test_file = self.get_test_loc('debcon/deb822/zlib_1.2.11.dfsg-1.dsc') - expected_loc = 'debcon/deb822/zlib_1.2.11.dfsg-1.dsc-expected-deb822.json' + test_file = self.get_test_loc("debcon/deb822/zlib_1.2.11.dfsg-1.dsc") + expected_loc = "debcon/deb822/zlib_1.2.11.dfsg-1.dsc-expected-deb822.json" results = debcon.Debian822.from_file(test_file).to_dict() self.check_json(results, expected_loc, regen=False) def test_Debian822_from_items_list(self): items = [ - ('Package', 'debian_inspector'), - ('Depends', 'python, python-pip, python-pip-accel'), - ('Installed-Size', '65'), + ("Package", "debian_inspector"), + ("Depends", "python, python-pip, python-pip-accel"), + ("Installed-Size", "65"), ] d822 = debcon.Debian822(items) expected = { - 'depends': 'python, python-pip, python-pip-accel', - 'installed-size': '65', - 'package': 'debian_inspector', + "depends": "python, python-pip, python-pip-accel", + "installed-size": "65", + "package": "debian_inspector", } assert d822.to_dict() == expected expected2 = { - 'depends': 'python, python-pip, python-pip-accel', - 'installed-size': '65', - 'package': 'debian_inspector', + "depends": "python, python-pip, python-pip-accel", + "installed-size": "65", + "package": "debian_inspector", } assert dict(d822) == expected2 class TestDebianFields(JsonTester): - test_data_dir = path.join(path.dirname(__file__), 'data') + test_data_dir = path.join(path.dirname(__file__), "data") def test_FormattedTextField(self): - test = 'simple' + test = "simple" results = debcon.FormattedTextField.from_value(test) - assert results.text == 'simple' - assert results.dumps() == 'simple' + assert results.text == "simple" + assert results.dumps() == "simple" - test = ' simple' + test = " simple" results = debcon.FormattedTextField.from_value(test) - assert results.text == 'simple' - assert results.dumps() == 'simple' + assert results.text == "simple" + assert results.dumps() == "simple" - test = ' simple' + test = " simple" results = debcon.FormattedTextField.from_value(test) - assert results.text == 'simple' - assert results.dumps() == 'simple' + assert results.text == "simple" + assert results.dumps() == "simple" def test_FormattedTextField_multilines(self): - test = ''' complex + test = """ complex some . nostrip . -''' +""" results = debcon.FormattedTextField.from_value(test) - expected = 'complex\nsome\n\n nostrip\n' + expected = "complex\nsome\n\n nostrip\n" assert results.text == expected - expected = 'complex\n some\n .\n nostrip' + expected = "complex\n some\n .\n nostrip" assert results.dumps() == expected def test_DescriptionField(self): - test = 'simple' + test = "simple" results = debcon.DescriptionField.from_value(test) - assert results.synopsis == 'simple' + assert results.synopsis == "simple" assert not results.text - assert results.dumps() == 'simple' + assert results.dumps() == "simple" - test = ' simple' - assert results.synopsis == 'simple' + test = " simple" + assert results.synopsis == "simple" assert not results.text - assert results.dumps() == 'simple' + assert results.dumps() == "simple" - test = ' simple' - assert results.synopsis == 'simple' + test = " simple" + assert results.synopsis == "simple" assert not results.text - assert results.dumps() == 'simple' + assert results.dumps() == "simple" def test_DescriptionField_multilines(self): - test = ''' complex + test = """ complex some . nostrip . -''' +""" results = debcon.DescriptionField.from_value(test) - assert results.synopsis == 'complex' - assert results.text == 'some\n\n nostrip\n' - assert results.dumps() == 'complex\n some\n .\n nostrip' + assert results.synopsis == "complex" + assert results.text == "some\n\n nostrip\n" + assert results.dumps() == "complex\n some\n .\n nostrip" def test_MaintainerField(self): - test = ' Joe Z. Doe ' + test = " Joe Z. Doe " results = debcon.MaintainerField.from_value(test) - assert results.name == 'Joe Z. Doe' - assert results.email_address == 'me@jzd.me' - assert results.dumps() == 'Joe Z. Doe ' + assert results.name == "Joe Z. Doe" + assert results.email_address == "me@jzd.me" + assert results.dumps() == "Joe Z. Doe " def test_MaintainerField_incorrect_email(self): - test = ' Joe Z. Doe me@j zd.me> ' + test = " Joe Z. Doe me@j zd.me> " results = debcon.MaintainerField.from_value(test) - assert results.name == 'Joe Z. Doe me@j zd.me>' + assert results.name == "Joe Z. Doe me@j zd.me>" assert not results.email_address - assert results.dumps() == 'Joe Z. Doe me@j zd.me>' + assert results.dumps() == "Joe Z. Doe me@j zd.me>" def test_SingleLineField(self): - test = ' some value ' + test = " some value " results = debcon.SingleLineField.from_value(test) - assert results.value == 'some value' - assert results.dumps() == 'some value' + assert results.value == "some value" + assert results.dumps() == "some value" assert results.dumps() == str(results) def test_LineSeparatedField(self): - test = ' some value \n some value \n some more ' + test = " some value \n some value \n some more " results = debcon.LineSeparatedField.from_value(test) - assert results.values == ['some value', 'some value', 'some more'] - assert results.dumps() == 'some value\n some value\n some more' + assert results.values == ["some value", "some value", "some more"] + assert results.dumps() == "some value\n some value\n some more" def test_AnyWhiteSpaceSeparatedField(self): - test = ' some value \n some value \n some more ' + test = " some value \n some value \n some more " results = debcon.AnyWhiteSpaceSeparatedField.from_value(test) - assert results.values == ['some', 'value', 'some', 'value', 'some', 'more'] - assert results.dumps() == 'some\n value\n some\n value\n some\n more' + assert results.values == ["some", "value", "some", "value", "some", "more"] + assert results.dumps() == "some\n value\n some\n value\n some\n more" def test_LineAndSpaceSeparatedField(self): - test = ' some value \n some value \n some more' + test = " some value \n some value \n some more" results = debcon.LineAndSpaceSeparatedField.from_value(test) - assert results.values == [('some', 'value'), ('some', 'value'), ('some', 'more')] - assert results.dumps() == 'some value\n some value\n some more' + assert results.values == [("some", "value"), ("some", "value"), ("some", "more")] + assert results.dumps() == "some value\n some value\n some more" diff --git a/tests/test_deps.py b/tests/test_deps.py index 5669b31..3d5fd35 100644 --- a/tests/test_deps.py +++ b/tests/test_deps.py @@ -16,159 +16,175 @@ class DepsTestCase(unittest.TestCase): - def test_relationship_parsing(self): - relationship_set = deps.parse_depends('foo, bar (>= 1) | baz') - expected = deps.AndRelationships(relationships=( - deps.Relationship(name='foo'), - deps.OrRelationships(relationships=( - deps.VersionedRelationship(name='bar', operator='>=', version='1'), - deps.Relationship(name='baz'))))) + relationship_set = deps.parse_depends("foo, bar (>= 1) | baz") + expected = deps.AndRelationships( + relationships=( + deps.Relationship(name="foo"), + deps.OrRelationships( + relationships=( + deps.VersionedRelationship(name="bar", operator=">=", version="1"), + deps.Relationship(name="baz"), + ) + ), + ) + ) assert relationship_set == expected def test_relationship_parsing_single_relationship(self): - expected = deps.AndRelationships(relationships=( - deps.VersionedRelationship(name='foo', operator='=', version='1.0'),)) - assert deps.parse_depends('foo (=1.0)') == expected + expected = deps.AndRelationships( + relationships=(deps.VersionedRelationship(name="foo", operator="=", version="1.0"),) + ) + assert deps.parse_depends("foo (=1.0)") == expected def test_relationship_parsing_raise_valueerror_for_invalid_relationship(self): - self.assertRaises(ValueError, deps.parse_depends, 'foo (bar) (baz)') - self.assertRaises(ValueError, deps.parse_depends, 'foo (bar baz qux)') + self.assertRaises(ValueError, deps.parse_depends, "foo (bar) (baz)") + self.assertRaises(ValueError, deps.parse_depends, "foo (bar baz qux)") def test_parse_depends(self): - depends = deps.parse_depends('python (>= 2.6), python (<< 3)') - expected = deps.AndRelationships(relationships=( - deps.VersionedRelationship(name='python', operator='>=', version='2.6'), - deps.VersionedRelationship(name='python', operator='<<', version='3')) + depends = deps.parse_depends("python (>= 2.6), python (<< 3)") + expected = deps.AndRelationships( + relationships=( + deps.VersionedRelationship(name="python", operator=">=", version="2.6"), + deps.VersionedRelationship(name="python", operator="<<", version="3"), + ) ) assert depends == expected - assert not depends.matches('python', '2.5') - assert depends.matches('python', '2.6') - assert depends.matches('python', '2.7') + assert not depends.matches("python", "2.5") + assert depends.matches("python", "2.6") + assert depends.matches("python", "2.7") def test_parse_alternatives_with_no_alternative(self): - depends = deps.parse_alternatives('python2.6') - expected = deps.Relationship(name='python2.6') + depends = deps.parse_alternatives("python2.6") + expected = deps.Relationship(name="python2.6") assert depends == expected def test_parse_alternatives(self): - depends = deps.parse_alternatives('python2.6 | python2.7') - expected = deps.OrRelationships(relationships=( - deps.Relationship(name='python2.6'), - deps.Relationship(name='python2.7')) + depends = deps.parse_alternatives("python2.6 | python2.7") + expected = deps.OrRelationships( + relationships=(deps.Relationship(name="python2.6"), deps.Relationship(name="python2.7")) ) assert depends == expected def test_architecture_restriction_parsing(self): - relationship_set = deps.parse_depends('qux [i386 amd64]') - assert 'qux' == relationship_set.relationships[0].name + relationship_set = deps.parse_depends("qux [i386 amd64]") + assert "qux" == relationship_set.relationships[0].name assert 2 == len(relationship_set.relationships[0].architectures) - assert 'i386' in relationship_set.relationships[0].architectures - assert 'amd64' in relationship_set.relationships[0].architectures + assert "i386" in relationship_set.relationships[0].architectures + assert "amd64" in relationship_set.relationships[0].architectures def test_relationships_objects_as_strings(self): - def strip(text): - return re.sub(r'\s+', '', text) + return re.sub(r"\s+", "", text) - relationship_set = deps.parse_depends('foo, bar(>=1)|baz[i386]') - expected = 'foo, bar (>= 1) | baz [i386]' + relationship_set = deps.parse_depends("foo, bar(>=1)|baz[i386]") + expected = "foo, bar (>= 1) | baz [i386]" assert str(relationship_set) == expected - expected = deps.AndRelationships(relationships=( - deps.Relationship(name='foo'), - deps.OrRelationships(relationships=( - deps.VersionedRelationship(name='bar', operator='>=', version='1'), - deps.Relationship(name='baz', architectures=('i386',)))))) + expected = deps.AndRelationships( + relationships=( + deps.Relationship(name="foo"), + deps.OrRelationships( + relationships=( + deps.VersionedRelationship(name="bar", operator=">=", version="1"), + deps.Relationship(name="baz", architectures=("i386",)), + ) + ), + ) + ) assert relationship_set == expected def test_relationship_evaluation_works_without_version(self): - relationship_set = deps.parse_depends('python') - assert relationship_set.matches('python') - assert not relationship_set.matches('python2.7') - assert ['python'] == list(relationship_set.names) + relationship_set = deps.parse_depends("python") + assert relationship_set.matches("python") + assert not relationship_set.matches("python2.7") + assert ["python"] == list(relationship_set.names) def test_relationship_evaluation_alternative_OR_works_without_version(self): - relationship_set = deps.parse_depends('python2.6 | python2.7') - assert not relationship_set.matches('python2.5') - assert relationship_set.matches('python2.6') - assert relationship_set.matches('python2.7') - assert not relationship_set.matches('python3.0') - assert ['python2.6', 'python2.7'] == sorted(relationship_set.names) + relationship_set = deps.parse_depends("python2.6 | python2.7") + assert not relationship_set.matches("python2.5") + assert relationship_set.matches("python2.6") + assert relationship_set.matches("python2.7") + assert not relationship_set.matches("python3.0") + assert ["python2.6", "python2.7"] == sorted(relationship_set.names) def test_relationship_evaluation_works_without_version_against_versioned(self): # Testing for matches without providing a version is valid (should not # raise an error) but will never match a relationship with a version. - relationship_set = deps.parse_depends('python (>= 2.6), python (<< 3)') - assert relationship_set.matches('python', '2.7') - assert not relationship_set.matches('python') - assert ['python'] == list(relationship_set.names) + relationship_set = deps.parse_depends("python (>= 2.6), python (<< 3)") + assert relationship_set.matches("python", "2.7") + assert not relationship_set.matches("python") + assert ["python"] == list(relationship_set.names) def test_relationship_evaluation_combination_AND_works_with_version(self): # Distinguishing between packages whose name was matched but whose # version didn't match vs packages whose name wasn't matched. - relationship_set = deps.parse_depends('python (>= 2.6), python (<< 3) | python (>= 3.4)') + relationship_set = deps.parse_depends("python (>= 2.6), python (<< 3) | python (>= 3.4)") # name matched, version didn't - assert relationship_set.matches('python', '2.5') is False + assert relationship_set.matches("python", "2.5") is False # name didn't match - assert relationship_set.matches('python2.6') is None + assert relationship_set.matches("python2.6") is None # name in alternative matched, version didn't - assert relationship_set.matches('python', '3.0') is False + assert relationship_set.matches("python", "3.0") is False # name and version match - assert relationship_set.matches('python', '2.7') is True - assert relationship_set.matches('python', '2.6') - assert relationship_set.matches('python', '3.4') - assert ['python'] == list(relationship_set.names) + assert relationship_set.matches("python", "2.7") is True + assert relationship_set.matches("python", "2.6") + assert relationship_set.matches("python", "3.4") + assert ["python"] == list(relationship_set.names) def test_parse_depends_misc(self): - dependencies = deps.parse_depends('python (>= 2.6), python (<< 3) | python (>= 3.4)') - expected = deps.AndRelationships(relationships=( - deps.VersionedRelationship(name='python', operator='>=', version='2.6'), - deps.OrRelationships(relationships=( - deps.VersionedRelationship(name='python', operator='<<', version='3'), - deps.VersionedRelationship(name='python', operator='>=', version='3.4') - ,)) - ,)) + dependencies = deps.parse_depends("python (>= 2.6), python (<< 3) | python (>= 3.4)") + expected = deps.AndRelationships( + relationships=( + deps.VersionedRelationship(name="python", operator=">=", version="2.6"), + deps.OrRelationships( + relationships=( + deps.VersionedRelationship(name="python", operator="<<", version="3"), + deps.VersionedRelationship(name="python", operator=">=", version="3.4"), + ) + ), + ) + ) assert dependencies == expected - expected = 'python (>= 2.6), python (<< 3) | python (>= 3.4)' + expected = "python (>= 2.6), python (<< 3) | python (>= 3.4)" assert str(dependencies) == expected def test_parse_relationship(self): - rel = deps.parse_relationship('python') - assert rel == deps.Relationship(name='python') - rel = deps.parse_relationship('python (<< 3)') - assert rel == deps.VersionedRelationship(name='python', operator='<<', version='3') + rel = deps.parse_relationship("python") + assert rel == deps.Relationship(name="python") + rel = deps.parse_relationship("python (<< 3)") + assert rel == deps.VersionedRelationship(name="python", operator="<<", version="3") def test_parse_depends_complex_cases_is_idempotent(self): depends = [ - 'clamav-freshclam (>= 0.103.2+dfsg) | clamav-data, libc6 (>= 2.15), libclamav9 (>= 0.103.2), ' - 'libcurl3 (>= 7.16.2), libjson-c2 (>= 0.10), libssl1.0.0 (>= 1.0.0), zlib1g (>= 1:1.2.3.3)', - 'sysvinit-utils (>= 2.86.ds1-62), insserv (>> 1.12.0-10)', - 'libgimp2.0 (>= 2.8.16), libgimp2.0 (<= 2.8.16-z)', - 'zlib1g (>= 1:1.1.4), python:any (>= 2.6.6-7~)', - 'git (>> 1:2.34.1), git (<< 1:2.34.1-.)', - 'libnspr4 (>= 2:4.13.1), libnspr4 (<= 2:4.13.1-0ubuntu0.16.04.1.1~)', - 'libc6 (>> 2.23), libc6 (<< 2.24)', - 'libptexenc1 (>= 2015.20160222.37495-1ubuntu0.1), libptexenc1 (<< 2015.20160222.37495-1ubuntu0.1.1~), ' - 'libkpathsea6 (>= 2015.20160222.37495-1ubuntu0.1), libkpathsea6 (<< 2015.20160222.37495-1ubuntu0.1.1~), ' - 'libsynctex1 (>= 2015.20160222.37495-1ubuntu0.1), libsynctex1 (<< 2015.20160222.37495-1ubuntu0.1.1~), ' - 'libtexlua52 (>= 2015.20160222.37495-1ubuntu0.1), libtexlua52 (<< 2015.20160222.37495-1ubuntu0.1.1~), ' - 'libtexluajit2 (>= 2015.20160222.37495-1ubuntu0.1), libtexluajit2 (<< 2015.20160222.37495-1ubuntu0.1.1~)', - 'libldb1 (<< 2:1.1.25~), libldb1 (>> 2:1.1.24~)', - 'libnettle6 (= 3.2-1ubuntu0.16.04.2), libhogweed4 (= 3.2-1ubuntu0.16.04.2), libgmp10-dev, dpkg (>= 1.15.4) | install-info', - 'libkf5widgetsaddons-data (= 5.18.0-0ubuntu1), libc6 (>= 2.14), libqt5core5a (>= 5.5.0), ' - 'libqt5gui5 (>= 5.3.0) | libqt5gui5-gles (>= 5.3.0), libqt5widgets5 (>= 5.2.0), libstdc++6 (>= 4.1.1)', - 'libnuma1 (= 2.0.11-1ubuntu1.1), libc6-dev | libc-dev', - 'perl, perl (>= 5.11.2) | libextutils-cbuilder-perl, perl (>= 5.12) | libextutils-parsexs-perl, ' - 'perl (>= 5.13.9) | libmodule-metadata-perl, perl (>= 5.13.9) | libversion-perl (>= 1:0.8700), ' - 'perl (>= 5.19.5) | libtest-harness-perl (>= 3.29), perl (>= 5.21.3) | libcpan-meta-perl (>= 2.142060)', - 'libc6 (>= 2.14), libglib2.0-0 (>= 2.37.3), libgtk-3-0 (>= 3.11.5), libgtksourceview-3.0-1 (>= 3.17.3), ' - 'libpeas-1.0-0 (>= 1.0.0), libzeitgeist-2.0-0 (>= 0.9.9), dconf-gsettings-backend | gsettings-backend, ' - 'python3 (<< 3.6), python3 (>= 3.5~), python3.5, gedit (>= 3.18), gedit (<< 3.19), gir1.2-git2-glib-1.0, ' - 'gir1.2-glib-2.0, gir1.2-gtk-3.0, gir1.2-gtksource-3.0, gir1.2-gucharmap-2.90, gir1.2-pango-1.0, gir1.2-peas-1.0, ' - 'gir1.2-vte-2.91, gir1.2-zeitgeist-2.0, python3-gi, python3-gi-cairo, python3-cairo, python3-dbus', + "clamav-freshclam (>= 0.103.2+dfsg) | clamav-data, libc6 (>= 2.15), libclamav9 (>= 0.103.2), " + "libcurl3 (>= 7.16.2), libjson-c2 (>= 0.10), libssl1.0.0 (>= 1.0.0), zlib1g (>= 1:1.2.3.3)", + "sysvinit-utils (>= 2.86.ds1-62), insserv (>> 1.12.0-10)", + "libgimp2.0 (>= 2.8.16), libgimp2.0 (<= 2.8.16-z)", + "zlib1g (>= 1:1.1.4), python:any (>= 2.6.6-7~)", + "git (>> 1:2.34.1), git (<< 1:2.34.1-.)", + "libnspr4 (>= 2:4.13.1), libnspr4 (<= 2:4.13.1-0ubuntu0.16.04.1.1~)", + "libc6 (>> 2.23), libc6 (<< 2.24)", + "libptexenc1 (>= 2015.20160222.37495-1ubuntu0.1), libptexenc1 (<< 2015.20160222.37495-1ubuntu0.1.1~), " + "libkpathsea6 (>= 2015.20160222.37495-1ubuntu0.1), libkpathsea6 (<< 2015.20160222.37495-1ubuntu0.1.1~), " + "libsynctex1 (>= 2015.20160222.37495-1ubuntu0.1), libsynctex1 (<< 2015.20160222.37495-1ubuntu0.1.1~), " + "libtexlua52 (>= 2015.20160222.37495-1ubuntu0.1), libtexlua52 (<< 2015.20160222.37495-1ubuntu0.1.1~), " + "libtexluajit2 (>= 2015.20160222.37495-1ubuntu0.1), libtexluajit2 (<< 2015.20160222.37495-1ubuntu0.1.1~)", + "libldb1 (<< 2:1.1.25~), libldb1 (>> 2:1.1.24~)", + "libnettle6 (= 3.2-1ubuntu0.16.04.2), libhogweed4 (= 3.2-1ubuntu0.16.04.2), libgmp10-dev, dpkg (>= 1.15.4) | install-info", + "libkf5widgetsaddons-data (= 5.18.0-0ubuntu1), libc6 (>= 2.14), libqt5core5a (>= 5.5.0), " + "libqt5gui5 (>= 5.3.0) | libqt5gui5-gles (>= 5.3.0), libqt5widgets5 (>= 5.2.0), libstdc++6 (>= 4.1.1)", + "libnuma1 (= 2.0.11-1ubuntu1.1), libc6-dev | libc-dev", + "perl, perl (>= 5.11.2) | libextutils-cbuilder-perl, perl (>= 5.12) | libextutils-parsexs-perl, " + "perl (>= 5.13.9) | libmodule-metadata-perl, perl (>= 5.13.9) | libversion-perl (>= 1:0.8700), " + "perl (>= 5.19.5) | libtest-harness-perl (>= 3.29), perl (>= 5.21.3) | libcpan-meta-perl (>= 2.142060)", + "libc6 (>= 2.14), libglib2.0-0 (>= 2.37.3), libgtk-3-0 (>= 3.11.5), libgtksourceview-3.0-1 (>= 3.17.3), " + "libpeas-1.0-0 (>= 1.0.0), libzeitgeist-2.0-0 (>= 0.9.9), dconf-gsettings-backend | gsettings-backend, " + "python3 (<< 3.6), python3 (>= 3.5~), python3.5, gedit (>= 3.18), gedit (<< 3.19), gir1.2-git2-glib-1.0, " + "gir1.2-glib-2.0, gir1.2-gtk-3.0, gir1.2-gtksource-3.0, gir1.2-gucharmap-2.90, gir1.2-pango-1.0, gir1.2-peas-1.0, " + "gir1.2-vte-2.91, gir1.2-zeitgeist-2.0, python3-gi, python3-gi-cairo, python3-cairo, python3-dbus", ] for depend in depends: diff --git a/tests/test_package.py b/tests/test_package.py index 2de0738..374027d 100644 --- a/tests/test_package.py +++ b/tests/test_package.py @@ -17,31 +17,39 @@ class PackageTestCase(unittest.TestCase): - def test_find_latest_version(self): - good = ['name_1.0_all.deb', 'name_0.5_all.deb'] - assert 'name_1.0_all.deb' == path.basename(package.find_latest_version(good).original_filename) - bad = ['one_1.0_all.deb', 'two_0.5_all.deb'] + good = ["name_1.0_all.deb", "name_0.5_all.deb"] + assert "name_1.0_all.deb" == path.basename( + package.find_latest_version(good).original_filename + ) + bad = ["one_1.0_all.deb", "two_0.5_all.deb"] self.assertRaises(ValueError, package.find_latest_version, bad) def test_find_latest_versions(self): - packages = ['one_1.0_all.deb', 'one_0.5_all.deb', 'two_1.5_all.deb', 'two_0.1_all.deb'] + packages = ["one_1.0_all.deb", "one_0.5_all.deb", "two_1.5_all.deb", "two_0.1_all.deb"] results = sorted( - [path.basename(a.original_filename) - for a in package.find_latest_versions(packages).values()]) - assert results == sorted(['one_1.0_all.deb', 'two_1.5_all.deb']) + [ + path.basename(a.original_filename) + for a in package.find_latest_versions(packages).values() + ] + ) + assert results == sorted(["one_1.0_all.deb", "two_1.5_all.deb"]) def test_test_DebArch_from_filename(self): - filename = '/var/cache/apt/archives/python2.7_2.7.3-0ubuntu3.4_amd64.deb' + filename = "/var/cache/apt/archives/python2.7_2.7.3-0ubuntu3.4_amd64.deb" deb = package.DebArchive.from_filename(filename) - assert deb.name == 'python2.7' - assert str(deb.version) == '2.7.3-0ubuntu3.4' - assert deb.architecture == 'amd64' + assert deb.name == "python2.7" + assert str(deb.version) == "2.7.3-0ubuntu3.4" + assert deb.architecture == "amd64" assert deb.original_filename == filename def test_DebArch_from_filename__raise_ValueError_on_errors(self): - self.assertRaises(ValueError, package.DebArchive.from_filename, 'python2.7_2.7.3-0ubuntu3.4_amd64.not-a-deb') - self.assertRaises(ValueError, package.DebArchive.from_filename, 'python2.7.deb') + self.assertRaises( + ValueError, + package.DebArchive.from_filename, + "python2.7_2.7.3-0ubuntu3.4_amd64.not-a-deb", + ) + self.assertRaises(ValueError, package.DebArchive.from_filename, "python2.7.deb") V = version.Version.from_string @@ -54,128 +62,135 @@ class VersionTestCase(unittest.TestCase): def test_version_comparison_can_sort(self): # Check version sorting implemented on top of `=' and `<<' comparisons. - expected_order = ['0.1', '0.5', '1.0', '2.0', '3.0', '1:0.4', '2:0.3'] + expected_order = ["0.1", "0.5", "1.0", "2.0", "3.0", "1:0.4", "2:0.3"] assert expected_order != list(sorted(expected_order)) assert list(map(str, sorted(map(V, expected_order)))) == expected_order def test_version_comparison_superior(self): - assert V('1.0') > V('0.5') # usual semantics - assert V('1:0.5') > V('2.0') # unusual semantics - assert not V('0.5') > V('2.0') # sanity check + assert V("1.0") > V("0.5") # usual semantics + assert V("1:0.5") > V("2.0") # unusual semantics + assert not V("0.5") > V("2.0") # sanity check def test_version_comparison_superior_or_equal(self): - assert V('0.75') >= V('0.5') # usual semantics - assert V('0.50') >= V('0.5') # usual semantics - assert V('1:0.5') >= V('5.0') # unusual semantics - assert not V('0.2') >= V('0.5') # sanity check + assert V("0.75") >= V("0.5") # usual semantics + assert V("0.50") >= V("0.5") # usual semantics + assert V("1:0.5") >= V("5.0") # unusual semantics + assert not V("0.2") >= V("0.5") # sanity check def test_version_comparison_inferior(self): - assert V('0.5') < V('1.0') # usual semantics - assert V('2.0') < V('1:0.5') # unusual semantics - assert not V('2.0') < V('0.5') # sanity check + assert V("0.5") < V("1.0") # usual semantics + assert V("2.0") < V("1:0.5") # unusual semantics + assert not V("2.0") < V("0.5") # sanity check def test_version_comparison_inferior_or_equal(self): - assert V('0.5') <= V('0.75') # usual semantics - assert V('0.5') <= V('0.50') # usual semantics - assert V('5.0') <= V('1:0.5') # unusual semantics - assert not V('0.5') <= V('0.2') # sanity check + assert V("0.5") <= V("0.75") # usual semantics + assert V("0.5") <= V("0.50") # usual semantics + assert V("5.0") <= V("1:0.5") # unusual semantics + assert not V("0.5") <= V("0.2") # sanity check def test_version_comparison_equal(self): - assert V('42') # usual semantics == V('42') - assert V('0:0.5') # unusual semantics == V('0.5') - assert V('1.0') # sanity check == not V('0.5') + assert V("42") # usual semantics == V('42') + assert V("0:0.5") # unusual semantics == V('0.5') + assert V("1.0") # sanity check == not V('0.5') def test_version_comparison_not_equal(self): - assert V('1') != V('0') # usual semantics - assert not V('0.5') != V('0:0.5') # unusual semantics + assert V("1") != V("0") # usual semantics + assert not V("0.5") != V("0:0.5") # unusual semantics class DebArchiveTestCase(unittest.TestCase): - def test_DebArchive_from_filename(self): - fn = '/var/cache/apt/archives/python2.7_2.7.3-0ubuntu3.4_amd64.deb' + fn = "/var/cache/apt/archives/python2.7_2.7.3-0ubuntu3.4_amd64.deb" debarch = package.DebArchive.from_filename(fn) expected = package.DebArchive( - name='python2.7', - version=version.Version(epoch=0, upstream='2.7.3', revision='0ubuntu3.4'), - architecture='amd64', - original_filename=fn) + name="python2.7", + version=version.Version(epoch=0, upstream="2.7.3", revision="0ubuntu3.4"), + architecture="amd64", + original_filename=fn, + ) assert debarch == expected def test_DebArchive_from_filename_udeb(self): - fn = '/var/cache/apt/archives/python2.7_2.7.3-0ubuntu3.4_amd64.udeb' + fn = "/var/cache/apt/archives/python2.7_2.7.3-0ubuntu3.4_amd64.udeb" debarch = package.DebArchive.from_filename(fn) expected = package.DebArchive( - name='python2.7', - version=version.Version(epoch=0, upstream='2.7.3', revision='0ubuntu3.4'), - architecture='amd64', - original_filename=fn) + name="python2.7", + version=version.Version(epoch=0, upstream="2.7.3", revision="0ubuntu3.4"), + architecture="amd64", + original_filename=fn, + ) assert debarch == expected def test_CodeArchive_from_filename(self): - fn = '/var/cache/apt/archives/python2.7_2.7.3-0ubuntu3.4.orig.tar.gz' + fn = "/var/cache/apt/archives/python2.7_2.7.3-0ubuntu3.4.orig.tar.gz" debarch = package.CodeArchive.from_filename(fn) expected = package.CodeArchive( - name='python2.7', - version=version.Version(epoch=0, upstream='2.7.3', revision='0ubuntu3.4'), - original_filename=fn) + name="python2.7", + version=version.Version(epoch=0, upstream="2.7.3", revision="0ubuntu3.4"), + original_filename=fn, + ) assert debarch == expected def test_CodeMetadata_from_filename_dsc(self): - fn = 'base-files_11.1+deb11u8.dsc' + fn = "base-files_11.1+deb11u8.dsc" debarch = package.CodeMetadata.from_filename(fn) expected = package.CodeMetadata( - name='base-files', - version=version.Version(epoch=0, upstream='11.1+deb11u8', revision='0'), - original_filename=fn) + name="base-files", + version=version.Version(epoch=0, upstream="11.1+deb11u8", revision="0"), + original_filename=fn, + ) assert debarch == expected def test_CodeMetadata_from_filename_copyright(self): - fn = 'bash_4.1-3+deb6u2_copyright' + fn = "bash_4.1-3+deb6u2_copyright" debarch = package.CodeMetadata.from_filename(fn) expected = package.CodeMetadata( - name='bash', - version=version.Version(epoch=0, upstream='4.1', revision='3+deb6u2'), - original_filename=fn) + name="bash", + version=version.Version(epoch=0, upstream="4.1", revision="3+deb6u2"), + original_filename=fn, + ) assert debarch == expected def test_CodeArchive_from_filename_supports_tar_gz_bz2_and_xz(self): - package.CodeArchive.from_filename('python2.7_2.7.3-0ubuntu3.4.orig.tar.gz') - package.CodeArchive.from_filename('python2.7_2.7.3-0ubuntu3.4.debian.tar.gz') + package.CodeArchive.from_filename("python2.7_2.7.3-0ubuntu3.4.orig.tar.gz") + package.CodeArchive.from_filename("python2.7_2.7.3-0ubuntu3.4.debian.tar.gz") - package.CodeArchive.from_filename('python2.7_2.7.3-0ubuntu3.4.orig.tar.bz2') - package.CodeArchive.from_filename('python2.7_2.7.3-0ubuntu3.4.debian.tar.bz2') + package.CodeArchive.from_filename("python2.7_2.7.3-0ubuntu3.4.orig.tar.bz2") + package.CodeArchive.from_filename("python2.7_2.7.3-0ubuntu3.4.debian.tar.bz2") - package.CodeArchive.from_filename('python2.7_2.7.3-0ubuntu3.4.orig.tar.xz') - package.CodeArchive.from_filename('python2.7_2.7.3-0ubuntu3.4.debian.tar.xz') + package.CodeArchive.from_filename("python2.7_2.7.3-0ubuntu3.4.orig.tar.xz") + package.CodeArchive.from_filename("python2.7_2.7.3-0ubuntu3.4.debian.tar.xz") def test_CodeArchive_from_filename_raises_ValueError(self): with self.assertRaises(ValueError): - package.CodeArchive.from_filename('python2.7_2.7.3-0ubuntu3.4.orif.tar.gz') + package.CodeArchive.from_filename("python2.7_2.7.3-0ubuntu3.4.orif.tar.gz") class ControlTestCase(unittest.TestCase): - def test_parse_control_fields_1(self): - deb822_package = debcon.Debian822([ - ('Package', 'python-py2deb'), - ('Depends', 'python-deb-pkg-tools, python-pip, python-pip-accel'), - ('Installed-Size', '42'), - ]) + deb822_package = debcon.Debian822( + [ + ("Package", "python-py2deb"), + ("Depends", "python-deb-pkg-tools, python-pip, python-pip-accel"), + ("Installed-Size", "42"), + ] + ) parsed_info = debcon.parse_control_fields(deb822_package) expected = { - 'Package': 'python-py2deb', - 'Depends': deps.AndRelationships(( - deps.Relationship(name=u'python-deb-pkg-tools'), - deps.Relationship(name=u'python-pip'), - deps.Relationship(name=u'python-pip-accel') - )), - 'Installed-Size': 42 + "Package": "python-py2deb", + "Depends": deps.AndRelationships( + ( + deps.Relationship(name="python-deb-pkg-tools"), + deps.Relationship(name="python-pip"), + deps.Relationship(name="python-pip-accel"), + ) + ), + "Installed-Size": 42, } assert parsed_info == expected def test_parse_control_fields_2(self): - unparsed_fields = debcon.Debian822.from_string(''' + unparsed_fields = debcon.Debian822.from_string(""" Package: python3.4-minimal Version: 3.4.0-1+precise1 Architecture: amd64 @@ -185,19 +200,19 @@ def test_parse_control_fields_2(self): Recommends: python3.4 Suggests: binfmt-support Conflicts: binfmt-support (<< 1.1.2) -''') +""") expected = { - 'Architecture': 'amd64', - 'Conflicts': 'binfmt-support (<< 1.1.2)', - 'Depends': 'libpython3.4-minimal (= 3.4.0-1+precise1), libexpat1 (>= 1.95.8), ' - 'libgcc1 (>= 1:4.1.1), zlib1g (>= 1:1.2.0), foo | bar', - 'Installed-Size': '3586', - 'Package': 'python3.4-minimal', - 'Pre-Depends': 'libc6 (>= 2.15)', - 'Recommends': 'python3.4', - 'Suggests': 'binfmt-support', - 'Version': '3.4.0-1+precise1', + "Architecture": "amd64", + "Conflicts": "binfmt-support (<< 1.1.2)", + "Depends": "libpython3.4-minimal (= 3.4.0-1+precise1), libexpat1 (>= 1.95.8), " + "libgcc1 (>= 1:4.1.1), zlib1g (>= 1:1.2.0), foo | bar", + "Installed-Size": "3586", + "Package": "python3.4-minimal", + "Pre-Depends": "libc6 (>= 2.15)", + "Recommends": "python3.4", + "Suggests": "binfmt-support", + "Version": "3.4.0-1+precise1", } assert unparsed_fields.to_dict(normalize_names=True) == expected @@ -205,27 +220,44 @@ def test_parse_control_fields_2(self): parsed_fields = debcon.parse_control_fields(unparsed_fields) expected = { - 'Architecture': 'amd64', - 'Conflicts': deps.AndRelationships(relationships=( - deps.VersionedRelationship(name=u'binfmt-support', operator=u'<<', version=u'1.1.2') - ,)), - 'Depends': deps.AndRelationships(relationships=( - deps.VersionedRelationship(name=u'libpython3.4-minimal', operator=u'=', version=u'3.4.0-1+precise1'), - deps.VersionedRelationship(name=u'libexpat1', operator=u'>=', version=u'1.95.8'), - deps.VersionedRelationship(name=u'libgcc1', operator=u'>=', version=u'1:4.1.1'), - deps.VersionedRelationship(name=u'zlib1g', operator=u'>=', version=u'1:1.2.0'), - deps.OrRelationships(relationships=( - deps.Relationship(name=u'foo'), - deps.Relationship(name=u'bar') - ,)) - ),), - 'Installed-Size': 3586, - 'Package': 'python3.4-minimal', - 'Pre-Depends': deps.AndRelationships(relationships=( - deps.VersionedRelationship(name=u'libc6', operator=u'>=', version=u'2.15') - ,)), - 'Recommends': deps.AndRelationships(relationships=(deps.Relationship(name=u'python3.4'),)), - 'Suggests': deps.AndRelationships(relationships=(deps.Relationship(name=u'binfmt-support'),)), - 'Version': '3.4.0-1+precise1'} + "Architecture": "amd64", + "Conflicts": deps.AndRelationships( + relationships=( + deps.VersionedRelationship( + name="binfmt-support", operator="<<", version="1.1.2" + ), + ) + ), + "Depends": deps.AndRelationships( + relationships=( + deps.VersionedRelationship( + name="libpython3.4-minimal", operator="=", version="3.4.0-1+precise1" + ), + deps.VersionedRelationship(name="libexpat1", operator=">=", version="1.95.8"), + deps.VersionedRelationship(name="libgcc1", operator=">=", version="1:4.1.1"), + deps.VersionedRelationship(name="zlib1g", operator=">=", version="1:1.2.0"), + deps.OrRelationships( + relationships=( + deps.Relationship(name="foo"), + deps.Relationship(name="bar"), + ) + ), + ), + ), + "Installed-Size": 3586, + "Package": "python3.4-minimal", + "Pre-Depends": deps.AndRelationships( + relationships=( + deps.VersionedRelationship(name="libc6", operator=">=", version="2.15"), + ) + ), + "Recommends": deps.AndRelationships( + relationships=(deps.Relationship(name="python3.4"),) + ), + "Suggests": deps.AndRelationships( + relationships=(deps.Relationship(name="binfmt-support"),) + ), + "Version": "3.4.0-1+precise1", + } assert parsed_fields == expected diff --git a/tests/test_skeleton_codestyle.py b/tests/test_skeleton_codestyle.py deleted file mode 100644 index 2eb6e55..0000000 --- a/tests/test_skeleton_codestyle.py +++ /dev/null @@ -1,36 +0,0 @@ -# -# Copyright (c) nexB Inc. and others. All rights reserved. -# ScanCode is a trademark of nexB Inc. -# SPDX-License-Identifier: Apache-2.0 -# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. -# See https://github.com/nexB/skeleton for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - -import subprocess -import unittest -import configparser - - -class BaseTests(unittest.TestCase): - def test_skeleton_codestyle(self): - """ - This test shouldn't run in proliferated repositories. - """ - setup_cfg = configparser.ConfigParser() - setup_cfg.read("setup.cfg") - if setup_cfg["metadata"]["name"] != "skeleton": - return - - args = "venv/bin/black --check -l 100 setup.py etc tests" - try: - subprocess.check_output(args.split()) - except subprocess.CalledProcessError as e: - print("===========================================================") - print(e.output) - print("===========================================================") - raise Exception( - "Black style check failed; please format the code using:\n" - " python -m black -l 100 setup.py etc tests", - e.output, - ) from e diff --git a/tests/test_unsign.py b/tests/test_unsign.py index f329f93..36fe657 100644 --- a/tests/test_unsign.py +++ b/tests/test_unsign.py @@ -13,19 +13,18 @@ class Testunsign(TestCase): - def test_remove_signature_and_is_signed_corner_cases(self): assert not unsign.is_signed(None) assert unsign.remove_signature(None) == None - assert not unsign.is_signed('') - assert unsign.remove_signature('') == '' + assert not unsign.is_signed("") + assert unsign.remove_signature("") == "" - assert not unsign.is_signed('\n') - assert unsign.remove_signature('\n') == '\n' + assert not unsign.is_signed("\n") + assert unsign.remove_signature("\n") == "\n" - assert not unsign.is_signed('sometext\n') - assert unsign.remove_signature('sometext\n') == 'sometext\n' + assert not unsign.is_signed("sometext\n") + assert unsign.remove_signature("sometext\n") == "sometext\n" def test_remove_signature_and_is_signed_not_signed(self): assert not unsign.is_signed(PLAIN) @@ -33,7 +32,7 @@ def test_remove_signature_and_is_signed_not_signed(self): def test_remove_signature_and_is_signed_empty_signed(self): assert unsign.is_signed(EMPTY) - assert unsign.remove_signature(EMPTY) == '\n' + assert unsign.remove_signature(EMPTY) == "\n" def test_remove_signature_and_is_signed_signed(self): assert unsign.is_signed(SIGNED) @@ -44,27 +43,27 @@ def test_remove_signature_and_is_signed_compact(self): assert unsign.remove_signature(COMPACT) == EXPECTED_COMPACT def test_is_signed1(self): - text = ''' + text = """ -----BEGIN PGP SIGNED MESSAGE----- -----END PGP SIGNATURE----- - ''' + """ assert unsign.is_signed(text) def test_is_signed2(self): - text = '''-----BEGIN PGP SIGNED MESSAGE----- - -----END PGP SIGNATURE-----''' + text = """-----BEGIN PGP SIGNED MESSAGE----- + -----END PGP SIGNATURE-----""" assert unsign.is_signed(text) def test_is_signed3(self): - text = '''-----BEGIN PGP SIGNED MESSAGE-----''' + text = """-----BEGIN PGP SIGNED MESSAGE-----""" assert not unsign.is_signed(text) def test_is_signed4(self): - text = '''-----END PGP SIGNATURE-----''' + text = """-----END PGP SIGNATURE-----""" assert not unsign.is_signed(text) -PLAIN = '''Format: 3.0 (quilt) +PLAIN = """Format: 3.0 (quilt) Source: zlib Binary: zlib1g, zlib1g-dev, zlib1g-dbg, zlib1g-udeb, lib64z1, lib64z1-dev, lib32z1, lib32z1-dev, libn32z1, libn32z1-dev Architecture: any @@ -72,9 +71,9 @@ def test_is_signed4(self): Maintainer: Mark Brown Homepage: http://zlib.net/ Standards-Version: 3.9.8 -''' +""" -SIGNED = '''-----BEGIN PGP SIGNED MESSAGE----- +SIGNED = """-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA512 Format: 3.0 (quilt) @@ -118,9 +117,9 @@ def test_is_signed4(self): 6DzauiRhm79p2HZPGWyLvZ0rMUaVvIGYeNjO2n4Lpkc+snZAEX/3LFVQ =BVVn -----END PGP SIGNATURE----- -''' +""" -EXPECTED_SIGNED = '''Format: 3.0 (quilt) +EXPECTED_SIGNED = """Format: 3.0 (quilt) Source: zlib Binary: zlib1g, zlib1g-dev, zlib1g-dbg, zlib1g-udeb, lib64z1, lib64z1-dev, lib32z1, lib32z1-dev, libn32z1, libn32z1-dev Architecture: any @@ -149,9 +148,9 @@ def test_is_signed4(self): Files: 2950b229ed4a5e556ad6581580e4ab2c 370248 zlib_1.2.11.dfsg.orig.tar.gz fd4b8f37a845569734dfa2e0fe8a08dc 18956 zlib_1.2.11.dfsg-1.debian.tar.xz -''' +""" -EMPTY = '''-----BEGIN PGP SIGNED MESSAGE----- +EMPTY = """-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA512 @@ -167,9 +166,9 @@ def test_is_signed4(self): 6DzauiRhm79p2HZPGWyLvZ0rMUaVvIGYeNjO2n4Lpkc+snZAEX/3LFVQ =BVVn -----END PGP SIGNATURE----- -''' +""" -COMPACT = '''-----BEGIN PGP SIGNED MESSAGE----- +COMPACT = """-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA512 Format: 3.0 (quilt) Source: zlib @@ -210,9 +209,9 @@ def test_is_signed4(self): 6DzauiRhm79p2HZPGWyLvZ0rMUaVvIGYeNjO2n4Lpkc+snZAEX/3LFVQ =BVVn -----END PGP SIGNATURE----- -''' +""" -EXPECTED_COMPACT = '''Hash: SHA512 +EXPECTED_COMPACT = """Hash: SHA512 Format: 3.0 (quilt) Source: zlib Binary: zlib1g, zlib1g-dev, zlib1g-dbg, zlib1g-udeb, lib64z1, lib64z1-dev, lib32z1, lib32z1-dev, libn32z1, libn32z1-dev @@ -241,4 +240,4 @@ def test_is_signed4(self): 00b95b629fbe9a5181f8ba1ceddedf627aba1ab42e47f5916be8a41deb54098a 18956 zlib_1.2.11.dfsg-1.debian.tar.xz Files: 2950b229ed4a5e556ad6581580e4ab2c 370248 zlib_1.2.11.dfsg.orig.tar.gz - fd4b8f37a845569734dfa2e0fe8a08dc 18956 zlib_1.2.11.dfsg-1.debian.tar.xz''' + fd4b8f37a845569734dfa2e0fe8a08dc 18956 zlib_1.2.11.dfsg-1.debian.tar.xz""" diff --git a/tests/test_utils.py b/tests/test_utils.py index 72a0ce0..962561a 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -15,26 +15,26 @@ class JsonTester(testcase.FileBasedTesting): - test_data_dir = os.path.join(os.path.dirname(__file__), 'data') + test_data_dir = os.path.join(os.path.dirname(__file__), "data") def check_json(self, results, expected_loc, regen=False, sort=False): """ - Helper to test a results Python native object against an expected JSON + Test Python native object results against an expected JSON file at expected_loc. """ expected_loc = self.get_test_loc(expected_loc, must_exist=False) if regen: regened_exp_loc = self.get_temp_file() - with open(regened_exp_loc, 'w') as ex: - json.dump(results, ex, indent=2, separators=(',', ': ')) + with open(regened_exp_loc, "w") as ex: + json.dump(results, ex, indent=2, separators=(",", ": ")) expected_dir = os.path.dirname(expected_loc) if not os.path.exists(expected_dir): os.makedirs(expected_dir) shutil.copy(regened_exp_loc, expected_loc) - with open(expected_loc, 'rb') as ex: + with open(expected_loc, "rb") as ex: expected = json.load(ex) if sort: assert sorted(results) == sorted(expected) @@ -43,14 +43,14 @@ def check_json(self, results, expected_loc, regen=False, sort=False): def check_file(self, results, expected_loc, regen=False, sort=False): """ - Helper to test a results text string against an expected file at + Test results text string against an expected file at expected_loc. """ expected_loc = self.get_test_loc(expected_loc) if regen: regened_exp_loc = self.get_temp_file() - with open(regened_exp_loc, 'w') as ex: + with open(regened_exp_loc, "w") as ex: ex.write(results) expected_dir = os.path.dirname(expected_loc) @@ -58,9 +58,9 @@ def check_file(self, results, expected_loc, regen=False, sort=False): os.makedirs(expected_dir) shutil.copy(regened_exp_loc, expected_loc) - with open(expected_loc, 'rb') as ex: + with open(expected_loc, "rb") as ex: expected = ex.read() - expected = expected.decode('utf-8') + expected = expected.decode("utf-8") if sort: assert sorted(results.splitlines()) == sorted(expected.splitlines()) else: diff --git a/tests/test_version.py b/tests/test_version.py index 8ee2fbb..9e62d11 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -26,7 +26,7 @@ def get_non_digit_prefix(s): val = version.get_non_digit_prefix(list(s)) - return ''.join(val) + return "".join(val) def get_digit_prefix(s): @@ -34,65 +34,64 @@ def get_digit_prefix(s): class DebianVersionTest(TestCase): - def test_Version_from_string_epoch(self): - assert 0 == Version.from_string('0').epoch - assert 0 == Version.from_string('0:0').epoch - assert 1 == Version.from_string('1:0').epoch + assert 0 == Version.from_string("0").epoch + assert 0 == Version.from_string("0:0").epoch + assert 1 == Version.from_string("1:0").epoch def test_Version_from_string_validates(self): - self.assertRaises(ValueError, Version.from_string, 'a') - self.assertRaises(ValueError, Version.from_string, '1a:0') - self.assertRaises(ValueError, Version.from_string, '0:a.0.0-foo') + self.assertRaises(ValueError, Version.from_string, "a") + self.assertRaises(ValueError, Version.from_string, "1a:0") + self.assertRaises(ValueError, Version.from_string, "0:a.0.0-foo") def test_Version_from_string_tuples(self): - assert (0, '00', '0') == Version.from_string('00').tuple() - assert (0, '00', '00') == Version.from_string('00-00').tuple() - assert (0, '0', '0') == Version.from_string('0:0').tuple() - assert (0, '0', '0') == Version.from_string('0:0-0').tuple() - assert (0, '0.0', '0') == Version.from_string('0:0.0').tuple() - assert (0, '0.0', '0') == Version.from_string('0:0.0-0').tuple() - assert (0, '0.0', '00') == Version.from_string('0:0.0-00').tuple() + assert (0, "00", "0") == Version.from_string("00").tuple() + assert (0, "00", "00") == Version.from_string("00-00").tuple() + assert (0, "0", "0") == Version.from_string("0:0").tuple() + assert (0, "0", "0") == Version.from_string("0:0-0").tuple() + assert (0, "0.0", "0") == Version.from_string("0:0.0").tuple() + assert (0, "0.0", "0") == Version.from_string("0:0.0-0").tuple() + assert (0, "0.0", "00") == Version.from_string("0:0.0-00").tuple() def test_Version_from_string_tilde(self): - assert '0~' == Version.from_string('0.0.0+dfsg-0~').revision + assert "0~" == Version.from_string("0.0.0+dfsg-0~").revision def test_get_non_digit_prefix(self): - assert '' == get_non_digit_prefix('') - assert '' == get_non_digit_prefix('0') - assert '' == get_non_digit_prefix('00') - assert '' == get_non_digit_prefix('0a') - assert 'a' == get_non_digit_prefix('a') - assert 'a' == get_non_digit_prefix('a0') - assert 'aHAD' == get_non_digit_prefix('aHAD0030') + assert "" == get_non_digit_prefix("") + assert "" == get_non_digit_prefix("0") + assert "" == get_non_digit_prefix("00") + assert "" == get_non_digit_prefix("0a") + assert "a" == get_non_digit_prefix("a") + assert "a" == get_non_digit_prefix("a0") + assert "aHAD" == get_non_digit_prefix("aHAD0030") def test_get_digit_prefix(self): - assert 0 == get_digit_prefix('00') - assert 0 == get_digit_prefix('0') - assert 0 == get_digit_prefix('0a') - assert 12 == get_digit_prefix('12a') - assert 0 == get_digit_prefix('a') - assert 0 == get_digit_prefix('a0') - assert 0 == get_digit_prefix('arttr23123') - assert 123 == get_digit_prefix('123sdf') - assert 123 == get_digit_prefix('0123sdf') + assert 0 == get_digit_prefix("00") + assert 0 == get_digit_prefix("0") + assert 0 == get_digit_prefix("0a") + assert 12 == get_digit_prefix("12a") + assert 0 == get_digit_prefix("a") + assert 0 == get_digit_prefix("a0") + assert 0 == get_digit_prefix("arttr23123") + assert 123 == get_digit_prefix("123sdf") + assert 123 == get_digit_prefix("0123sdf") def test_compare_strings(self): - assert -1 == compare_strings('~', '.') - assert -1 == compare_strings('~', 'a') - assert -1 == compare_strings('a', '.') - assert 1 == compare_strings('a', '~') - assert 1 == compare_strings('.', '~') - assert 1 == compare_strings('.', 'a') - assert 0 == compare_strings('.', '.') - assert 0 == compare_strings('0', '0') - assert 0 == compare_strings('a', 'a') + assert -1 == compare_strings("~", ".") + assert -1 == compare_strings("~", "a") + assert -1 == compare_strings("a", ".") + assert 1 == compare_strings("a", "~") + assert 1 == compare_strings(".", "~") + assert 1 == compare_strings(".", "a") + assert 0 == compare_strings(".", ".") + assert 0 == compare_strings("0", "0") + assert 0 == compare_strings("a", "a") def test_compare_strings_can_sort(self): # taken from # http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version - result = sorted(['a', '', '~', '~~a', '~~'], key=version.compare_strings_key) - expected = ['~~', '~~a', '~', '', 'a'] + result = sorted(["a", "", "~", "~~a", "~~"], key=version.compare_strings_key) + expected = ["~~", "~~a", "~", "", "a"] assert result == expected def test_compare_strings_more(self): @@ -101,72 +100,72 @@ def test_compare_strings_more(self): # revision onto itself, not an upstream of 0.0.9 and a debian of foo. # equals - assert 0 == compare_strings('0', '0') - assert 0 == compare_strings('0', '00') - assert 0 == compare_strings('00.0.9', '0.0.9') - assert 0 == compare_strings('0.00.9-foo', '0.0.9-foo') - assert 0 == compare_strings('0.0.9-1.00foo', '0.0.9-1.0foo') + assert 0 == compare_strings("0", "0") + assert 0 == compare_strings("0", "00") + assert 0 == compare_strings("00.0.9", "0.0.9") + assert 0 == compare_strings("0.00.9-foo", "0.0.9-foo") + assert 0 == compare_strings("0.0.9-1.00foo", "0.0.9-1.0foo") # less than - assert -1 == compare_strings('0.0.9', '0.0.10') - assert -1 == compare_strings('0.0.9-foo', '0.0.10-foo') - assert -1 == compare_strings('0.0.9-foo', '0.0.10-goo') - assert -1 == compare_strings('0.0.9-foo', '0.0.9-goo') - assert -1 == compare_strings('0.0.10-foo', '0.0.10-goo') - assert -1 == compare_strings('0.0.9-1.0foo', '0.0.9-1.1foo') + assert -1 == compare_strings("0.0.9", "0.0.10") + assert -1 == compare_strings("0.0.9-foo", "0.0.10-foo") + assert -1 == compare_strings("0.0.9-foo", "0.0.10-goo") + assert -1 == compare_strings("0.0.9-foo", "0.0.9-goo") + assert -1 == compare_strings("0.0.10-foo", "0.0.10-goo") + assert -1 == compare_strings("0.0.9-1.0foo", "0.0.9-1.1foo") # greater than - assert 1 == compare_strings('0.0.10', '0.0.9') - assert 1 == compare_strings('0.0.10-foo', '0.0.9-foo') - assert 1 == compare_strings('0.0.10-foo', '0.0.9-goo') - assert 1 == compare_strings('0.0.9-1.0foo', '0.0.9-1.0bar') + assert 1 == compare_strings("0.0.10", "0.0.9") + assert 1 == compare_strings("0.0.10-foo", "0.0.9-foo") + assert 1 == compare_strings("0.0.10-foo", "0.0.9-goo") + assert 1 == compare_strings("0.0.9-1.0foo", "0.0.9-1.0bar") def test_compare_versions(self): # "This [the epoch] is a single (generally small) unsigned integer. # It may be omitted, in which case zero is assumed." - assert 0 == compare_versions('0.0.0', '0:0.0.0') - assert 0 == compare_versions('0:0.0.0-foo', '0.0.0-foo') - assert 0 == compare_versions('0.0.0-a', '0:0.0.0-a') + assert 0 == compare_versions("0.0.0", "0:0.0.0") + assert 0 == compare_versions("0:0.0.0-foo", "0.0.0-foo") + assert 0 == compare_versions("0.0.0-a", "0:0.0.0-a") # "The absence of a debian_revision is equivalent to a debian_revision of 0." - assert 0 == compare_versions('0.0.0', '0.0.0-0') + assert 0 == compare_versions("0.0.0", "0.0.0-0") # tricksy: - assert 0 == compare_versions('0.0.0', '0.0.0-00') + assert 0 == compare_versions("0.0.0", "0.0.0-00") # combining the above - assert 0 == compare_versions('0.0.0-0', '0:0.0.0') + assert 0 == compare_versions("0.0.0-0", "0:0.0.0") # explicitly equal - assert 0 == compare_versions('0.0.0', '0.0.0') - assert 0 == compare_versions('1:0.0.0', '1:0.0.0') - assert 0 == compare_versions('0.0.0-10', '0.0.0-10') - assert 0 == compare_versions('2:0.0.0-1', '2:0.0.0-1') + assert 0 == compare_versions("0.0.0", "0.0.0") + assert 0 == compare_versions("1:0.0.0", "1:0.0.0") + assert 0 == compare_versions("0.0.0-10", "0.0.0-10") + assert 0 == compare_versions("2:0.0.0-1", "2:0.0.0-1") # less than - assert -1 == compare_versions('0.0.0-0', '0:0.0.1') - assert -1 == compare_versions('0.0.0-0', '0:0.0.0-a') - assert -1 == compare_versions('0.0.0-0', '0:0.0.0-1') - assert -1 == compare_versions('0.0.9', '0.0.10') - assert -1 == compare_versions('0.9.0', '0.10.0') - assert -1 == compare_versions('9.0.0', '10.0.0') + assert -1 == compare_versions("0.0.0-0", "0:0.0.1") + assert -1 == compare_versions("0.0.0-0", "0:0.0.0-a") + assert -1 == compare_versions("0.0.0-0", "0:0.0.0-1") + assert -1 == compare_versions("0.0.9", "0.0.10") + assert -1 == compare_versions("0.9.0", "0.10.0") + assert -1 == compare_versions("9.0.0", "10.0.0") assert -1 == compare_versions("1.2.3-1~deb7u1", "1.2.3-1") assert -1 == compare_versions("2.7.4+reloaded2-13ubuntu1", "2.7.4+reloaded2-13+deb9u1") assert -1 == compare_versions("2.7.4+reloaded2-13", "2.7.4+reloaded2-13+deb9u1") # greater than - assert 1 == compare_versions('0.0.1-0', '0:0.0.0') - assert 1 == compare_versions('0.0.0-a', '0:0.0.0-1') - assert 1 == compare_versions('0.0.0-a', '0:0.0.0-0') - assert 1 == compare_versions('0.0.9', '0.0.1') - assert 1 == compare_versions('0.9.0', '0.1.0') - assert 1 == compare_versions('9.0.0', '1.0.0') + assert 1 == compare_versions("0.0.1-0", "0:0.0.0") + assert 1 == compare_versions("0.0.0-a", "0:0.0.0-1") + assert 1 == compare_versions("0.0.0-a", "0:0.0.0-0") + assert 1 == compare_versions("0.0.9", "0.0.1") + assert 1 == compare_versions("0.9.0", "0.1.0") + assert 1 == compare_versions("9.0.0", "1.0.0") assert 1 == compare_versions("1.2.3-1", "1.2.3-1~deb7u1") assert 1 == compare_versions("2.7.4+reloaded2-13+deb9u1", "2.7.4+reloaded2-13ubuntu1") assert 1 == compare_versions("2.7.4+reloaded2-13+deb9u1", "2.7.4+reloaded2-13") # unicode - assert -1 == compare_versions(u'2:0.0.44-1', u'2:0.0.44-nobin') - assert 1 == compare_versions(u'2:0.0.44-nobin', u'2:0.0.44-1') - assert 0 == compare_versions(u'2:0.0.44-1', u'2:0.0.44-1') + assert -1 == compare_versions("2:0.0.44-1", "2:0.0.44-nobin") + assert 1 == compare_versions("2:0.0.44-nobin", "2:0.0.44-1") + assert 0 == compare_versions("2:0.0.44-1", "2:0.0.44-1") diff --git a/tests/test_version_python_deb_pkg_tools.py b/tests/test_version_python_deb_pkg_tools.py index 3e91587..6fc5c0e 100644 --- a/tests/test_version_python_deb_pkg_tools.py +++ b/tests/test_version_python_deb_pkg_tools.py @@ -16,38 +16,37 @@ class DebPkgToolsTestCase(TestCase): - def test_version_sorting(self): # Check version sorting implemented on top of `=' and `<<' comparisons. - expected_order = ['0.1', '0.5', '1.0', '2.0', '3.0', '1:0.4', '2:0.3'] + expected_order = ["0.1", "0.5", "1.0", "2.0", "3.0", "1:0.4", "2:0.3"] assert list(sorted(expected_order)) != expected_order result = [str(v) for v in sorted(map(Version.from_string, expected_order))] assert expected_order == result def test_version_comparison(self): - assert Version.from_string('1.0') > Version.from_string('0.5') - assert Version.from_string('1:0.5') > Version.from_string('2.0') - assert not Version.from_string('0.5') > Version.from_string('2.0') - - assert Version.from_string('0.75') >= Version.from_string('0.5') - assert Version.from_string('0.50') >= Version.from_string('0.5') - assert Version.from_string('1:0.5') >= Version.from_string('5.0') - assert not Version.from_string('0.2') >= Version.from_string('0.5') - - assert Version.from_string('0.5') < Version.from_string('1.0') - assert Version.from_string('2.0') < Version.from_string('1:0.5') - assert not Version.from_string('2.0') < Version.from_string('0.5') - - assert Version.from_string('0.5') <= Version.from_string('0.75') - assert Version.from_string('0.5') <= Version.from_string('0.50') - assert Version.from_string('5.0') <= Version.from_string('1:0.5') - assert not Version.from_string('0.5') <= Version.from_string('0.2') - - assert Version.from_string('42') == Version.from_string('42') - assert Version.from_string('0.5') == Version.from_string('0:0.5') - assert Version.from_string('0.5') != Version.from_string('1.0') - - assert Version.from_string('1') != Version.from_string('0') - assert not Version.from_string('0.5') != Version.from_string('0:0.5') + assert Version.from_string("1.0") > Version.from_string("0.5") + assert Version.from_string("1:0.5") > Version.from_string("2.0") + assert not Version.from_string("0.5") > Version.from_string("2.0") + + assert Version.from_string("0.75") >= Version.from_string("0.5") + assert Version.from_string("0.50") >= Version.from_string("0.5") + assert Version.from_string("1:0.5") >= Version.from_string("5.0") + assert not Version.from_string("0.2") >= Version.from_string("0.5") + + assert Version.from_string("0.5") < Version.from_string("1.0") + assert Version.from_string("2.0") < Version.from_string("1:0.5") + assert not Version.from_string("2.0") < Version.from_string("0.5") + + assert Version.from_string("0.5") <= Version.from_string("0.75") + assert Version.from_string("0.5") <= Version.from_string("0.50") + assert Version.from_string("5.0") <= Version.from_string("1:0.5") + assert not Version.from_string("0.5") <= Version.from_string("0.2") + + assert Version.from_string("42") == Version.from_string("42") + assert Version.from_string("0.5") == Version.from_string("0:0.5") + assert Version.from_string("0.5") != Version.from_string("1.0") + + assert Version.from_string("1") != Version.from_string("0") + assert not Version.from_string("0.5") != Version.from_string("0:0.5") assert Version.from_string("1.3~rc2") < Version.from_string("1.3")