From ea5044dda26f3ef18d46f139c4d9eb97baec5ba8 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Mon, 17 Jul 2023 13:28:38 -0700 Subject: [PATCH 01/78] Create script to update repo skeleton #80 Signed-off-by: Jono Yang --- etc/scripts/update_skeleton.py | 104 +++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 etc/scripts/update_skeleton.py diff --git a/etc/scripts/update_skeleton.py b/etc/scripts/update_skeleton.py new file mode 100644 index 0000000..635898b --- /dev/null +++ b/etc/scripts/update_skeleton.py @@ -0,0 +1,104 @@ +#!/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://aboutcode.org for more information about nexB OSS projects. +# + +from pathlib import Path +import os +import subprocess + +import click + + +NEXB_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=NEXB_PUBLIC_REPO_NAMES): + """ + Update project files of nexB 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:nexB/{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:nexB/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() From 008d521aec51e5983f6d6a2adc4efa7fd92159cf Mon Sep 17 00:00:00 2001 From: Ayan Sinha Mahapatra Date: Mon, 19 Feb 2024 15:21:45 +0530 Subject: [PATCH 02/78] Update CI runners and python version Signed-off-by: Ayan Sinha Mahapatra --- .github/workflows/docs-ci.yml | 2 +- .github/workflows/pypi-release.yml | 8 ++++---- azure-pipelines.yml | 22 +++++++++++++++------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/.github/workflows/docs-ci.yml b/.github/workflows/docs-ci.yml index ada779b..8c2abfe 100644 --- a/.github/workflows/docs-ci.yml +++ b/.github/workflows/docs-ci.yml @@ -4,7 +4,7 @@ on: [push, pull_request] jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: max-parallel: 4 diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index 9585730..d2206c8 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -21,10 +21,10 @@ on: jobs: build-pypi-distribs: name: Build and publish library to PyPI - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: @@ -47,7 +47,7 @@ jobs: name: Create GH release needs: - build-pypi-distribs - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Download built archives @@ -67,7 +67,7 @@ jobs: name: Create PyPI release needs: - create-gh-release - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Download built archives diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 764883d..373b78c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -11,7 +11,7 @@ jobs: parameters: job_name: ubuntu20_cpython image_name: ubuntu-20.04 - python_versions: ['3.7', '3.8', '3.9', '3.10', '3.11'] + python_versions: ['3.8', '3.9', '3.10', '3.11', '3.12'] test_suites: all: venv/bin/pytest -n 2 -vvs @@ -19,7 +19,7 @@ jobs: parameters: job_name: ubuntu22_cpython image_name: ubuntu-22.04 - python_versions: ['3.7', '3.8', '3.9', '3.10', '3.11'] + python_versions: ['3.8', '3.9', '3.10', '3.11', '3.12'] test_suites: all: venv/bin/pytest -n 2 -vvs @@ -27,7 +27,7 @@ jobs: parameters: job_name: macos11_cpython image_name: macOS-11 - python_versions: ['3.7', '3.8', '3.9', '3.10', '3.11'] + python_versions: ['3.8', '3.9', '3.10', '3.11', '3.12'] test_suites: all: venv/bin/pytest -n 2 -vvs @@ -35,7 +35,7 @@ jobs: parameters: job_name: macos12_cpython image_name: macOS-12 - python_versions: ['3.7', '3.8', '3.9', '3.10', '3.11'] + python_versions: ['3.8', '3.9', '3.10', '3.11', '3.12'] test_suites: all: venv/bin/pytest -n 2 -vvs @@ -43,7 +43,15 @@ jobs: parameters: job_name: macos13_cpython image_name: macOS-13 - python_versions: ['3.7', '3.8', '3.9', '3.10', '3.11'] + python_versions: ['3.8', '3.9', '3.10', '3.11', '3.12'] + test_suites: + all: venv/bin/pytest -n 2 -vvs + + - template: etc/ci/azure-posix.yml + parameters: + job_name: macos14_cpython + image_name: macOS-14 + python_versions: ['3.8', '3.9', '3.10', '3.11', '3.12'] test_suites: all: venv/bin/pytest -n 2 -vvs @@ -51,7 +59,7 @@ jobs: parameters: job_name: win2019_cpython image_name: windows-2019 - python_versions: ['3.7', '3.8', '3.9', '3.10', '3.11'] + python_versions: ['3.8', '3.9', '3.10', '3.11', '3.12'] test_suites: all: venv\Scripts\pytest -n 2 -vvs @@ -59,6 +67,6 @@ jobs: parameters: job_name: win2022_cpython image_name: windows-2022 - python_versions: ['3.7', '3.8', '3.9', '3.10', '3.11'] + python_versions: ['3.8', '3.9', '3.10', '3.11', '3.12'] test_suites: all: venv\Scripts\pytest -n 2 -vvs From 124da3dcef0d95a6f6aa76ed849f47ada25b83e2 Mon Sep 17 00:00:00 2001 From: Ayan Sinha Mahapatra Date: Mon, 1 Jul 2024 15:11:21 +0530 Subject: [PATCH 03/78] Replace deprecated macos CI runners Replace macos-11 runners with macos-14 runners. Reference: https://github.com/actions/runner-images?tab=readme-ov-file#available-images Reference: https://github.com/nexB/skeleton/issues/89 Signed-off-by: Ayan Sinha Mahapatra --- azure-pipelines.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 373b78c..c2a3b52 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -25,24 +25,24 @@ jobs: - template: etc/ci/azure-posix.yml parameters: - job_name: macos11_cpython - image_name: macOS-11 + job_name: macos12_cpython + image_name: macOS-12 python_versions: ['3.8', '3.9', '3.10', '3.11', '3.12'] test_suites: all: venv/bin/pytest -n 2 -vvs - template: etc/ci/azure-posix.yml parameters: - job_name: macos12_cpython - image_name: macOS-12 + job_name: macos13_cpython + image_name: macOS-13 python_versions: ['3.8', '3.9', '3.10', '3.11', '3.12'] test_suites: all: venv/bin/pytest -n 2 -vvs - template: etc/ci/azure-posix.yml parameters: - job_name: macos13_cpython - image_name: macOS-13 + job_name: macos14_cpython_arm64 + image_name: macOS-14 python_versions: ['3.8', '3.9', '3.10', '3.11', '3.12'] test_suites: all: venv/bin/pytest -n 2 -vvs @@ -50,8 +50,8 @@ jobs: - template: etc/ci/azure-posix.yml parameters: job_name: macos14_cpython - image_name: macOS-14 - python_versions: ['3.8', '3.9', '3.10', '3.11', '3.12'] + image_name: macOS-14-large + python_versions: ['3.8', '3.8', '3.9', '3.10', '3.12'] test_suites: all: venv/bin/pytest -n 2 -vvs From be4e14d414cf4f7112b529dc71f7abccc9dcf24a Mon Sep 17 00:00:00 2001 From: Ayan Sinha Mahapatra Date: Mon, 1 Jul 2024 16:00:40 +0530 Subject: [PATCH 04/78] Update minimum required python version to 3.8 Signed-off-by: Ayan Sinha Mahapatra --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index bd0e58a..a8e20c5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -38,7 +38,7 @@ zip_safe = false setup_requires = setuptools_scm[toml] >= 4 -python_requires = >=3.7 +python_requires = >=3.8 install_requires = From 9c57f340d22d8891a5614a93553b20d75e2f3136 Mon Sep 17 00:00:00 2001 From: Chin Yeung Li Date: Tue, 20 Aug 2024 16:46:20 +0800 Subject: [PATCH 05/78] Update link references of ownership from nexB to aboutcode-org Signed-off-by: Chin Yeung Li --- Makefile | 4 +- NOTICE | 2 +- configure | 2 +- configure.bat | 2 +- docs/source/conf.py | 2 +- docs/source/contribute/contrib_doc.rst | 2 +- docs/source/skeleton-usage.rst | 2 +- etc/scripts/check_thirdparty.py | 5 +- etc/scripts/fetch_thirdparty.py | 19 ++++-- etc/scripts/gen_requirements.py | 2 +- etc/scripts/gen_requirements_dev.py | 2 +- etc/scripts/utils_dejacode.py | 11 ++-- etc/scripts/utils_requirements.py | 11 ++-- etc/scripts/utils_thirdparty.py | 89 +++++++++++++++++--------- setup.cfg | 2 +- tests/test_skeleton_codestyle.py | 2 +- 16 files changed, 100 insertions(+), 59 deletions(-) diff --git a/Makefile b/Makefile index cc36c35..94451b3 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. # @@ -35,7 +35,7 @@ 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 . + @${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 diff --git a/NOTICE b/NOTICE index 65936b2..cbdaef7 100644 --- a/NOTICE +++ b/NOTICE @@ -2,7 +2,7 @@ # Copyright (c) nexB Inc. and others. # SPDX-License-Identifier: Apache-2.0 # -# Visit https://aboutcode.org and https://github.com/nexB/ for support and download. +# Visit https://aboutcode.org and https://github.com/aboutcode-org/ for support and download. # ScanCode is a trademark of nexB Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/configure b/configure index 926a894..22d9288 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. # diff --git a/configure.bat b/configure.bat index 5e95b31..5b9a9d6 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. diff --git a/docs/source/conf.py b/docs/source/conf.py index 7771ff0..8c88fa2 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -43,7 +43,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 intersphinx_mapping = { "aboutcode": ("https://aboutcode.readthedocs.io/en/latest/", None), diff --git a/docs/source/contribute/contrib_doc.rst b/docs/source/contribute/contrib_doc.rst index 13882e1..5640db2 100644 --- a/docs/source/contribute/contrib_doc.rst +++ b/docs/source/contribute/contrib_doc.rst @@ -12,7 +12,7 @@ To get started, create or identify a working directory on your local machine. Open that directory and execute the following command in a terminal session:: - git clone https://github.com/nexB/skeleton.git + git clone https://github.com/aboutcode-org/skeleton.git That will create an ``/skeleton`` directory in your working directory. Now you can install the dependencies in a virtualenv:: diff --git a/docs/source/skeleton-usage.rst b/docs/source/skeleton-usage.rst index cde23dc..6cb4cc5 100644 --- a/docs/source/skeleton-usage.rst +++ b/docs/source/skeleton-usage.rst @@ -118,7 +118,7 @@ corrected. You can check to see if your corrections are valid by running: Once the wheels are collected and the ABOUT files are generated and correct, upload them to thirdparty.aboutcode.org/pypi by placing the wheels and ABOUT files from the thirdparty directory to the pypi directory at -https://github.com/nexB/thirdparty-packages +https://github.com/aboutcode-org/thirdparty-packages Usage after project initialization diff --git a/etc/scripts/check_thirdparty.py b/etc/scripts/check_thirdparty.py index b052f25..2daded9 100644 --- a/etc/scripts/check_thirdparty.py +++ b/etc/scripts/check_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 click @@ -17,7 +17,8 @@ @click.option( "-d", "--dest", - type=click.Path(exists=True, readable=True, path_type=str, file_okay=False), + type=click.Path(exists=True, readable=True, + path_type=str, file_okay=False), required=True, help="Path to the thirdparty directory to check.", ) diff --git a/etc/scripts/fetch_thirdparty.py b/etc/scripts/fetch_thirdparty.py index eedf05c..3f9ff52 100644 --- a/etc/scripts/fetch_thirdparty.py +++ b/etc/scripts/fetch_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. # @@ -55,7 +55,8 @@ "-d", "--dest", "dest_dir", - type=click.Path(exists=True, readable=True, path_type=str, file_okay=False), + type=click.Path(exists=True, readable=True, + path_type=str, file_okay=False), metavar="DIR", default=utils_thirdparty.THIRDPARTY_DIR, show_default=True, @@ -224,7 +225,8 @@ def fetch_thirdparty( environments = None if wheels: evts = itertools.product(python_versions, operating_systems) - environments = [utils_thirdparty.Environment.from_pyver_and_os(pyv, os) for pyv, os in evts] + environments = [utils_thirdparty.Environment.from_pyver_and_os( + pyv, os) for pyv, os in evts] # Collect PyPI repos repos = [] @@ -260,13 +262,14 @@ def fetch_thirdparty( repos=repos, ) if not fetched: - wheels_or_sdist_not_found[f"{name}=={version}"].append(environment) + wheels_or_sdist_not_found[f"{name}=={version}"].append( + environment) if TRACE: print(f" NOT FOUND") 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}") @@ -289,7 +292,8 @@ def fetch_thirdparty( sdist_missing = sdists and "sdist" in dists and not name 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 not name in sdist_only if wheels_missing: mia.append(f"Wheels missing: {nv} {dists}") @@ -299,7 +303,8 @@ def fetch_thirdparty( raise Exception(mia) print(f"==> 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.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 diff --git a/etc/scripts/gen_requirements.py b/etc/scripts/gen_requirements.py index 07e26f7..2b65ae8 100644 --- a/etc/scripts/gen_requirements.py +++ b/etc/scripts/gen_requirements.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 argparse diff --git a/etc/scripts/gen_requirements_dev.py b/etc/scripts/gen_requirements_dev.py index 12cc06d..5db1c48 100644 --- a/etc/scripts/gen_requirements_dev.py +++ b/etc/scripts/gen_requirements_dev.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 argparse diff --git a/etc/scripts/utils_dejacode.py b/etc/scripts/utils_dejacode.py index c42e6c9..652252d 100644 --- a/etc/scripts/utils_dejacode.py +++ b/etc/scripts/utils_dejacode.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 io @@ -33,7 +33,8 @@ def can_do_api_calls(): if not DEJACODE_API_KEY and DEJACODE_API_URL: - print("DejaCode DEJACODE_API_KEY and DEJACODE_API_URL not configured. Doing nothing") + print( + "DejaCode DEJACODE_API_KEY and DEJACODE_API_URL not configured. Doing nothing") return False else: return True @@ -68,7 +69,8 @@ def get_package_data(distribution): return results[0] elif len_results > 1: - print(f"More than 1 entry exists, review at: {DEJACODE_API_URL_PACKAGES}") + print( + f"More than 1 entry exists, review at: {DEJACODE_API_URL_PACKAGES}") else: print("Could not find package:", distribution.download_url) @@ -149,7 +151,8 @@ def find_latest_dejacode_package(distribution): # there was no exact match, find the latest version # TODO: consider the closest version rather than the latest # or the version that has the best data - with_versions = [(packaging_version.parse(p["version"]), p) for p in packages] + with_versions = [(packaging_version.parse(p["version"]), p) + for p in packages] with_versions = sorted(with_versions) latest_version, latest_package_version = sorted(with_versions)[-1] print( diff --git a/etc/scripts/utils_requirements.py b/etc/scripts/utils_requirements.py index 0fc25a3..1c50239 100644 --- a/etc/scripts/utils_requirements.py +++ b/etc/scripts/utils_requirements.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. # @@ -102,7 +102,8 @@ def lock_dev_requirements( all_req_nvs = get_required_name_versions(all_req_lines) dev_only_req_nvs = {n: v for n, v in all_req_nvs if n not in main_names} - new_reqs = "\n".join(f"{n}=={v}" for n, v in sorted(dev_only_req_nvs.items())) + new_reqs = "\n".join( + f"{n}=={v}" for n, v in sorted(dev_only_req_nvs.items())) with open(dev_requirements_file, "w") as fo: fo.write(new_reqs) @@ -113,10 +114,12 @@ def get_installed_reqs(site_packages_dir): as a text. """ if not os.path.exists(site_packages_dir): - raise Exception(f"site_packages directory: {site_packages_dir!r} does not exists") + raise Exception( + f"site_packages directory: {site_packages_dir!r} does not exists") # Also include these packages in the output with --all: wheel, distribute, # setuptools, pip - args = ["pip", "freeze", "--exclude-editable", "--all", "--path", site_packages_dir] + args = ["pip", "freeze", "--exclude-editable", + "--all", "--path", site_packages_dir] return subprocess.check_output(args, encoding="utf-8") diff --git a/etc/scripts/utils_thirdparty.py b/etc/scripts/utils_thirdparty.py index addf8e5..46dc728 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 @@ -245,9 +245,11 @@ def download_wheel(name, version, environment, dest_dir=THIRDPARTY_DIR, repos=tu package = repo.get_package_version(name=name, version=version) if not package: if TRACE_DEEP: - print(f" download_wheel: No package in {repo.index_url} for {name}=={version}") + print( + f" download_wheel: No package in {repo.index_url} for {name}=={version}") continue - supported_wheels = list(package.get_supported_wheels(environment=environment)) + supported_wheels = list( + package.get_supported_wheels(environment=environment)) if not supported_wheels: if TRACE_DEEP: print( @@ -291,7 +293,8 @@ def download_sdist(name, version, dest_dir=THIRDPARTY_DIR, repos=tuple()): if not package: if TRACE_DEEP: - print(f" download_sdist: No package in {repo.index_url} for {name}=={version}") + print( + f" download_sdist: No package in {repo.index_url} for {name}=={version}") continue sdist = package.sdist if not sdist: @@ -300,7 +303,8 @@ def download_sdist(name, version, dest_dir=THIRDPARTY_DIR, repos=tuple()): continue if TRACE_DEEP: - print(f" download_sdist: Getting sdist from index (or cache): {sdist.download_url}") + print( + f" download_sdist: Getting sdist from index (or cache): {sdist.download_url}") fetched_sdist_filename = package.sdist.download(dest_dir=dest_dir) if fetched_sdist_filename: @@ -533,7 +537,8 @@ def get_best_download_url(self, repos=tuple()): repos = DEFAULT_PYPI_REPOS for repo in repos: - package = repo.get_package_version(name=self.name, version=self.version) + package = repo.get_package_version( + name=self.name, version=self.version) if not package: if TRACE: print( @@ -772,7 +777,8 @@ def load_remote_about_data(self): if notice_text: about_data["notice_text"] = notice_text except RemoteNotFetchedException: - print(f"Failed to fetch NOTICE file: {self.notice_download_url}") + print( + f"Failed to fetch NOTICE file: {self.notice_download_url}") return self.load_about_data(about_data) def get_checksums(self, dest_dir=THIRDPARTY_DIR): @@ -821,9 +827,11 @@ def fetch_license_files(self, dest_dir=THIRDPARTY_DIR, use_cached_index=False): Fetch license files if missing in `dest_dir`. Return True if license files were fetched. """ - urls = LinksRepository.from_url(use_cached_index=use_cached_index).links + urls = LinksRepository.from_url( + use_cached_index=use_cached_index).links errors = [] - extra_lic_names = [l.get("file") for l in self.extra_data.get("licenses", {})] + extra_lic_names = [l.get("file") + for l in self.extra_data.get("licenses", {})] extra_lic_names += [self.extra_data.get("license_file")] extra_lic_names = [ln for ln in extra_lic_names if ln] lic_names = [f"{key}.LICENSE" for key in self.get_license_keys()] @@ -834,7 +842,8 @@ def fetch_license_files(self, dest_dir=THIRDPARTY_DIR, use_cached_index=False): try: # try remotely first - lic_url = get_license_link_for_filename(filename=filename, urls=urls) + lic_url = get_license_link_for_filename( + filename=filename, urls=urls) fetch_and_save( path_or_url=lic_url, @@ -911,7 +920,8 @@ def load_pkginfo_data(self, dest_dir=THIRDPARTY_DIR): c for c in classifiers if c.startswith("License") ] license_expression = get_license_expression(declared_license) - other_classifiers = [c for c in classifiers if not c.startswith("License")] + other_classifiers = [ + c for c in classifiers if not c.startswith("License")] holder = raw_data["Author"] holder_contact = raw_data["Author-email"] @@ -953,7 +963,8 @@ def update(self, data, overwrite=False, keep_extra=True): package_url = data.get("package_url") if package_url: purl_from_data = packageurl.PackageURL.from_string(package_url) - purl_from_self = packageurl.PackageURL.from_string(self.package_url) + purl_from_self = packageurl.PackageURL.from_string( + self.package_url) if purl_from_data != purl_from_self: print( f"Invalid dist update attempt, no same same purl with dist: " @@ -1003,7 +1014,8 @@ def get_license_link_for_filename(filename, urls): if not path_or_url: raise Exception(f"Missing link to file: {filename}") if not len(path_or_url) == 1: - raise Exception(f"Multiple links to file: {filename}: \n" + "\n".join(path_or_url)) + raise Exception( + f"Multiple links to file: {filename}: \n" + "\n".join(path_or_url)) return path_or_url[0] @@ -1397,7 +1409,8 @@ def packages_from_dir(cls, directory): """ base = os.path.abspath(directory) - paths = [os.path.join(base, f) for f in os.listdir(base) if f.endswith(EXTENSIONS)] + paths = [os.path.join(base, f) + for f in os.listdir(base) if f.endswith(EXTENSIONS)] if TRACE_ULTRA_DEEP: print("packages_from_dir: paths:", paths) @@ -1458,7 +1471,8 @@ def dists_from_paths_or_urls(cls, paths_or_urls): dists = [] if TRACE_ULTRA_DEEP: print(" ###paths_or_urls:", paths_or_urls) - installable = [f for f in paths_or_urls if f.endswith(EXTENSIONS_INSTALLABLE)] + installable = [f for f in paths_or_urls if f.endswith( + EXTENSIONS_INSTALLABLE)] for path_or_url in installable: try: dist = Distribution.from_path_or_url(path_or_url) @@ -1476,7 +1490,8 @@ def dists_from_paths_or_urls(cls, paths_or_urls): ) except InvalidDistributionFilename: if TRACE_DEEP: - print(f" Skipping invalid distribution from: {path_or_url}") + print( + f" Skipping invalid distribution from: {path_or_url}") continue return dists @@ -1525,7 +1540,8 @@ class Environment: implementation = attr.ib( type=str, default="cp", - metadata=dict(help="Python implementation supported by this environment."), + metadata=dict( + help="Python implementation supported by this environment."), repr=False, ) @@ -1539,7 +1555,8 @@ class Environment: platforms = attr.ib( type=list, default=attr.Factory(list), - metadata=dict(help="List of platform tags supported by this environment."), + metadata=dict( + help="List of platform tags supported by this environment."), repr=False, ) @@ -1623,7 +1640,8 @@ class PypiSimpleRepository: fetched_package_normalized_names = attr.ib( type=set, default=attr.Factory(set), - metadata=dict(help="A set of already fetched package normalized names."), + metadata=dict( + help="A set of already fetched package normalized names."), ) use_cached_index = attr.ib( @@ -1654,10 +1672,12 @@ def _get_package_versions_map(self, name): self.packages[normalized_name] = versions except RemoteNotFetchedException as e: if TRACE: - print(f"failed to fetch package name: {name} from: {self.index_url}:\n{e}") + print( + f"failed to fetch package name: {name} from: {self.index_url}:\n{e}") if not versions and TRACE: - print(f"WARNING: package {name} not found in repo: {self.index_url}") + print( + f"WARNING: package {name} not found in repo: {self.index_url}") return versions @@ -1842,7 +1862,8 @@ def get(self, path_or_url, as_text=True, force=False): if force or not os.path.exists(cached): if TRACE_DEEP: print(f" FILE CACHE MISS: {path_or_url}") - content = get_file_content(path_or_url=path_or_url, as_text=as_text) + content = get_file_content( + path_or_url=path_or_url, as_text=as_text) wmode = "w" if as_text else "wb" with open(cached, wmode) as fo: fo.write(content) @@ -1864,7 +1885,8 @@ def get_file_content(path_or_url, as_text=True): if path_or_url.startswith("https://"): if TRACE_DEEP: print(f"Fetching: {path_or_url}") - _headers, content = get_remote_file_content(url=path_or_url, as_text=as_text) + _headers, content = get_remote_file_content( + url=path_or_url, as_text=as_text) return content elif path_or_url.startswith("file://") or ( @@ -1930,7 +1952,8 @@ def get_remote_file_content( ) else: - raise RemoteNotFetchedException(f"Failed HTTP request from {url} with {status}") + raise RemoteNotFetchedException( + f"Failed HTTP request from {url} with {status}") if headers_only: return response.headers, None @@ -2021,7 +2044,8 @@ def get_other_dists(_package, _dist): # if has key data we may look to improve later, but we can move on if local_dist.has_key_metadata(): local_dist.save_about_and_notice_files(dest_dir=dest_dir) - local_dist.fetch_license_files(dest_dir=dest_dir, use_cached_index=use_cached_index) + local_dist.fetch_license_files( + dest_dir=dest_dir, use_cached_index=use_cached_index) continue # lets try to get from another dist of the same local package @@ -2033,7 +2057,8 @@ def get_other_dists(_package, _dist): # if has key data we may look to improve later, but we can move on if local_dist.has_key_metadata(): local_dist.save_about_and_notice_files(dest_dir=dest_dir) - local_dist.fetch_license_files(dest_dir=dest_dir, use_cached_index=use_cached_index) + local_dist.fetch_license_files( + dest_dir=dest_dir, use_cached_index=use_cached_index) continue # try to get another version of the same package that is not our version @@ -2044,7 +2069,8 @@ def get_other_dists(_package, _dist): ] other_local_version = other_local_packages and other_local_packages[-1] if other_local_version: - latest_local_dists = list(other_local_version.get_distributions()) + latest_local_dists = list( + other_local_version.get_distributions()) for latest_local_dist in latest_local_dists: latest_local_dist.load_about_data(dest_dir=dest_dir) if not latest_local_dist.has_key_metadata(): @@ -2070,7 +2096,8 @@ def get_other_dists(_package, _dist): # if has key data we may look to improve later, but we can move on if local_dist.has_key_metadata(): local_dist.save_about_and_notice_files(dest_dir=dest_dir) - local_dist.fetch_license_files(dest_dir=dest_dir, use_cached_index=use_cached_index) + local_dist.fetch_license_files( + dest_dir=dest_dir, use_cached_index=use_cached_index) continue # try to get a latest version of the same package that is not our version @@ -2111,7 +2138,8 @@ def get_other_dists(_package, _dist): # if local_dist.has_key_metadata() or not local_dist.has_key_metadata(): local_dist.save_about_and_notice_files(dest_dir) - lic_errs = local_dist.fetch_license_files(dest_dir, use_cached_index=use_cached_index) + lic_errs = local_dist.fetch_license_files( + dest_dir, use_cached_index=use_cached_index) if not local_dist.has_key_metadata(): print(f"Unable to add essential ABOUT data for: {local_dist}") @@ -2259,7 +2287,8 @@ def find_problems( for dist in package.get_distributions(): dist.load_about_data(dest_dir=dest_dir) - abpth = os.path.abspath(os.path.join(dest_dir, dist.about_filename)) + abpth = os.path.abspath(os.path.join( + dest_dir, dist.about_filename)) if not dist.has_key_metadata(): print(f" Missing key ABOUT data in file://{abpth}") if "classifiers" in dist.extra_data: diff --git a/setup.cfg b/setup.cfg index a8e20c5..ef7d369 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,7 +6,7 @@ license = Apache-2.0 description = skeleton long_description = file:README.rst long_description_content_type = text/x-rst -url = https://github.com/nexB/skeleton +url = https://github.com/aboutcode-org/skeleton author = nexB. Inc. and others author_email = info@aboutcode.org diff --git a/tests/test_skeleton_codestyle.py b/tests/test_skeleton_codestyle.py index 2eb6e55..b4ce8c1 100644 --- a/tests/test_skeleton_codestyle.py +++ b/tests/test_skeleton_codestyle.py @@ -3,7 +3,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. # From a92905297acf39ecd820bfb133f8670c39b40c97 Mon Sep 17 00:00:00 2001 From: Ayan Sinha Mahapatra Date: Fri, 17 Jan 2025 20:07:25 +0100 Subject: [PATCH 06/78] Drop deprecated macos-12 runner --- azure-pipelines.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c2a3b52..39601e6 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -23,14 +23,6 @@ jobs: 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.8', '3.9', '3.10', '3.11', '3.12'] - test_suites: - all: venv/bin/pytest -n 2 -vvs - - template: etc/ci/azure-posix.yml parameters: job_name: macos13_cpython From 4af4fce3cc57d001c6c26f77d477cd44cef2ffef Mon Sep 17 00:00:00 2001 From: Ayan Sinha Mahapatra Date: Sat, 15 Feb 2025 00:09:49 +0530 Subject: [PATCH 07/78] Update CI/Actions runners Signed-off-by: Ayan Sinha Mahapatra --- .github/workflows/docs-ci.yml | 8 ++++---- .github/workflows/pypi-release.yml | 18 +++++++++--------- azure-pipelines.yml | 22 +++++++++++----------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/workflows/docs-ci.yml b/.github/workflows/docs-ci.yml index 8c2abfe..621de4b 100644 --- a/.github/workflows/docs-ci.yml +++ b/.github/workflows/docs-ci.yml @@ -4,19 +4,19 @@ on: [push, pull_request] jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: max-parallel: 4 matrix: - python-version: [3.9] + python-version: [3.12] 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 }} diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index d2206c8..a66c9c8 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -21,14 +21,14 @@ on: jobs: build-pypi-distribs: name: Build and publish library to PyPI - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - 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 @@ -37,7 +37,7 @@ jobs: run: python -m build --sdist --wheel --outdir dist/ - name: Upload built archives - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: pypi_archives path: dist/* @@ -47,17 +47,17 @@ jobs: name: Create GH release needs: - build-pypi-distribs - runs-on: ubuntu-22.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 +67,11 @@ jobs: name: Create PyPI release needs: - create-gh-release - runs-on: ubuntu-22.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/azure-pipelines.yml b/azure-pipelines.yml index 39601e6..a220f2b 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -9,17 +9,17 @@ jobs: - template: etc/ci/azure-posix.yml parameters: - job_name: ubuntu20_cpython - image_name: ubuntu-20.04 - python_versions: ['3.8', '3.9', '3.10', '3.11', '3.12'] + 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: ubuntu22_cpython - image_name: ubuntu-22.04 - python_versions: ['3.8', '3.9', '3.10', '3.11', '3.12'] + 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 @@ -27,7 +27,7 @@ jobs: parameters: job_name: macos13_cpython image_name: macOS-13 - python_versions: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] test_suites: all: venv/bin/pytest -n 2 -vvs @@ -35,7 +35,7 @@ jobs: parameters: job_name: macos14_cpython_arm64 image_name: macOS-14 - python_versions: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] test_suites: all: venv/bin/pytest -n 2 -vvs @@ -43,7 +43,7 @@ jobs: parameters: job_name: macos14_cpython image_name: macOS-14-large - python_versions: ['3.8', '3.8', '3.9', '3.10', '3.12'] + python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] test_suites: all: venv/bin/pytest -n 2 -vvs @@ -51,7 +51,7 @@ jobs: parameters: job_name: win2019_cpython image_name: windows-2019 - python_versions: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] test_suites: all: venv\Scripts\pytest -n 2 -vvs @@ -59,6 +59,6 @@ jobs: parameters: job_name: win2022_cpython image_name: windows-2022 - python_versions: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] test_suites: all: venv\Scripts\pytest -n 2 -vvs From 320ec21daa249ceae0c07787f9e52134b3ad06ab Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Thu, 27 Mar 2025 14:54:31 -0700 Subject: [PATCH 08/78] Replace black and isort with ruff * Use ruff config and Make commands from scancode.io Signed-off-by: Jono Yang --- Makefile | 27 ++++++++++++--------------- pyproject.toml | 37 +++++++++++++++++++++++++++++++++++++ setup.cfg | 3 +-- 3 files changed, 50 insertions(+), 17 deletions(-) diff --git a/Makefile b/Makefile index 94451b3..1738b20 100644 --- a/Makefile +++ b/Makefile @@ -17,27 +17,24 @@ 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 - -black: - @echo "-> Apply black code formatter" - ${VENV}/bin/black -l 100 src tests setup.py - doc8: @echo "-> Run doc8 validation" @${ACTIVATE} doc8 --max-line-length 100 --ignore-path docs/_build/ --quiet docs/ -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" diff --git a/pyproject.toml b/pyproject.toml index cde7907..01e60fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,3 +50,40 @@ addopts = [ "--strict-markers", "--doctest-modules" ] + +[tool.ruff] +line-length = 88 +extend-exclude = [] +target-version = "py310" + +[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", "D203", "D205", "D212", "D400", "D415"] + +[tool.ruff.lint.isort] +force-single-line = true +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 diff --git a/setup.cfg b/setup.cfg index ef7d369..aaec643 100644 --- a/setup.cfg +++ b/setup.cfg @@ -54,8 +54,7 @@ testing = aboutcode-toolkit >= 7.0.2 pycodestyle >= 2.8.0 twine - black - isort + ruff docs = Sphinx>=5.0.2 From d4e29c36c21ab81797604911cdeaea83d80e8088 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 00:46:06 +0100 Subject: [PATCH 09/78] Use org standard 100 line length Signed-off-by: Philippe Ombredanne --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 01e60fc..cea91bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ addopts = [ ] [tool.ruff] -line-length = 88 +line-length = 100 extend-exclude = [] target-version = "py310" From 6c028f7219ae876ea62074ae435e574525e205d6 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 08:40:28 +0100 Subject: [PATCH 10/78] Lint all common code directories Signed-off-by: Philippe Ombredanne --- pyproject.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index cea91bd..9e62736 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,14 @@ addopts = [ line-length = 100 extend-exclude = [] target-version = "py310" +include = [ + "pyproject.toml", + "src/**/*.py", + "etc/**/*.py", + "test/**/*.py", + "doc/**/*", + "*.py" +] [tool.ruff.lint] # Rules: https://docs.astral.sh/ruff/rules/ From 233f3edabfbab390029fb9f1842bf43766b04583 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 09:07:47 +0100 Subject: [PATCH 11/78] Remove unused targets Signed-off-by: Philippe Ombredanne --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1738b20..930e801 100644 --- a/Makefile +++ b/Makefile @@ -48,4 +48,4 @@ docs: rm -rf docs/_build/ @${ACTIVATE} sphinx-build docs/ docs/_build/ -.PHONY: conf dev check valid black isort clean test docs +.PHONY: conf dev check valid clean test docs From 55545bf7a1a8f119a560c7f548ce5a460f39f37d Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 11:03:05 +0100 Subject: [PATCH 12/78] Improve import sorting Signed-off-by: Philippe Ombredanne --- etc/scripts/check_thirdparty.py | 1 - etc/scripts/fetch_thirdparty.py | 2 +- etc/scripts/test_utils_pip_compatibility_tags.py | 3 +-- etc/scripts/utils_dejacode.py | 1 - etc/scripts/utils_pip_compatibility_tags.py | 14 ++++++-------- etc/scripts/utils_thirdparty.py | 3 +-- pyproject.toml | 7 ++++++- 7 files changed, 15 insertions(+), 16 deletions(-) diff --git a/etc/scripts/check_thirdparty.py b/etc/scripts/check_thirdparty.py index 2daded9..62dbb14 100644 --- a/etc/scripts/check_thirdparty.py +++ b/etc/scripts/check_thirdparty.py @@ -12,7 +12,6 @@ import utils_thirdparty - @click.command() @click.option( "-d", diff --git a/etc/scripts/fetch_thirdparty.py b/etc/scripts/fetch_thirdparty.py index 3f9ff52..30d376c 100644 --- a/etc/scripts/fetch_thirdparty.py +++ b/etc/scripts/fetch_thirdparty.py @@ -16,8 +16,8 @@ import click -import utils_thirdparty import utils_requirements +import utils_thirdparty TRACE = False TRACE_DEEP = False diff --git a/etc/scripts/test_utils_pip_compatibility_tags.py b/etc/scripts/test_utils_pip_compatibility_tags.py index 98187c5..a33b8b3 100644 --- a/etc/scripts/test_utils_pip_compatibility_tags.py +++ b/etc/scripts/test_utils_pip_compatibility_tags.py @@ -25,14 +25,13 @@ 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 import utils_pip_compatibility_tags - @pytest.mark.parametrize( "version_info, expected", [ diff --git a/etc/scripts/utils_dejacode.py b/etc/scripts/utils_dejacode.py index 652252d..c71543f 100644 --- a/etc/scripts/utils_dejacode.py +++ b/etc/scripts/utils_dejacode.py @@ -14,7 +14,6 @@ import requests import saneyaml - from packvers import version as packaging_version """ diff --git a/etc/scripts/utils_pip_compatibility_tags.py b/etc/scripts/utils_pip_compatibility_tags.py index af42a0c..de0ac95 100644 --- a/etc/scripts/utils_pip_compatibility_tags.py +++ b/etc/scripts/utils_pip_compatibility_tags.py @@ -27,14 +27,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+)_(.+)") diff --git a/etc/scripts/utils_thirdparty.py b/etc/scripts/utils_thirdparty.py index 46dc728..b0295ec 100644 --- a/etc/scripts/utils_thirdparty.py +++ b/etc/scripts/utils_thirdparty.py @@ -25,14 +25,13 @@ import packageurl import requests import saneyaml +import utils_pip_compatibility_tags from commoncode import fileutils from commoncode.hash import multi_checksums from commoncode.text import python_safe_name from packvers import tags as packaging_tags from packvers import version as packaging_version -import utils_pip_compatibility_tags - """ Utilities to manage Python thirparty libraries source, binaries and metadata in local directories and remote repositories. diff --git a/pyproject.toml b/pyproject.toml index 9e62736..ba55770 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,10 +76,15 @@ select = [ "I", # isort "C9", # McCabe complexity ] -ignore = ["D1", "D203", "D205", "D212", "D400", "D415"] +ignore = ["D1", "D200", "D203", "D205", "D212", "D400", "D415"] [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", From 0b63e5073b6b1cdc0960abe35060ad0fdb67b665 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 21:35:16 +0100 Subject: [PATCH 13/78] Apply small code updates Signed-off-by: Philippe Ombredanne --- etc/scripts/utils_requirements.py | 20 ++++++++----- etc/scripts/utils_thirdparty.py | 48 +++++++++++++++---------------- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/etc/scripts/utils_requirements.py b/etc/scripts/utils_requirements.py index 1c50239..a9ac223 100644 --- a/etc/scripts/utils_requirements.py +++ b/etc/scripts/utils_requirements.py @@ -57,21 +57,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 @@ -120,7 +124,7 @@ def get_installed_reqs(site_packages_dir): # 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 = ( @@ -150,9 +154,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 not 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 b0295ec..6d5ffdc 100644 --- a/etc/scripts/utils_thirdparty.py +++ b/etc/scripts/utils_thirdparty.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # # Copyright (c) nexB Inc. and others. All rights reserved. # ScanCode is a trademark of nexB Inc. @@ -559,7 +558,8 @@ def download(self, dest_dir=THIRDPARTY_DIR): Download this distribution into `dest_dir` directory. Return the fetched filename. """ - assert self.filename + if not self.filename: + raise ValueError(f"self.filename has no value but is required: {self.filename!r}") if TRACE_DEEP: print( f"Fetching distribution of {self.name}=={self.version}:", @@ -829,10 +829,9 @@ def fetch_license_files(self, dest_dir=THIRDPARTY_DIR, use_cached_index=False): urls = LinksRepository.from_url( use_cached_index=use_cached_index).links errors = [] - extra_lic_names = [l.get("file") - for l in self.extra_data.get("licenses", {})] + extra_lic_names = [lic.get("file") for lic in self.extra_data.get("licenses", {})] extra_lic_names += [self.extra_data.get("license_file")] - extra_lic_names = [ln for ln in extra_lic_names if ln] + extra_lic_names = [eln for eln in extra_lic_names if eln] lic_names = [f"{key}.LICENSE" for key in self.get_license_keys()] for filename in lic_names + extra_lic_names: floc = os.path.join(dest_dir, filename) @@ -853,7 +852,7 @@ def fetch_license_files(self, dest_dir=THIRDPARTY_DIR, use_cached_index=False): if TRACE: print(f"Fetched license from remote: {lic_url}") - except: + except Exception: try: # try licensedb second lic_url = f"{LICENSEDB_API_URL}/{filename}" @@ -866,8 +865,9 @@ def fetch_license_files(self, dest_dir=THIRDPARTY_DIR, use_cached_index=False): if TRACE: print(f"Fetched license from licensedb: {lic_url}") - except: - msg = f'No text for license {filename} in expression "{self.license_expression}" from {self}' + except Exception: + msg = f"No text for license {filename} in expression " + f"{self.license_expression!r} from {self}" print(msg) errors.append(msg) @@ -1009,7 +1009,7 @@ def get_license_link_for_filename(filename, urls): exception if no link is found or if there are more than one link for that file name. """ - path_or_url = [l for l in urls if l.endswith(f"/{filename}")] + path_or_url = [url for url in urls if url.endswith(f"/{filename}")] if not path_or_url: raise Exception(f"Missing link to file: {filename}") if not len(path_or_url) == 1: @@ -1140,7 +1140,6 @@ def to_filename(self): @attr.attributes class Wheel(Distribution): - """ Represents a wheel file. @@ -1301,7 +1300,7 @@ def is_pure(self): def is_pure_wheel(filename): try: return Wheel.from_filename(filename).is_pure() - except: + except Exception: return False @@ -1489,8 +1488,7 @@ def dists_from_paths_or_urls(cls, paths_or_urls): ) except InvalidDistributionFilename: if TRACE_DEEP: - print( - f" Skipping invalid distribution from: {path_or_url}") + print(f" Skipping invalid distribution from: {path_or_url}") continue return dists @@ -1500,8 +1498,7 @@ def get_distributions(self): """ if self.sdist: yield self.sdist - for wheel in self.wheels: - yield wheel + yield from self.wheels def get_url_for_filename(self, filename): """ @@ -1632,7 +1629,8 @@ class PypiSimpleRepository: type=dict, default=attr.Factory(lambda: defaultdict(dict)), metadata=dict( - help="Mapping of {name: {version: PypiPackage, version: PypiPackage, etc} available in this repo" + help="Mapping of {name: {version: PypiPackage, version: PypiPackage, etc} " + "available in this repo" ), ) @@ -1647,7 +1645,8 @@ class PypiSimpleRepository: type=bool, default=False, metadata=dict( - help="If True, use any existing on-disk cached PyPI index files. Otherwise, fetch and cache." + help="If True, use any existing on-disk cached PyPI index files. " + "Otherwise, fetch and cache." ), ) @@ -1656,7 +1655,8 @@ def _get_package_versions_map(self, name): Return a mapping of all available PypiPackage version for this package name. The mapping may be empty. It is ordered by version from oldest to newest """ - assert name + if not name: + raise ValueError(f"name is required: {name!r}") normalized_name = NameVer.normalize_name(name) versions = self.packages[normalized_name] if not versions and normalized_name not in self.fetched_package_normalized_names: @@ -1713,7 +1713,7 @@ def fetch_links(self, normalized_name): ) links = collect_urls(text) # TODO: keep sha256 - links = [l.partition("#sha256=") for l in links] + links = [link.partition("#sha256=") for link in links] links = [url for url, _, _sha256 in links] return links @@ -1936,7 +1936,7 @@ def get_remote_file_content( # several redirects and that we can ignore content there. A HEAD request may # not get us this last header print(f" DOWNLOADING: {url}") - with requests.get(url, allow_redirects=True, stream=True, headers=headers) as response: + with requests.get(url, allow_redirects=True, stream=True, headers=headers) as response: # noqa: S113 status = response.status_code if status != requests.codes.ok: # NOQA if status == 429 and _delay < 20: @@ -2161,7 +2161,7 @@ def call(args, verbose=TRACE): """ if TRACE_DEEP: print("Calling:", " ".join(args)) - with subprocess.Popen( + with subprocess.Popen( # noqa: S603 args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8" ) as process: @@ -2227,7 +2227,7 @@ def download_wheels_with_pip( cli_args.extend(["--requirement", req_file]) if TRACE: - print(f"Downloading wheels using command:", " ".join(cli_args)) + print("Downloading wheels using command:", " ".join(cli_args)) existing = set(os.listdir(dest_dir)) error = False @@ -2260,7 +2260,7 @@ def download_wheels_with_pip( def check_about(dest_dir=THIRDPARTY_DIR): try: - subprocess.check_output(f"venv/bin/about check {dest_dir}".split()) + subprocess.check_output(f"venv/bin/about check {dest_dir}".split()) # noqa: S603 except subprocess.CalledProcessError as cpe: print() print("Invalid ABOUT files:") @@ -2312,5 +2312,5 @@ def get_license_expression(declared_licenses): return get_only_expression_from_extracted_license(declared_licenses) except ImportError: # Scancode is not installed, clean and join all the licenses - lics = [python_safe_name(l).lower() for l in declared_licenses] + lics = [python_safe_name(lic).lower() for lic in declared_licenses] return " AND ".join(lics).lower() From 092f545f5b87442ae22884cb4d5381883343a1c2 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 21:42:03 +0100 Subject: [PATCH 14/78] Format code Signed-off-by: Philippe Ombredanne --- etc/scripts/check_thirdparty.py | 3 +- etc/scripts/fetch_thirdparty.py | 26 ++++----- etc/scripts/gen_pypi_simple.py | 4 +- etc/scripts/utils_dejacode.py | 15 +++--- etc/scripts/utils_requirements.py | 9 ++-- etc/scripts/utils_thirdparty.py | 90 +++++++++++-------------------- 6 files changed, 50 insertions(+), 97 deletions(-) diff --git a/etc/scripts/check_thirdparty.py b/etc/scripts/check_thirdparty.py index 62dbb14..1aa4e28 100644 --- a/etc/scripts/check_thirdparty.py +++ b/etc/scripts/check_thirdparty.py @@ -16,8 +16,7 @@ @click.option( "-d", "--dest", - type=click.Path(exists=True, readable=True, - path_type=str, file_okay=False), + type=click.Path(exists=True, readable=True, path_type=str, file_okay=False), required=True, help="Path to the thirdparty directory to check.", ) diff --git a/etc/scripts/fetch_thirdparty.py b/etc/scripts/fetch_thirdparty.py index 30d376c..c224683 100644 --- a/etc/scripts/fetch_thirdparty.py +++ b/etc/scripts/fetch_thirdparty.py @@ -55,8 +55,7 @@ "-d", "--dest", "dest_dir", - type=click.Path(exists=True, readable=True, - path_type=str, file_okay=False), + type=click.Path(exists=True, readable=True, path_type=str, file_okay=False), metavar="DIR", default=utils_thirdparty.THIRDPARTY_DIR, show_default=True, @@ -121,7 +120,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", @@ -132,7 +131,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", @@ -143,7 +142,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( @@ -225,8 +224,7 @@ def fetch_thirdparty( environments = None if wheels: evts = itertools.product(python_versions, operating_systems) - environments = [utils_thirdparty.Environment.from_pyver_and_os( - pyv, os) for pyv, os in evts] + environments = [utils_thirdparty.Environment.from_pyver_and_os(pyv, os) for pyv, os in evts] # Collect PyPI repos repos = [] @@ -250,7 +248,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,14 +259,11 @@ def fetch_thirdparty( repos=repos, ) if not fetched: - wheels_or_sdist_not_found[f"{name}=={version}"].append( - environment) + wheels_or_sdist_not_found[f"{name}=={version}"].append(environment) if TRACE: print(f" 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}") @@ -292,8 +286,7 @@ def fetch_thirdparty( sdist_missing = sdists and "sdist" in dists and not name 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 not name in sdist_only if wheels_missing: mia.append(f"Wheels missing: {nv} {dists}") @@ -303,8 +296,7 @@ def fetch_thirdparty( raise Exception(mia) print(f"==> 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.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 diff --git a/etc/scripts/gen_pypi_simple.py b/etc/scripts/gen_pypi_simple.py index 214d90d..cfe68e6 100644 --- a/etc/scripts/gen_pypi_simple.py +++ b/etc/scripts/gen_pypi_simple.py @@ -69,7 +69,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: @@ -200,11 +199,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/utils_dejacode.py b/etc/scripts/utils_dejacode.py index c71543f..cd39cda 100644 --- a/etc/scripts/utils_dejacode.py +++ b/etc/scripts/utils_dejacode.py @@ -32,8 +32,7 @@ def can_do_api_calls(): if not DEJACODE_API_KEY and DEJACODE_API_URL: - print( - "DejaCode DEJACODE_API_KEY and DEJACODE_API_URL not configured. Doing nothing") + print("DejaCode DEJACODE_API_KEY and DEJACODE_API_URL not configured. Doing nothing") return False else: return True @@ -68,8 +67,7 @@ def get_package_data(distribution): return results[0] elif len_results > 1: - print( - f"More than 1 entry exists, review at: {DEJACODE_API_URL_PACKAGES}") + print(f"More than 1 entry exists, review at: {DEJACODE_API_URL_PACKAGES}") else: print("Could not find package:", distribution.download_url) @@ -150,12 +148,11 @@ def find_latest_dejacode_package(distribution): # there was no exact match, find the latest version # TODO: consider the closest version rather than the latest # or the version that has the best data - with_versions = [(packaging_version.parse(p["version"]), p) - for p in packages] + with_versions = [(packaging_version.parse(p["version"]), p) for p in packages] 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 @@ -181,7 +178,7 @@ def create_dejacode_package(distribution): } fields_to_carry_over = [ - "download_url" "type", + "download_urltype", "namespace", "name", "version", @@ -209,5 +206,5 @@ def create_dejacode_package(distribution): 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_requirements.py b/etc/scripts/utils_requirements.py index a9ac223..167bc9f 100644 --- a/etc/scripts/utils_requirements.py +++ b/etc/scripts/utils_requirements.py @@ -106,8 +106,7 @@ def lock_dev_requirements( all_req_nvs = get_required_name_versions(all_req_lines) dev_only_req_nvs = {n: v for n, v in all_req_nvs if n not in main_names} - new_reqs = "\n".join( - f"{n}=={v}" for n, v in sorted(dev_only_req_nvs.items())) + new_reqs = "\n".join(f"{n}=={v}" for n, v in sorted(dev_only_req_nvs.items())) with open(dev_requirements_file, "w") as fo: fo.write(new_reqs) @@ -118,12 +117,10 @@ def get_installed_reqs(site_packages_dir): as a text. """ if not os.path.exists(site_packages_dir): - raise Exception( - f"site_packages directory: {site_packages_dir!r} does not exists") + raise Exception(f"site_packages directory: {site_packages_dir!r} does not exists") # Also include these packages in the output with --all: wheel, distribute, # setuptools, pip - args = ["pip", "freeze", "--exclude-editable", - "--all", "--path", site_packages_dir] + args = ["pip", "freeze", "--exclude-editable", "--all", "--path", site_packages_dir] return subprocess.check_output(args, encoding="utf-8") # noqa: S603 diff --git a/etc/scripts/utils_thirdparty.py b/etc/scripts/utils_thirdparty.py index 6d5ffdc..4ea1bab 100644 --- a/etc/scripts/utils_thirdparty.py +++ b/etc/scripts/utils_thirdparty.py @@ -243,11 +243,9 @@ def download_wheel(name, version, environment, dest_dir=THIRDPARTY_DIR, repos=tu package = repo.get_package_version(name=name, version=version) if not package: if TRACE_DEEP: - print( - f" download_wheel: No package in {repo.index_url} for {name}=={version}") + print(f" download_wheel: No package in {repo.index_url} for {name}=={version}") continue - supported_wheels = list( - package.get_supported_wheels(environment=environment)) + supported_wheels = list(package.get_supported_wheels(environment=environment)) if not supported_wheels: if TRACE_DEEP: print( @@ -291,8 +289,7 @@ def download_sdist(name, version, dest_dir=THIRDPARTY_DIR, repos=tuple()): if not package: if TRACE_DEEP: - print( - f" download_sdist: No package in {repo.index_url} for {name}=={version}") + print(f" download_sdist: No package in {repo.index_url} for {name}=={version}") continue sdist = package.sdist if not sdist: @@ -301,8 +298,7 @@ def download_sdist(name, version, dest_dir=THIRDPARTY_DIR, repos=tuple()): continue if TRACE_DEEP: - print( - f" download_sdist: Getting sdist from index (or cache): {sdist.download_url}") + print(f" download_sdist: Getting sdist from index (or cache): {sdist.download_url}") fetched_sdist_filename = package.sdist.download(dest_dir=dest_dir) if fetched_sdist_filename: @@ -357,7 +353,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", @@ -535,8 +530,7 @@ def get_best_download_url(self, repos=tuple()): repos = DEFAULT_PYPI_REPOS for repo in repos: - package = repo.get_package_version( - name=self.name, version=self.version) + package = repo.get_package_version(name=self.name, version=self.version) if not package: if TRACE: print( @@ -776,8 +770,7 @@ def load_remote_about_data(self): if notice_text: about_data["notice_text"] = notice_text except RemoteNotFetchedException: - print( - f"Failed to fetch NOTICE file: {self.notice_download_url}") + print(f"Failed to fetch NOTICE file: {self.notice_download_url}") return self.load_about_data(about_data) def get_checksums(self, dest_dir=THIRDPARTY_DIR): @@ -826,8 +819,7 @@ def fetch_license_files(self, dest_dir=THIRDPARTY_DIR, use_cached_index=False): Fetch license files if missing in `dest_dir`. Return True if license files were fetched. """ - urls = LinksRepository.from_url( - use_cached_index=use_cached_index).links + urls = LinksRepository.from_url(use_cached_index=use_cached_index).links errors = [] extra_lic_names = [lic.get("file") for lic in self.extra_data.get("licenses", {})] extra_lic_names += [self.extra_data.get("license_file")] @@ -840,8 +832,7 @@ def fetch_license_files(self, dest_dir=THIRDPARTY_DIR, use_cached_index=False): try: # try remotely first - lic_url = get_license_link_for_filename( - filename=filename, urls=urls) + lic_url = get_license_link_for_filename(filename=filename, urls=urls) fetch_and_save( path_or_url=lic_url, @@ -919,8 +910,7 @@ def load_pkginfo_data(self, dest_dir=THIRDPARTY_DIR): c for c in classifiers if c.startswith("License") ] license_expression = get_license_expression(declared_license) - other_classifiers = [ - c for c in classifiers if not c.startswith("License")] + other_classifiers = [c for c in classifiers if not c.startswith("License")] holder = raw_data["Author"] holder_contact = raw_data["Author-email"] @@ -962,8 +952,7 @@ def update(self, data, overwrite=False, keep_extra=True): package_url = data.get("package_url") if package_url: purl_from_data = packageurl.PackageURL.from_string(package_url) - purl_from_self = packageurl.PackageURL.from_string( - self.package_url) + purl_from_self = packageurl.PackageURL.from_string(self.package_url) if purl_from_data != purl_from_self: print( f"Invalid dist update attempt, no same same purl with dist: " @@ -1013,8 +1002,7 @@ def get_license_link_for_filename(filename, urls): if not path_or_url: raise Exception(f"Missing link to file: {filename}") if not len(path_or_url) == 1: - raise Exception( - f"Multiple links to file: {filename}: \n" + "\n".join(path_or_url)) + raise Exception(f"Multiple links to file: {filename}: \n" + "\n".join(path_or_url)) return path_or_url[0] @@ -1102,7 +1090,6 @@ def get_sdist_name_ver_ext(filename): @attr.attributes class Sdist(Distribution): - extension = attr.ib( repr=False, type=str, @@ -1407,8 +1394,7 @@ def packages_from_dir(cls, directory): """ base = os.path.abspath(directory) - paths = [os.path.join(base, f) - for f in os.listdir(base) if f.endswith(EXTENSIONS)] + paths = [os.path.join(base, f) for f in os.listdir(base) if f.endswith(EXTENSIONS)] if TRACE_ULTRA_DEEP: print("packages_from_dir: paths:", paths) @@ -1469,8 +1455,7 @@ def dists_from_paths_or_urls(cls, paths_or_urls): dists = [] if TRACE_ULTRA_DEEP: print(" ###paths_or_urls:", paths_or_urls) - installable = [f for f in paths_or_urls if f.endswith( - EXTENSIONS_INSTALLABLE)] + installable = [f for f in paths_or_urls if f.endswith(EXTENSIONS_INSTALLABLE)] for path_or_url in installable: try: dist = Distribution.from_path_or_url(path_or_url) @@ -1536,8 +1521,7 @@ class Environment: implementation = attr.ib( type=str, default="cp", - metadata=dict( - help="Python implementation supported by this environment."), + metadata=dict(help="Python implementation supported by this environment."), repr=False, ) @@ -1551,8 +1535,7 @@ class Environment: platforms = attr.ib( type=list, default=attr.Factory(list), - metadata=dict( - help="List of platform tags supported by this environment."), + metadata=dict(help="List of platform tags supported by this environment."), repr=False, ) @@ -1637,8 +1620,7 @@ class PypiSimpleRepository: fetched_package_normalized_names = attr.ib( type=set, default=attr.Factory(set), - metadata=dict( - help="A set of already fetched package normalized names."), + metadata=dict(help="A set of already fetched package normalized names."), ) use_cached_index = attr.ib( @@ -1671,12 +1653,10 @@ def _get_package_versions_map(self, name): self.packages[normalized_name] = versions except RemoteNotFetchedException as e: if TRACE: - print( - f"failed to fetch package name: {name} from: {self.index_url}:\n{e}") + print(f"failed to fetch package name: {name} from: {self.index_url}:\n{e}") if not versions and TRACE: - print( - f"WARNING: package {name} not found in repo: {self.index_url}") + print(f"WARNING: package {name} not found in repo: {self.index_url}") return versions @@ -1861,8 +1841,7 @@ def get(self, path_or_url, as_text=True, force=False): if force or not os.path.exists(cached): if TRACE_DEEP: print(f" FILE CACHE MISS: {path_or_url}") - content = get_file_content( - path_or_url=path_or_url, as_text=as_text) + content = get_file_content(path_or_url=path_or_url, as_text=as_text) wmode = "w" if as_text else "wb" with open(cached, wmode) as fo: fo.write(content) @@ -1884,8 +1863,7 @@ def get_file_content(path_or_url, as_text=True): if path_or_url.startswith("https://"): if TRACE_DEEP: print(f"Fetching: {path_or_url}") - _headers, content = get_remote_file_content( - url=path_or_url, as_text=as_text) + _headers, content = get_remote_file_content(url=path_or_url, as_text=as_text) return content elif path_or_url.startswith("file://") or ( @@ -1936,7 +1914,7 @@ def get_remote_file_content( # several redirects and that we can ignore content there. A HEAD request may # not get us this last header print(f" DOWNLOADING: {url}") - with requests.get(url, allow_redirects=True, stream=True, headers=headers) as response: # noqa: S113 + with requests.get(url, allow_redirects=True, stream=True, headers=headers) as response: # noqa: S113 status = response.status_code if status != requests.codes.ok: # NOQA if status == 429 and _delay < 20: @@ -1951,8 +1929,7 @@ def get_remote_file_content( ) else: - raise RemoteNotFetchedException( - f"Failed HTTP request from {url} with {status}") + raise RemoteNotFetchedException(f"Failed HTTP request from {url} with {status}") if headers_only: return response.headers, None @@ -2043,8 +2020,7 @@ def get_other_dists(_package, _dist): # if has key data we may look to improve later, but we can move on if local_dist.has_key_metadata(): local_dist.save_about_and_notice_files(dest_dir=dest_dir) - local_dist.fetch_license_files( - dest_dir=dest_dir, use_cached_index=use_cached_index) + local_dist.fetch_license_files(dest_dir=dest_dir, use_cached_index=use_cached_index) continue # lets try to get from another dist of the same local package @@ -2056,8 +2032,7 @@ def get_other_dists(_package, _dist): # if has key data we may look to improve later, but we can move on if local_dist.has_key_metadata(): local_dist.save_about_and_notice_files(dest_dir=dest_dir) - local_dist.fetch_license_files( - dest_dir=dest_dir, use_cached_index=use_cached_index) + local_dist.fetch_license_files(dest_dir=dest_dir, use_cached_index=use_cached_index) continue # try to get another version of the same package that is not our version @@ -2068,8 +2043,7 @@ def get_other_dists(_package, _dist): ] other_local_version = other_local_packages and other_local_packages[-1] if other_local_version: - latest_local_dists = list( - other_local_version.get_distributions()) + latest_local_dists = list(other_local_version.get_distributions()) for latest_local_dist in latest_local_dists: latest_local_dist.load_about_data(dest_dir=dest_dir) if not latest_local_dist.has_key_metadata(): @@ -2095,8 +2069,7 @@ def get_other_dists(_package, _dist): # if has key data we may look to improve later, but we can move on if local_dist.has_key_metadata(): local_dist.save_about_and_notice_files(dest_dir=dest_dir) - local_dist.fetch_license_files( - dest_dir=dest_dir, use_cached_index=use_cached_index) + local_dist.fetch_license_files(dest_dir=dest_dir, use_cached_index=use_cached_index) continue # try to get a latest version of the same package that is not our version @@ -2137,8 +2110,7 @@ def get_other_dists(_package, _dist): # if local_dist.has_key_metadata() or not local_dist.has_key_metadata(): local_dist.save_about_and_notice_files(dest_dir) - lic_errs = local_dist.fetch_license_files( - dest_dir, use_cached_index=use_cached_index) + lic_errs = local_dist.fetch_license_files(dest_dir, use_cached_index=use_cached_index) if not local_dist.has_key_metadata(): print(f"Unable to add essential ABOUT data for: {local_dist}") @@ -2161,10 +2133,9 @@ def call(args, verbose=TRACE): """ if TRACE_DEEP: print("Calling:", " ".join(args)) - with subprocess.Popen( # noqa: S603 + with subprocess.Popen( # noqa: S603 args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8" ) as process: - stdouts = [] while True: line = process.stdout.readline() @@ -2260,7 +2231,7 @@ def download_wheels_with_pip( def check_about(dest_dir=THIRDPARTY_DIR): try: - subprocess.check_output(f"venv/bin/about check {dest_dir}".split()) # noqa: S603 + subprocess.check_output(f"venv/bin/about check {dest_dir}".split()) # noqa: S603 except subprocess.CalledProcessError as cpe: print() print("Invalid ABOUT files:") @@ -2286,8 +2257,7 @@ def find_problems( for dist in package.get_distributions(): dist.load_about_data(dest_dir=dest_dir) - abpth = os.path.abspath(os.path.join( - dest_dir, dist.about_filename)) + abpth = os.path.abspath(os.path.join(dest_dir, dist.about_filename)) if not dist.has_key_metadata(): print(f" Missing key ABOUT data in file://{abpth}") if "classifiers" in dist.extra_data: From d05665ad44a50b71f66b974ad24c81f7443e8180 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 22:02:19 +0100 Subject: [PATCH 15/78] Apply cosmetic refactorings Signed-off-by: Philippe Ombredanne --- docs/source/conf.py | 3 ++- etc/scripts/check_thirdparty.py | 4 +--- etc/scripts/fetch_thirdparty.py | 17 ++++++++--------- etc/scripts/gen_pypi_simple.py | 15 +++++++-------- etc/scripts/gen_requirements.py | 4 ++-- etc/scripts/gen_requirements_dev.py | 4 ++-- .../test_utils_pip_compatibility_tags.py | 9 +++++---- etc/scripts/utils_dejacode.py | 9 +++++---- etc/scripts/utils_pip_compatibility_tags.py | 8 +++++--- etc/scripts/utils_requirements.py | 3 +-- etc/scripts/utils_thirdparty.py | 3 ++- 11 files changed, 40 insertions(+), 39 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 8c88fa2..8aad829 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -94,7 +94,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/scripts/check_thirdparty.py b/etc/scripts/check_thirdparty.py index 1aa4e28..bb8347a 100644 --- a/etc/scripts/check_thirdparty.py +++ b/etc/scripts/check_thirdparty.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # # Copyright (c) nexB Inc. and others. All rights reserved. # ScanCode is a trademark of nexB Inc. @@ -41,8 +40,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 c224683..76a19a6 100644 --- a/etc/scripts/fetch_thirdparty.py +++ b/etc/scripts/fetch_thirdparty.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # # Copyright (c) nexB Inc. and others. All rights reserved. # ScanCode is a trademark of nexB Inc. @@ -10,7 +9,6 @@ # import itertools -import os import sys from collections import defaultdict @@ -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", @@ -261,7 +260,7 @@ 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 TRACE: @@ -276,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}") @@ -295,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 cfe68e6..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. @@ -132,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 @@ -177,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) diff --git a/etc/scripts/gen_requirements.py b/etc/scripts/gen_requirements.py index 2b65ae8..1b87944 100644 --- a/etc/scripts/gen_requirements.py +++ b/etc/scripts/gen_requirements.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # # Copyright (c) nexB Inc. and others. All rights reserved. # ScanCode is a trademark of nexB Inc. @@ -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 5db1c48..8548205 100644 --- a/etc/scripts/gen_requirements_dev.py +++ b/etc/scripts/gen_requirements_dev.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # # Copyright (c) nexB Inc. and others. All rights reserved. # ScanCode is a trademark of nexB Inc. @@ -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 a33b8b3..de4b706 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 @@ -50,7 +51,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. @@ -81,7 +82,7 @@ def test_no_hyphen_tag(self): assert "-" not in tag.platform -class TestManylinux2010Tags(object): +class TestManylinux2010Tags: @pytest.mark.parametrize( "manylinux2010,manylinux1", [ @@ -104,7 +105,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/utils_dejacode.py b/etc/scripts/utils_dejacode.py index cd39cda..b6bff51 100644 --- a/etc/scripts/utils_dejacode.py +++ b/etc/scripts/utils_dejacode.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # # Copyright (c) nexB Inc. and others. All rights reserved. # ScanCode is a trademark of nexB Inc. @@ -25,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", } @@ -50,6 +49,7 @@ def fetch_dejacode_packages(params): DEJACODE_API_URL_PACKAGES, params=params, headers=DEJACODE_API_HEADERS, + timeout=10, ) return response.json()["results"] @@ -93,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) @@ -113,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: @@ -201,6 +201,7 @@ 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: diff --git a/etc/scripts/utils_pip_compatibility_tags.py b/etc/scripts/utils_pip_compatibility_tags.py index de0ac95..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 @@ -130,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( @@ -140,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 167bc9f..b9b2c0e 100644 --- a/etc/scripts/utils_requirements.py +++ b/etc/scripts/utils_requirements.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # # Copyright (c) nexB Inc. and others. All rights reserved. # ScanCode is a trademark of nexB Inc. @@ -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) diff --git a/etc/scripts/utils_thirdparty.py b/etc/scripts/utils_thirdparty.py index 4ea1bab..aafc1d6 100644 --- a/etc/scripts/utils_thirdparty.py +++ b/etc/scripts/utils_thirdparty.py @@ -91,7 +91,8 @@ - parse requirement file - create a TODO queue of requirements to process -- done: create an empty map of processed binary requirements as {package name: (list of versions/tags} +- done: create an empty map of processed binary requirements as + {package name: (list of versions/tags} - while we have package reqs in TODO queue, process one requirement: From 63bcbf507e8a25f22853d56605c107e47c3673cc Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 22:05:23 +0100 Subject: [PATCH 16/78] Reformat test code Signed-off-by: Philippe Ombredanne --- .gitignore | 1 + pyproject.toml | 19 +++++++++++-------- tests/test_skeleton_codestyle.py | 25 ++++++++++++++++--------- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index 2d48196..8a93c94 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,4 @@ tcl # Ignore Jupyter Notebook related temp files .ipynb_checkpoints/ +/.ruff_cache/ diff --git a/pyproject.toml b/pyproject.toml index ba55770..a872ab3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,16 +67,17 @@ include = [ [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 +# "E", # pycodestyle +# "W", # pycodestyle warnings +# "D", # pydocstyle +# "F", # Pyflakes +# "UP", # pyupgrade +# "S", # flake8-bandit "I", # isort - "C9", # McCabe complexity +# "C9", # McCabe complexity ] -ignore = ["D1", "D200", "D203", "D205", "D212", "D400", "D415"] +ignore = ["D1", "D200", "D202", "D203", "D205", "D212", "D400", "D415"] + [tool.ruff.lint.isort] force-single-line = true @@ -100,3 +101,5 @@ max-complexity = 10 [tool.ruff.lint.per-file-ignores] # Place paths of files to be ignored by ruff here +"tests/*" = ["S101"] +"test_*.py" = ["S101"] diff --git a/tests/test_skeleton_codestyle.py b/tests/test_skeleton_codestyle.py index b4ce8c1..8cd85c9 100644 --- a/tests/test_skeleton_codestyle.py +++ b/tests/test_skeleton_codestyle.py @@ -7,30 +7,37 @@ # See https://aboutcode.org for more information about nexB OSS projects. # +import configparser import subprocess import unittest -import configparser - class BaseTests(unittest.TestCase): def test_skeleton_codestyle(self): - """ - This test shouldn't run in proliferated repositories. - """ + # This test shouldn't run in proliferated repositories. + + # TODO: update with switch to pyproject.toml 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" + commands = [ + ["venv/bin/ruff", "--check"], + ["venv/bin/ruff", "format", "--check"], + ] + command = None try: - subprocess.check_output(args.split()) + for command in commands: + subprocess.check_output(command) # noqa: S603 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", + f"Code style and linting command check failed: {' '.join(command)!r}.\n" + "You can check and format the code using:\n" + " make valid\n", + "OR:\n ruff format\n", + " ruff check --fix\n", e.output, ) from e From 9d1393a85303bf8cf92c9a25aa5cc50bdfd080d1 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 22:08:25 +0100 Subject: [PATCH 17/78] Format code Signed-off-by: Philippe Ombredanne --- etc/scripts/check_thirdparty.py | 1 + etc/scripts/test_utils_pip_compatibility_tags.py | 1 + tests/test_skeleton_codestyle.py | 1 + 3 files changed, 3 insertions(+) diff --git a/etc/scripts/check_thirdparty.py b/etc/scripts/check_thirdparty.py index bb8347a..65ae595 100644 --- a/etc/scripts/check_thirdparty.py +++ b/etc/scripts/check_thirdparty.py @@ -11,6 +11,7 @@ import utils_thirdparty + @click.command() @click.option( "-d", diff --git a/etc/scripts/test_utils_pip_compatibility_tags.py b/etc/scripts/test_utils_pip_compatibility_tags.py index de4b706..0e9c360 100644 --- a/etc/scripts/test_utils_pip_compatibility_tags.py +++ b/etc/scripts/test_utils_pip_compatibility_tags.py @@ -33,6 +33,7 @@ import utils_pip_compatibility_tags + @pytest.mark.parametrize( "version_info, expected", [ diff --git a/tests/test_skeleton_codestyle.py b/tests/test_skeleton_codestyle.py index 8cd85c9..7135ac0 100644 --- a/tests/test_skeleton_codestyle.py +++ b/tests/test_skeleton_codestyle.py @@ -11,6 +11,7 @@ import subprocess import unittest + class BaseTests(unittest.TestCase): def test_skeleton_codestyle(self): # This test shouldn't run in proliferated repositories. From f10b783b6b6fe33032a7862352ed532294efdf14 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 22:10:45 +0100 Subject: [PATCH 18/78] Refine ruff configuration Signed-off-by: Philippe Ombredanne --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a872ab3..0f8bd58 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,11 +72,11 @@ select = [ # "D", # pydocstyle # "F", # Pyflakes # "UP", # pyupgrade -# "S", # flake8-bandit + "S", # flake8-bandit "I", # isort # "C9", # McCabe complexity ] -ignore = ["D1", "D200", "D202", "D203", "D205", "D212", "D400", "D415"] +ignore = ["D1", "D200", "D202", "D203", "D205", "D212", "D400", "D415", "I001"] [tool.ruff.lint.isort] From 1d6c8f3bb8755aa7c9d2804240c01b0161417328 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 22:54:01 +0100 Subject: [PATCH 19/78] Format doc Signed-off-by: Philippe Ombredanne --- AUTHORS.rst | 2 +- README.rst | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 51a19cc..16e2046 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -1,3 +1,3 @@ The following organizations or individuals have contributed to this repo: -- +- diff --git a/README.rst b/README.rst index 6cbd839..3d6cb4e 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,6 @@ A Simple Python Project Skeleton ================================ + This repo attempts to standardize the structure of the Python-based project's repositories using modern Python packaging and configuration techniques. Using this `blog post`_ as inspiration, this repository serves as the base for @@ -47,16 +48,19 @@ Release Notes - 2022-03-04: - Synchronize configure and configure.bat scripts for sanity - Update CI operating system support with latest Azure OS images - - Streamline utility scripts in etc/scripts/ to create, fetch and manage third-party dependencies - There are now fewer scripts. See etc/scripts/README.rst for details + - Streamline utility scripts in etc/scripts/ to create, fetch and manage third-party + dependencies. There are now fewer scripts. See etc/scripts/README.rst for details - 2021-09-03: - - ``configure`` now requires pinned dependencies via the use of ``requirements.txt`` and ``requirements-dev.txt`` + - ``configure`` now requires pinned dependencies via the use of ``requirements.txt`` + and ``requirements-dev.txt`` - ``configure`` can now accept multiple options at once - Add utility scripts from scancode-toolkit/etc/release/ for use in generating project files - Rename virtual environment directory from ``tmp`` to ``venv`` - - Update README.rst with instructions for generating ``requirements.txt`` and ``requirements-dev.txt``, - as well as collecting dependencies as wheels and generating ABOUT files for them. + - Update README.rst with instructions for generating ``requirements.txt`` + and ``requirements-dev.txt``, as well as collecting dependencies as wheels and generating + ABOUT files for them. - 2021-05-11: - - Adopt new configure scripts from ScanCode TK that allows correct configuration of which Python version is used. + - Adopt new configure scripts from ScanCode TK that allows correct configuration of which + Python version is used. From 0213c1ea9a15ab94a854b8d7af27a1a036e393f4 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 22:54:35 +0100 Subject: [PATCH 20/78] Run doc8 on all rst files Signed-off-by: Philippe Ombredanne --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 930e801..debc404 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ dev: doc8: @echo "-> Run doc8 validation" - @${ACTIVATE} doc8 --max-line-length 100 --ignore-path docs/_build/ --quiet docs/ + @${ACTIVATE} doc8 --max-line-length 100 --ignore-path docs/_build/ --quiet docs/ *.rst valid: @echo "-> Run Ruff format" From c112f2a9c20d58e986424f5f32bd259814fc8e3f Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 22:55:20 +0100 Subject: [PATCH 21/78] Enable doc style checks Signed-off-by: Philippe Ombredanne --- pyproject.toml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0f8bd58..51761ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,8 @@ include = [ "etc/**/*.py", "test/**/*.py", "doc/**/*", - "*.py" + "*.py", + "." ] [tool.ruff.lint] @@ -69,10 +70,10 @@ include = [ select = [ # "E", # pycodestyle # "W", # pycodestyle warnings -# "D", # pydocstyle + "D", # pydocstyle # "F", # Pyflakes # "UP", # pyupgrade - "S", # flake8-bandit +# "S", # flake8-bandit "I", # isort # "C9", # McCabe complexity ] From 944b6c5371bea2ce0763fd26888de6436116d185 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 00:34:26 +0100 Subject: [PATCH 22/78] Add support for new OS versions Signed-off-by: Philippe Ombredanne --- README.rst | 50 +++++++++++++++++++++++++++++++++++++++++---- azure-pipelines.yml | 36 ++++++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 6cbd839..f848b4b 100644 --- a/README.rst +++ b/README.rst @@ -1,9 +1,11 @@ A Simple Python Project Skeleton ================================ -This repo attempts to standardize the structure of the Python-based project's -repositories using modern Python packaging and configuration techniques. -Using this `blog post`_ as inspiration, this repository serves as the base for -all new Python projects and is mergeable in existing repositories as well. + +This repo attempts to standardize the structure of the Python-based project's repositories using +modern Python packaging and configuration techniques that can then be applied to many repos. + +Using this `blog post`_ as inspiration, this repository serves as the base for all new Python +projects and is mergeable in existing repositories as well. .. _blog post: https://blog.jaraco.com/a-project-skeleton-for-python-projects/ @@ -13,6 +15,7 @@ Usage A brand new project ------------------- + .. code-block:: bash git init my-new-repo @@ -26,6 +29,7 @@ From here, you can make the appropriate changes to the files for your specific p Update an existing project --------------------------- + .. code-block:: bash cd my-existing-project @@ -41,17 +45,54 @@ More usage instructions can be found in ``docs/skeleton-usage.rst``. Release Notes ============= +- 2025-03-29: + + - Add support for beta macOS-15 + - Add support for beta windows-2025 + +- 2025-02-14: + + - Drop support for Python 3.8, add support in CI for Python 3.13, use Python 3.12 as default + version. + +- 2025-01-17: + + - Drop support for macOS-12, add support for macOS-14 + - Add support in CI for ubuntu-24.04 + - Add support in CI for Python 3.12 + +- 2024-08-20: + + - Update references of ownership from nexB to aboutcode-org + +- 2024-07-01: + + - Drop support for Python 3.8 + - Drop support for macOS-11, add support for macOS-14 + +- 2024-02-19: + + - Replace support in CI of default ubuntu-20.04 by ubuntu-22.04 + +- 2023-10-18: + + - Add dark mode support in documentation + - 2023-07-18: + - Add macOS-13 job in azure-pipelines.yml - 2022-03-04: + - Synchronize configure and configure.bat scripts for sanity - Update CI operating system support with latest Azure OS images - Streamline utility scripts in etc/scripts/ to create, fetch and manage third-party dependencies There are now fewer scripts. See etc/scripts/README.rst for details - 2021-09-03: + - ``configure`` now requires pinned dependencies via the use of ``requirements.txt`` and ``requirements-dev.txt`` + - ``configure`` can now accept multiple options at once - Add utility scripts from scancode-toolkit/etc/release/ for use in generating project files - Rename virtual environment directory from ``tmp`` to ``venv`` @@ -59,4 +100,5 @@ Release Notes as well as collecting dependencies as wheels and generating ABOUT files for them. - 2021-05-11: + - Adopt new configure scripts from ScanCode TK that allows correct configuration of which Python version is used. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a220f2b..80ae45b 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -26,11 +26,27 @@ jobs: - template: etc/ci/azure-posix.yml parameters: job_name: macos13_cpython + image_name: macOS-13-xlarge + 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_arm64 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-posix.yml + parameters: + job_name: macos14_cpython + image_name: macOS-14-large + 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: macos14_cpython_arm64 @@ -41,8 +57,16 @@ jobs: - template: etc/ci/azure-posix.yml parameters: - job_name: macos14_cpython - image_name: macOS-14-large + 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 + + - template: etc/ci/azure-posix.yml + parameters: + job_name: macos15_cpython_arm64 + image_name: macOS-15-large python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] test_suites: all: venv/bin/pytest -n 2 -vvs @@ -62,3 +86,11 @@ jobs: 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: 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 From 136af3912336616fbd2431a96230961517a2c356 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sun, 30 Mar 2025 12:45:32 +0200 Subject: [PATCH 23/78] Update scripts aboutcode references Signed-off-by: Philippe Ombredanne --- etc/scripts/update_skeleton.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/etc/scripts/update_skeleton.py b/etc/scripts/update_skeleton.py index 635898b..5705fc4 100644 --- a/etc/scripts/update_skeleton.py +++ b/etc/scripts/update_skeleton.py @@ -1,11 +1,10 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # -# Copyright (c) nexB Inc. and others. All rights reserved. +# 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/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. # @@ -16,7 +15,7 @@ import click -NEXB_PUBLIC_REPO_NAMES=[ +ABOUTCODE_PUBLIC_REPO_NAMES=[ "aboutcode-toolkit", "ahocode", "bitcode", @@ -56,9 +55,9 @@ @click.command() @click.help_option("-h", "--help") -def update_skeleton_files(repo_names=NEXB_PUBLIC_REPO_NAMES): +def update_skeleton_files(repo_names=ABOUTCODE_PUBLIC_REPO_NAMES): """ - Update project files of nexB projects that use the skeleton + Update project files of AboutCode projects that use the skeleton This script will: - Clone the repo @@ -81,14 +80,14 @@ def update_skeleton_files(repo_names=NEXB_PUBLIC_REPO_NAMES): os.chdir(work_dir_path) # Clone repo - repo_git = f"git@github.com:nexB/{repo_name}.git" + 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:nexB/skeleton.git"]) + subprocess.run(["git", "remote", "add", "skeleton", "git@github.com:aboutcode-org/skeleton.git"]) # Fetch skeleton files subprocess.run(["git", "fetch", "skeleton"]) From da8eff0383611df60311b8bac599657450eaeb52 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sun, 30 Mar 2025 14:40:36 +0200 Subject: [PATCH 24/78] Do not format more test data Signed-off-by: Philippe Ombredanne --- pyproject.toml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 51761ff..7d807eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,10 +60,25 @@ include = [ "src/**/*.py", "etc/**/*.py", "test/**/*.py", + "tests/**/*.py", "doc/**/*", + "docs/**/*", "*.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/**/*", +] + [tool.ruff.lint] # Rules: https://docs.astral.sh/ruff/rules/ From 4f9e936d452acc3822df8d3f932cbd7071b31d72 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sun, 30 Mar 2025 14:58:36 +0200 Subject: [PATCH 25/78] Do not treat rst as Python Signed-off-by: Philippe Ombredanne --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7d807eb..5e16b56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,8 +61,8 @@ include = [ "etc/**/*.py", "test/**/*.py", "tests/**/*.py", - "doc/**/*", - "docs/**/*", + "doc/**/*.py", + "docs/**/*.py", "*.py", "." ] From a2809fb28c60b54aec0c367285acacdea1cb03a8 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sun, 30 Mar 2025 16:41:57 +0200 Subject: [PATCH 26/78] Combine testing and docs extra for simplicity Signed-off-by: Philippe Ombredanne --- configure | 2 -- configure.bat | 4 ---- setup.cfg | 3 --- 3 files changed, 9 deletions(-) diff --git a/configure b/configure index 22d9288..83fd203 100755 --- a/configure +++ b/configure @@ -30,7 +30,6 @@ 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" # where we create a virtualenv VIRTUALENV_DIR=venv @@ -185,7 +184,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 5b9a9d6..18b3703 100644 --- a/configure.bat +++ b/configure.bat @@ -28,7 +28,6 @@ @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" @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 ) diff --git a/setup.cfg b/setup.cfg index aaec643..ad8e0d8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -55,8 +55,6 @@ testing = pycodestyle >= 2.8.0 twine ruff - -docs = Sphinx>=5.0.2 sphinx-rtd-theme>=1.0.0 sphinx-reredirects >= 0.1.2 @@ -64,4 +62,3 @@ docs = sphinx-autobuild sphinx-rtd-dark-mode>=1.3.0 sphinx-copybutton - From 43b96c28baaa1621d24b6f5791c6d915d2edc5f3 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sun, 30 Mar 2025 17:18:19 +0200 Subject: [PATCH 27/78] Refine checking of docs with doc8 Signed-off-by: Philippe Ombredanne --- Makefile | 2 +- pyproject.toml | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index debc404..d21a2f9 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ dev: doc8: @echo "-> Run doc8 validation" - @${ACTIVATE} doc8 --max-line-length 100 --ignore-path docs/_build/ --quiet docs/ *.rst + @${ACTIVATE} doc8 docs/ *.rst valid: @echo "-> Run Ruff format" diff --git a/pyproject.toml b/pyproject.toml index 5e16b56..bfb1d35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -119,3 +119,10 @@ max-complexity = 10 # 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 +verbose=0 From b7194c80c9425087f1d05e430bd9d6a14fb9c3a0 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sun, 30 Mar 2025 18:41:00 +0200 Subject: [PATCH 28/78] Refine doc handling * remove CI scripts and use Makefile targets instead * ensure doc8 runs quiet * add new docs-check make target to run documentation and links checks * update oudated doc for docs contribution Signed-off-by: Philippe Ombredanne --- .github/workflows/docs-ci.yml | 12 +++++------- Makefile | 10 +++++++--- docs/scripts/doc8_style_check.sh | 5 ----- docs/scripts/sphinx_build_link_check.sh | 5 ----- docs/source/conf.py | 2 +- docs/source/contribute/contrib_doc.rst | 8 ++++---- pyproject.toml | 2 -- 7 files changed, 17 insertions(+), 27 deletions(-) delete mode 100755 docs/scripts/doc8_style_check.sh delete mode 100644 docs/scripts/sphinx_build_link_check.sh diff --git a/.github/workflows/docs-ci.yml b/.github/workflows/docs-ci.yml index 621de4b..10ba5fa 100644 --- a/.github/workflows/docs-ci.yml +++ b/.github/workflows/docs-ci.yml @@ -21,14 +21,12 @@ jobs: 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/Makefile b/Makefile index d21a2f9..413399e 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ dev: doc8: @echo "-> Run doc8 validation" - @${ACTIVATE} doc8 docs/ *.rst + @${ACTIVATE} doc8 --quiet docs/ *.rst valid: @echo "-> Run Ruff format" @@ -46,6 +46,10 @@ test: docs: rm -rf docs/_build/ - @${ACTIVATE} sphinx-build docs/ docs/_build/ + @${ACTIVATE} sphinx-build docs/source docs/_build/ -.PHONY: conf dev check valid clean test docs +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 clean test docs docs-check 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 8aad829..056ca6e 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -18,7 +18,7 @@ # -- Project information ----------------------------------------------------- project = "nexb-skeleton" -copyright = "nexB Inc. and others." +copyright = "nexB Inc., AboutCode and others." author = "AboutCode.org authors and contributors" diff --git a/docs/source/contribute/contrib_doc.rst b/docs/source/contribute/contrib_doc.rst index 5640db2..041b358 100644 --- a/docs/source/contribute/contrib_doc.rst +++ b/docs/source/contribute/contrib_doc.rst @@ -147,7 +147,7 @@ What is Checked? ^^^^^^^^^^^^^^^^ PyCQA is an Organization for code quality tools (and plugins) for the Python programming language. -Doc8 is a sub-project of the same Organization. Refer this `README `_ for more details. +Doc8 is a sub-project of the same Organization. Refer this `README `_ for more details. What is checked: @@ -169,11 +169,11 @@ What is checked: Interspinx ---------- -ScanCode toolkit documentation uses `Intersphinx `_ +ScanCode toolkit documentation uses `Intersphinx `_ to link to other Sphinx Documentations, to maintain links to other Aboutcode Projects. To link sections in the same documentation, standart reST labels are used. Refer -`Cross-Referencing `_ for more information. +`Cross-Referencing `_ for more information. For example:: @@ -230,7 +230,7 @@ Style Conventions for the Documentaion 1. Headings - (`Refer `_) + (`Refer `_) Normally, there are no heading levels assigned to certain characters as the structure is determined from the succession of headings. However, this convention is used in Python’s Style Guide for documenting which you may follow: diff --git a/pyproject.toml b/pyproject.toml index bfb1d35..c9e6772 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -122,7 +122,5 @@ max-complexity = 10 [tool.doc8] - ignore-path = ["docs/build", "doc/build", "docs/_build", "doc/_build"] max-line-length=100 -verbose=0 From a5bcdbdd71d1542a0e9ec9b190a2e3d573c53744 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sun, 30 Mar 2025 18:49:01 +0200 Subject: [PATCH 29/78] Add twine check to release publication Signed-off-by: Philippe Ombredanne --- .github/workflows/pypi-release.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index a66c9c8..cf0579a 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -30,12 +30,15 @@ jobs: with: 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 build twine - name: Build a binary wheel and a source tarball run: python -m build --sdist --wheel --outdir dist/ + - name: Validate wheel and sdis for Pypi + run: python -m twine check dist/* + - name: Upload built archives uses: actions/upload-artifact@v4 with: From a6c25fb2a2fa35311d26621b9db400ca52bd376e Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sun, 30 Mar 2025 19:16:31 +0200 Subject: [PATCH 30/78] Refine doc contribution docs Signed-off-by: Philippe Ombredanne --- docs/source/contribute/contrib_doc.rst | 119 ++++++++----------------- 1 file changed, 38 insertions(+), 81 deletions(-) diff --git a/docs/source/contribute/contrib_doc.rst b/docs/source/contribute/contrib_doc.rst index 041b358..dee9296 100644 --- a/docs/source/contribute/contrib_doc.rst +++ b/docs/source/contribute/contrib_doc.rst @@ -8,109 +8,59 @@ Contributing to the Documentation Setup Local Build ----------------- -To get started, create or identify a working directory on your local machine. +To get started, check out and configure the repository for development:: -Open that directory and execute the following command in a terminal session:: + git clone https://github.com/aboutcode-org/.git - git clone https://github.com/aboutcode-org/skeleton.git + cd your-repo + ./configure --dev -That will create an ``/skeleton`` directory in your working directory. -Now you can install the dependencies in a virtualenv:: - - cd skeleton - ./configure --docs +(Or use "make dev") .. note:: - In case of windows, run ``configure --docs`` instead of this. - -Now, this will install the following prerequisites: - -- Sphinx -- sphinx_rtd_theme (the format theme used by ReadTheDocs) -- docs8 (style linter) + In case of windows, run ``configure --dev``. -These requirements are already present in setup.cfg and `./configure --docs` installs them. +This will install and configure all requirements foer development including for docs development. -Now you can build the HTML documents locally:: +Now you can build the HTML documentation locally:: source venv/bin/activate - cd docs - make html - -Assuming that your Sphinx installation was successful, Sphinx should build a local instance of the -documentation .html files:: - - open build/html/index.html - -.. note:: - - In case this command did not work, for example on Ubuntu 18.04 you may get a message like “Couldn’t - get a file descriptor referring to the console”, try: - - :: - - see build/html/index.html + make docs -You now have a local build of the AboutCode documents. +This will build a local instance of the ``docs/_build`` directory:: -.. _contrib_doc_share_improvements: + open docs/_build/index.html -Share Document Improvements ---------------------------- - -Ensure that you have the latest files:: - - git pull - git status -Before commiting changes run Continious Integration Scripts locally to run tests. Refer -:ref:`doc_ci` for instructions on the same. +To validate the documentation style and content, use:: -Follow standard git procedures to upload your new and modified files. The following commands are -examples:: - - git status - git add source/index.rst - git add source/how-to-scan.rst - git status - git commit -m "New how-to document that explains how to scan" - git status - git push - git status - -The Scancode-Toolkit webhook with ReadTheDocs should rebuild the documentation after your -Pull Request is Merged. + source venv/bin/activate + make doc8 + make docs-check -Refer the `Pro Git Book `_ available online for Git tutorials -covering more complex topics on Branching, Merging, Rebasing etc. .. _doc_ci: Continuous Integration ---------------------- -The documentations are checked on every new commit through Travis-CI, so that common errors are -avoided and documentation standards are enforced. Travis-CI presently checks for these 3 aspects -of the documentation : +The documentations are checked on every new commit, so that common errors are avoided and +documentation standards are enforced. We checks for these aspects of the documentation: 1. Successful Builds (By using ``sphinx-build``) -2. No Broken Links (By Using ``link-check``) -3. Linting Errors (By Using ``Doc8``) +2. No Broken Links (By Using ``linkcheck``) +3. Linting Errors (By Using ``doc8``) -So run these scripts at your local system before creating a Pull Request:: +You myst run these scripts locally before creating a pull request:: - cd docs - ./scripts/sphinx_build_link_check.sh - ./scripts/doc8_style_check.sh + make doc8 + make check-docs -If you don't have permission to run the scripts, run:: - - chmod u+x ./scripts/doc8_style_check.sh .. _doc_style_docs8: -Style Checks Using ``Doc8`` +Style Checks Using ``doc8`` --------------------------- How To Run Style Tests @@ -118,8 +68,7 @@ How To Run Style Tests In the project root, run the following commands:: - $ cd docs - $ ./scripts/doc8_style_check.sh + make doc8 A sample output is:: @@ -143,11 +92,13 @@ A sample output is:: Now fix the errors and run again till there isn't any style error in the documentation. + What is Checked? ^^^^^^^^^^^^^^^^ PyCQA is an Organization for code quality tools (and plugins) for the Python programming language. -Doc8 is a sub-project of the same Organization. Refer this `README `_ for more details. +Doc8 is a sub-project of the same Organization. Refer this +`README `_ for more details. What is checked: @@ -164,16 +115,19 @@ What is checked: - no carriage returns (use UNIX newlines) - D004 - no newline at end of file - D005 + .. _doc_interspinx: Interspinx ---------- -ScanCode toolkit documentation uses `Intersphinx `_ +AboutCode documentation uses +`Intersphinx `_ to link to other Sphinx Documentations, to maintain links to other Aboutcode Projects. To link sections in the same documentation, standart reST labels are used. Refer -`Cross-Referencing `_ for more information. +`Cross-Referencing `_ +for more information. For example:: @@ -223,6 +177,7 @@ Intersphinx, and you link to that label, it will create a link to the local labe For more information, refer this tutorial named `Using Intersphinx `_. + .. _doc_style_conv: Style Conventions for the Documentaion @@ -303,12 +258,14 @@ Style Conventions for the Documentaion ``rst_snippets/warning_snippets/`` and then included to eliminate redundancy, as these are frequently used in multiple files. + Converting from Markdown ------------------------ -If you want to convert a ``.md`` file to a ``.rst`` file, this `tool `_ -does it pretty well. You'd still have to clean up and check for errors as this contains a lot of -bugs. But this is definitely better than converting everything by yourself. +If you want to convert a ``.md`` file to a ``.rst`` file, this +`tool `_ does it pretty well. +You will still have to clean up and check for errors as this contains a lot of bugs. But this is +definitely better than converting everything by yourself. This will be helpful in converting GitHub wiki's (Markdown Files) to reStructuredtext files for Sphinx/ReadTheDocs hosting. From 68daae1e7e475a89568e353f64f29af13754ce9e Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Thu, 27 Mar 2025 14:54:31 -0700 Subject: [PATCH 31/78] Replace black and isort with ruff * Use ruff config and Make commands from scancode.io Signed-off-by: Jono Yang --- Makefile | 27 ++++++++++++--------------- pyproject.toml | 37 +++++++++++++++++++++++++++++++++++++ setup.cfg | 3 +-- 3 files changed, 50 insertions(+), 17 deletions(-) diff --git a/Makefile b/Makefile index 94451b3..1738b20 100644 --- a/Makefile +++ b/Makefile @@ -17,27 +17,24 @@ 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 - -black: - @echo "-> Apply black code formatter" - ${VENV}/bin/black -l 100 src tests setup.py - doc8: @echo "-> Run doc8 validation" @${ACTIVATE} doc8 --max-line-length 100 --ignore-path docs/_build/ --quiet docs/ -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" diff --git a/pyproject.toml b/pyproject.toml index cde7907..01e60fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,3 +50,40 @@ addopts = [ "--strict-markers", "--doctest-modules" ] + +[tool.ruff] +line-length = 88 +extend-exclude = [] +target-version = "py310" + +[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", "D203", "D205", "D212", "D400", "D415"] + +[tool.ruff.lint.isort] +force-single-line = true +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 diff --git a/setup.cfg b/setup.cfg index ef7d369..aaec643 100644 --- a/setup.cfg +++ b/setup.cfg @@ -54,8 +54,7 @@ testing = aboutcode-toolkit >= 7.0.2 pycodestyle >= 2.8.0 twine - black - isort + ruff docs = Sphinx>=5.0.2 From 6a8c9ae144a1985b59fb69a0b2c55e32831714b8 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 00:46:06 +0100 Subject: [PATCH 32/78] Use org standard 100 line length Signed-off-by: Philippe Ombredanne --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 01e60fc..cea91bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ addopts = [ ] [tool.ruff] -line-length = 88 +line-length = 100 extend-exclude = [] target-version = "py310" From 2fd31d54afa47418c764de0f1a30d67c7059ed7b Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 08:40:28 +0100 Subject: [PATCH 33/78] Lint all common code directories Signed-off-by: Philippe Ombredanne --- pyproject.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index cea91bd..9e62736 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,14 @@ addopts = [ line-length = 100 extend-exclude = [] target-version = "py310" +include = [ + "pyproject.toml", + "src/**/*.py", + "etc/**/*.py", + "test/**/*.py", + "doc/**/*", + "*.py" +] [tool.ruff.lint] # Rules: https://docs.astral.sh/ruff/rules/ From eb5fc82ab1cab8a4742a2b9028d1436956960e81 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 09:07:47 +0100 Subject: [PATCH 34/78] Remove unused targets Signed-off-by: Philippe Ombredanne --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1738b20..930e801 100644 --- a/Makefile +++ b/Makefile @@ -48,4 +48,4 @@ docs: rm -rf docs/_build/ @${ACTIVATE} sphinx-build docs/ docs/_build/ -.PHONY: conf dev check valid black isort clean test docs +.PHONY: conf dev check valid clean test docs From 529d51621c9e2af8e1ec2503044b8752c71c3ba7 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 11:03:05 +0100 Subject: [PATCH 35/78] Improve import sorting Signed-off-by: Philippe Ombredanne --- etc/scripts/check_thirdparty.py | 1 - etc/scripts/fetch_thirdparty.py | 2 +- etc/scripts/test_utils_pip_compatibility_tags.py | 3 +-- etc/scripts/utils_dejacode.py | 1 - etc/scripts/utils_pip_compatibility_tags.py | 14 ++++++-------- etc/scripts/utils_thirdparty.py | 3 +-- pyproject.toml | 7 ++++++- 7 files changed, 15 insertions(+), 16 deletions(-) diff --git a/etc/scripts/check_thirdparty.py b/etc/scripts/check_thirdparty.py index 2daded9..62dbb14 100644 --- a/etc/scripts/check_thirdparty.py +++ b/etc/scripts/check_thirdparty.py @@ -12,7 +12,6 @@ import utils_thirdparty - @click.command() @click.option( "-d", diff --git a/etc/scripts/fetch_thirdparty.py b/etc/scripts/fetch_thirdparty.py index 3f9ff52..30d376c 100644 --- a/etc/scripts/fetch_thirdparty.py +++ b/etc/scripts/fetch_thirdparty.py @@ -16,8 +16,8 @@ import click -import utils_thirdparty import utils_requirements +import utils_thirdparty TRACE = False TRACE_DEEP = False diff --git a/etc/scripts/test_utils_pip_compatibility_tags.py b/etc/scripts/test_utils_pip_compatibility_tags.py index 98187c5..a33b8b3 100644 --- a/etc/scripts/test_utils_pip_compatibility_tags.py +++ b/etc/scripts/test_utils_pip_compatibility_tags.py @@ -25,14 +25,13 @@ 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 import utils_pip_compatibility_tags - @pytest.mark.parametrize( "version_info, expected", [ diff --git a/etc/scripts/utils_dejacode.py b/etc/scripts/utils_dejacode.py index 652252d..c71543f 100644 --- a/etc/scripts/utils_dejacode.py +++ b/etc/scripts/utils_dejacode.py @@ -14,7 +14,6 @@ import requests import saneyaml - from packvers import version as packaging_version """ diff --git a/etc/scripts/utils_pip_compatibility_tags.py b/etc/scripts/utils_pip_compatibility_tags.py index af42a0c..de0ac95 100644 --- a/etc/scripts/utils_pip_compatibility_tags.py +++ b/etc/scripts/utils_pip_compatibility_tags.py @@ -27,14 +27,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+)_(.+)") diff --git a/etc/scripts/utils_thirdparty.py b/etc/scripts/utils_thirdparty.py index 46dc728..b0295ec 100644 --- a/etc/scripts/utils_thirdparty.py +++ b/etc/scripts/utils_thirdparty.py @@ -25,14 +25,13 @@ import packageurl import requests import saneyaml +import utils_pip_compatibility_tags from commoncode import fileutils from commoncode.hash import multi_checksums from commoncode.text import python_safe_name from packvers import tags as packaging_tags from packvers import version as packaging_version -import utils_pip_compatibility_tags - """ Utilities to manage Python thirparty libraries source, binaries and metadata in local directories and remote repositories. diff --git a/pyproject.toml b/pyproject.toml index 9e62736..ba55770 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,10 +76,15 @@ select = [ "I", # isort "C9", # McCabe complexity ] -ignore = ["D1", "D203", "D205", "D212", "D400", "D415"] +ignore = ["D1", "D200", "D203", "D205", "D212", "D400", "D415"] [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", From aae1a2847c0e493b0e8bea542da30dbdfb2be68e Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 21:35:16 +0100 Subject: [PATCH 36/78] Apply small code updates Signed-off-by: Philippe Ombredanne --- etc/scripts/utils_requirements.py | 20 ++++++++----- etc/scripts/utils_thirdparty.py | 48 +++++++++++++++---------------- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/etc/scripts/utils_requirements.py b/etc/scripts/utils_requirements.py index 1c50239..a9ac223 100644 --- a/etc/scripts/utils_requirements.py +++ b/etc/scripts/utils_requirements.py @@ -57,21 +57,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 @@ -120,7 +124,7 @@ def get_installed_reqs(site_packages_dir): # 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 = ( @@ -150,9 +154,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 not 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 b0295ec..6d5ffdc 100644 --- a/etc/scripts/utils_thirdparty.py +++ b/etc/scripts/utils_thirdparty.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # # Copyright (c) nexB Inc. and others. All rights reserved. # ScanCode is a trademark of nexB Inc. @@ -559,7 +558,8 @@ def download(self, dest_dir=THIRDPARTY_DIR): Download this distribution into `dest_dir` directory. Return the fetched filename. """ - assert self.filename + if not self.filename: + raise ValueError(f"self.filename has no value but is required: {self.filename!r}") if TRACE_DEEP: print( f"Fetching distribution of {self.name}=={self.version}:", @@ -829,10 +829,9 @@ def fetch_license_files(self, dest_dir=THIRDPARTY_DIR, use_cached_index=False): urls = LinksRepository.from_url( use_cached_index=use_cached_index).links errors = [] - extra_lic_names = [l.get("file") - for l in self.extra_data.get("licenses", {})] + extra_lic_names = [lic.get("file") for lic in self.extra_data.get("licenses", {})] extra_lic_names += [self.extra_data.get("license_file")] - extra_lic_names = [ln for ln in extra_lic_names if ln] + extra_lic_names = [eln for eln in extra_lic_names if eln] lic_names = [f"{key}.LICENSE" for key in self.get_license_keys()] for filename in lic_names + extra_lic_names: floc = os.path.join(dest_dir, filename) @@ -853,7 +852,7 @@ def fetch_license_files(self, dest_dir=THIRDPARTY_DIR, use_cached_index=False): if TRACE: print(f"Fetched license from remote: {lic_url}") - except: + except Exception: try: # try licensedb second lic_url = f"{LICENSEDB_API_URL}/{filename}" @@ -866,8 +865,9 @@ def fetch_license_files(self, dest_dir=THIRDPARTY_DIR, use_cached_index=False): if TRACE: print(f"Fetched license from licensedb: {lic_url}") - except: - msg = f'No text for license {filename} in expression "{self.license_expression}" from {self}' + except Exception: + msg = f"No text for license {filename} in expression " + f"{self.license_expression!r} from {self}" print(msg) errors.append(msg) @@ -1009,7 +1009,7 @@ def get_license_link_for_filename(filename, urls): exception if no link is found or if there are more than one link for that file name. """ - path_or_url = [l for l in urls if l.endswith(f"/{filename}")] + path_or_url = [url for url in urls if url.endswith(f"/{filename}")] if not path_or_url: raise Exception(f"Missing link to file: {filename}") if not len(path_or_url) == 1: @@ -1140,7 +1140,6 @@ def to_filename(self): @attr.attributes class Wheel(Distribution): - """ Represents a wheel file. @@ -1301,7 +1300,7 @@ def is_pure(self): def is_pure_wheel(filename): try: return Wheel.from_filename(filename).is_pure() - except: + except Exception: return False @@ -1489,8 +1488,7 @@ def dists_from_paths_or_urls(cls, paths_or_urls): ) except InvalidDistributionFilename: if TRACE_DEEP: - print( - f" Skipping invalid distribution from: {path_or_url}") + print(f" Skipping invalid distribution from: {path_or_url}") continue return dists @@ -1500,8 +1498,7 @@ def get_distributions(self): """ if self.sdist: yield self.sdist - for wheel in self.wheels: - yield wheel + yield from self.wheels def get_url_for_filename(self, filename): """ @@ -1632,7 +1629,8 @@ class PypiSimpleRepository: type=dict, default=attr.Factory(lambda: defaultdict(dict)), metadata=dict( - help="Mapping of {name: {version: PypiPackage, version: PypiPackage, etc} available in this repo" + help="Mapping of {name: {version: PypiPackage, version: PypiPackage, etc} " + "available in this repo" ), ) @@ -1647,7 +1645,8 @@ class PypiSimpleRepository: type=bool, default=False, metadata=dict( - help="If True, use any existing on-disk cached PyPI index files. Otherwise, fetch and cache." + help="If True, use any existing on-disk cached PyPI index files. " + "Otherwise, fetch and cache." ), ) @@ -1656,7 +1655,8 @@ def _get_package_versions_map(self, name): Return a mapping of all available PypiPackage version for this package name. The mapping may be empty. It is ordered by version from oldest to newest """ - assert name + if not name: + raise ValueError(f"name is required: {name!r}") normalized_name = NameVer.normalize_name(name) versions = self.packages[normalized_name] if not versions and normalized_name not in self.fetched_package_normalized_names: @@ -1713,7 +1713,7 @@ def fetch_links(self, normalized_name): ) links = collect_urls(text) # TODO: keep sha256 - links = [l.partition("#sha256=") for l in links] + links = [link.partition("#sha256=") for link in links] links = [url for url, _, _sha256 in links] return links @@ -1936,7 +1936,7 @@ def get_remote_file_content( # several redirects and that we can ignore content there. A HEAD request may # not get us this last header print(f" DOWNLOADING: {url}") - with requests.get(url, allow_redirects=True, stream=True, headers=headers) as response: + with requests.get(url, allow_redirects=True, stream=True, headers=headers) as response: # noqa: S113 status = response.status_code if status != requests.codes.ok: # NOQA if status == 429 and _delay < 20: @@ -2161,7 +2161,7 @@ def call(args, verbose=TRACE): """ if TRACE_DEEP: print("Calling:", " ".join(args)) - with subprocess.Popen( + with subprocess.Popen( # noqa: S603 args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8" ) as process: @@ -2227,7 +2227,7 @@ def download_wheels_with_pip( cli_args.extend(["--requirement", req_file]) if TRACE: - print(f"Downloading wheels using command:", " ".join(cli_args)) + print("Downloading wheels using command:", " ".join(cli_args)) existing = set(os.listdir(dest_dir)) error = False @@ -2260,7 +2260,7 @@ def download_wheels_with_pip( def check_about(dest_dir=THIRDPARTY_DIR): try: - subprocess.check_output(f"venv/bin/about check {dest_dir}".split()) + subprocess.check_output(f"venv/bin/about check {dest_dir}".split()) # noqa: S603 except subprocess.CalledProcessError as cpe: print() print("Invalid ABOUT files:") @@ -2312,5 +2312,5 @@ def get_license_expression(declared_licenses): return get_only_expression_from_extracted_license(declared_licenses) except ImportError: # Scancode is not installed, clean and join all the licenses - lics = [python_safe_name(l).lower() for l in declared_licenses] + lics = [python_safe_name(lic).lower() for lic in declared_licenses] return " AND ".join(lics).lower() From 037f9fc1b03736eeac9e0eefac3e35acc916d193 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 21:42:03 +0100 Subject: [PATCH 37/78] Format code Signed-off-by: Philippe Ombredanne --- etc/scripts/check_thirdparty.py | 3 +- etc/scripts/fetch_thirdparty.py | 26 ++++----- etc/scripts/gen_pypi_simple.py | 4 +- etc/scripts/utils_dejacode.py | 15 +++--- etc/scripts/utils_requirements.py | 9 ++-- etc/scripts/utils_thirdparty.py | 90 +++++++++++-------------------- 6 files changed, 50 insertions(+), 97 deletions(-) diff --git a/etc/scripts/check_thirdparty.py b/etc/scripts/check_thirdparty.py index 62dbb14..1aa4e28 100644 --- a/etc/scripts/check_thirdparty.py +++ b/etc/scripts/check_thirdparty.py @@ -16,8 +16,7 @@ @click.option( "-d", "--dest", - type=click.Path(exists=True, readable=True, - path_type=str, file_okay=False), + type=click.Path(exists=True, readable=True, path_type=str, file_okay=False), required=True, help="Path to the thirdparty directory to check.", ) diff --git a/etc/scripts/fetch_thirdparty.py b/etc/scripts/fetch_thirdparty.py index 30d376c..c224683 100644 --- a/etc/scripts/fetch_thirdparty.py +++ b/etc/scripts/fetch_thirdparty.py @@ -55,8 +55,7 @@ "-d", "--dest", "dest_dir", - type=click.Path(exists=True, readable=True, - path_type=str, file_okay=False), + type=click.Path(exists=True, readable=True, path_type=str, file_okay=False), metavar="DIR", default=utils_thirdparty.THIRDPARTY_DIR, show_default=True, @@ -121,7 +120,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", @@ -132,7 +131,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", @@ -143,7 +142,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( @@ -225,8 +224,7 @@ def fetch_thirdparty( environments = None if wheels: evts = itertools.product(python_versions, operating_systems) - environments = [utils_thirdparty.Environment.from_pyver_and_os( - pyv, os) for pyv, os in evts] + environments = [utils_thirdparty.Environment.from_pyver_and_os(pyv, os) for pyv, os in evts] # Collect PyPI repos repos = [] @@ -250,7 +248,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,14 +259,11 @@ def fetch_thirdparty( repos=repos, ) if not fetched: - wheels_or_sdist_not_found[f"{name}=={version}"].append( - environment) + wheels_or_sdist_not_found[f"{name}=={version}"].append(environment) if TRACE: print(f" 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}") @@ -292,8 +286,7 @@ def fetch_thirdparty( sdist_missing = sdists and "sdist" in dists and not name 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 not name in sdist_only if wheels_missing: mia.append(f"Wheels missing: {nv} {dists}") @@ -303,8 +296,7 @@ def fetch_thirdparty( raise Exception(mia) print(f"==> 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.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 diff --git a/etc/scripts/gen_pypi_simple.py b/etc/scripts/gen_pypi_simple.py index 214d90d..cfe68e6 100644 --- a/etc/scripts/gen_pypi_simple.py +++ b/etc/scripts/gen_pypi_simple.py @@ -69,7 +69,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: @@ -200,11 +199,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/utils_dejacode.py b/etc/scripts/utils_dejacode.py index c71543f..cd39cda 100644 --- a/etc/scripts/utils_dejacode.py +++ b/etc/scripts/utils_dejacode.py @@ -32,8 +32,7 @@ def can_do_api_calls(): if not DEJACODE_API_KEY and DEJACODE_API_URL: - print( - "DejaCode DEJACODE_API_KEY and DEJACODE_API_URL not configured. Doing nothing") + print("DejaCode DEJACODE_API_KEY and DEJACODE_API_URL not configured. Doing nothing") return False else: return True @@ -68,8 +67,7 @@ def get_package_data(distribution): return results[0] elif len_results > 1: - print( - f"More than 1 entry exists, review at: {DEJACODE_API_URL_PACKAGES}") + print(f"More than 1 entry exists, review at: {DEJACODE_API_URL_PACKAGES}") else: print("Could not find package:", distribution.download_url) @@ -150,12 +148,11 @@ def find_latest_dejacode_package(distribution): # there was no exact match, find the latest version # TODO: consider the closest version rather than the latest # or the version that has the best data - with_versions = [(packaging_version.parse(p["version"]), p) - for p in packages] + with_versions = [(packaging_version.parse(p["version"]), p) for p in packages] 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 @@ -181,7 +178,7 @@ def create_dejacode_package(distribution): } fields_to_carry_over = [ - "download_url" "type", + "download_urltype", "namespace", "name", "version", @@ -209,5 +206,5 @@ def create_dejacode_package(distribution): 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_requirements.py b/etc/scripts/utils_requirements.py index a9ac223..167bc9f 100644 --- a/etc/scripts/utils_requirements.py +++ b/etc/scripts/utils_requirements.py @@ -106,8 +106,7 @@ def lock_dev_requirements( all_req_nvs = get_required_name_versions(all_req_lines) dev_only_req_nvs = {n: v for n, v in all_req_nvs if n not in main_names} - new_reqs = "\n".join( - f"{n}=={v}" for n, v in sorted(dev_only_req_nvs.items())) + new_reqs = "\n".join(f"{n}=={v}" for n, v in sorted(dev_only_req_nvs.items())) with open(dev_requirements_file, "w") as fo: fo.write(new_reqs) @@ -118,12 +117,10 @@ def get_installed_reqs(site_packages_dir): as a text. """ if not os.path.exists(site_packages_dir): - raise Exception( - f"site_packages directory: {site_packages_dir!r} does not exists") + raise Exception(f"site_packages directory: {site_packages_dir!r} does not exists") # Also include these packages in the output with --all: wheel, distribute, # setuptools, pip - args = ["pip", "freeze", "--exclude-editable", - "--all", "--path", site_packages_dir] + args = ["pip", "freeze", "--exclude-editable", "--all", "--path", site_packages_dir] return subprocess.check_output(args, encoding="utf-8") # noqa: S603 diff --git a/etc/scripts/utils_thirdparty.py b/etc/scripts/utils_thirdparty.py index 6d5ffdc..4ea1bab 100644 --- a/etc/scripts/utils_thirdparty.py +++ b/etc/scripts/utils_thirdparty.py @@ -243,11 +243,9 @@ def download_wheel(name, version, environment, dest_dir=THIRDPARTY_DIR, repos=tu package = repo.get_package_version(name=name, version=version) if not package: if TRACE_DEEP: - print( - f" download_wheel: No package in {repo.index_url} for {name}=={version}") + print(f" download_wheel: No package in {repo.index_url} for {name}=={version}") continue - supported_wheels = list( - package.get_supported_wheels(environment=environment)) + supported_wheels = list(package.get_supported_wheels(environment=environment)) if not supported_wheels: if TRACE_DEEP: print( @@ -291,8 +289,7 @@ def download_sdist(name, version, dest_dir=THIRDPARTY_DIR, repos=tuple()): if not package: if TRACE_DEEP: - print( - f" download_sdist: No package in {repo.index_url} for {name}=={version}") + print(f" download_sdist: No package in {repo.index_url} for {name}=={version}") continue sdist = package.sdist if not sdist: @@ -301,8 +298,7 @@ def download_sdist(name, version, dest_dir=THIRDPARTY_DIR, repos=tuple()): continue if TRACE_DEEP: - print( - f" download_sdist: Getting sdist from index (or cache): {sdist.download_url}") + print(f" download_sdist: Getting sdist from index (or cache): {sdist.download_url}") fetched_sdist_filename = package.sdist.download(dest_dir=dest_dir) if fetched_sdist_filename: @@ -357,7 +353,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", @@ -535,8 +530,7 @@ def get_best_download_url(self, repos=tuple()): repos = DEFAULT_PYPI_REPOS for repo in repos: - package = repo.get_package_version( - name=self.name, version=self.version) + package = repo.get_package_version(name=self.name, version=self.version) if not package: if TRACE: print( @@ -776,8 +770,7 @@ def load_remote_about_data(self): if notice_text: about_data["notice_text"] = notice_text except RemoteNotFetchedException: - print( - f"Failed to fetch NOTICE file: {self.notice_download_url}") + print(f"Failed to fetch NOTICE file: {self.notice_download_url}") return self.load_about_data(about_data) def get_checksums(self, dest_dir=THIRDPARTY_DIR): @@ -826,8 +819,7 @@ def fetch_license_files(self, dest_dir=THIRDPARTY_DIR, use_cached_index=False): Fetch license files if missing in `dest_dir`. Return True if license files were fetched. """ - urls = LinksRepository.from_url( - use_cached_index=use_cached_index).links + urls = LinksRepository.from_url(use_cached_index=use_cached_index).links errors = [] extra_lic_names = [lic.get("file") for lic in self.extra_data.get("licenses", {})] extra_lic_names += [self.extra_data.get("license_file")] @@ -840,8 +832,7 @@ def fetch_license_files(self, dest_dir=THIRDPARTY_DIR, use_cached_index=False): try: # try remotely first - lic_url = get_license_link_for_filename( - filename=filename, urls=urls) + lic_url = get_license_link_for_filename(filename=filename, urls=urls) fetch_and_save( path_or_url=lic_url, @@ -919,8 +910,7 @@ def load_pkginfo_data(self, dest_dir=THIRDPARTY_DIR): c for c in classifiers if c.startswith("License") ] license_expression = get_license_expression(declared_license) - other_classifiers = [ - c for c in classifiers if not c.startswith("License")] + other_classifiers = [c for c in classifiers if not c.startswith("License")] holder = raw_data["Author"] holder_contact = raw_data["Author-email"] @@ -962,8 +952,7 @@ def update(self, data, overwrite=False, keep_extra=True): package_url = data.get("package_url") if package_url: purl_from_data = packageurl.PackageURL.from_string(package_url) - purl_from_self = packageurl.PackageURL.from_string( - self.package_url) + purl_from_self = packageurl.PackageURL.from_string(self.package_url) if purl_from_data != purl_from_self: print( f"Invalid dist update attempt, no same same purl with dist: " @@ -1013,8 +1002,7 @@ def get_license_link_for_filename(filename, urls): if not path_or_url: raise Exception(f"Missing link to file: {filename}") if not len(path_or_url) == 1: - raise Exception( - f"Multiple links to file: {filename}: \n" + "\n".join(path_or_url)) + raise Exception(f"Multiple links to file: {filename}: \n" + "\n".join(path_or_url)) return path_or_url[0] @@ -1102,7 +1090,6 @@ def get_sdist_name_ver_ext(filename): @attr.attributes class Sdist(Distribution): - extension = attr.ib( repr=False, type=str, @@ -1407,8 +1394,7 @@ def packages_from_dir(cls, directory): """ base = os.path.abspath(directory) - paths = [os.path.join(base, f) - for f in os.listdir(base) if f.endswith(EXTENSIONS)] + paths = [os.path.join(base, f) for f in os.listdir(base) if f.endswith(EXTENSIONS)] if TRACE_ULTRA_DEEP: print("packages_from_dir: paths:", paths) @@ -1469,8 +1455,7 @@ def dists_from_paths_or_urls(cls, paths_or_urls): dists = [] if TRACE_ULTRA_DEEP: print(" ###paths_or_urls:", paths_or_urls) - installable = [f for f in paths_or_urls if f.endswith( - EXTENSIONS_INSTALLABLE)] + installable = [f for f in paths_or_urls if f.endswith(EXTENSIONS_INSTALLABLE)] for path_or_url in installable: try: dist = Distribution.from_path_or_url(path_or_url) @@ -1536,8 +1521,7 @@ class Environment: implementation = attr.ib( type=str, default="cp", - metadata=dict( - help="Python implementation supported by this environment."), + metadata=dict(help="Python implementation supported by this environment."), repr=False, ) @@ -1551,8 +1535,7 @@ class Environment: platforms = attr.ib( type=list, default=attr.Factory(list), - metadata=dict( - help="List of platform tags supported by this environment."), + metadata=dict(help="List of platform tags supported by this environment."), repr=False, ) @@ -1637,8 +1620,7 @@ class PypiSimpleRepository: fetched_package_normalized_names = attr.ib( type=set, default=attr.Factory(set), - metadata=dict( - help="A set of already fetched package normalized names."), + metadata=dict(help="A set of already fetched package normalized names."), ) use_cached_index = attr.ib( @@ -1671,12 +1653,10 @@ def _get_package_versions_map(self, name): self.packages[normalized_name] = versions except RemoteNotFetchedException as e: if TRACE: - print( - f"failed to fetch package name: {name} from: {self.index_url}:\n{e}") + print(f"failed to fetch package name: {name} from: {self.index_url}:\n{e}") if not versions and TRACE: - print( - f"WARNING: package {name} not found in repo: {self.index_url}") + print(f"WARNING: package {name} not found in repo: {self.index_url}") return versions @@ -1861,8 +1841,7 @@ def get(self, path_or_url, as_text=True, force=False): if force or not os.path.exists(cached): if TRACE_DEEP: print(f" FILE CACHE MISS: {path_or_url}") - content = get_file_content( - path_or_url=path_or_url, as_text=as_text) + content = get_file_content(path_or_url=path_or_url, as_text=as_text) wmode = "w" if as_text else "wb" with open(cached, wmode) as fo: fo.write(content) @@ -1884,8 +1863,7 @@ def get_file_content(path_or_url, as_text=True): if path_or_url.startswith("https://"): if TRACE_DEEP: print(f"Fetching: {path_or_url}") - _headers, content = get_remote_file_content( - url=path_or_url, as_text=as_text) + _headers, content = get_remote_file_content(url=path_or_url, as_text=as_text) return content elif path_or_url.startswith("file://") or ( @@ -1936,7 +1914,7 @@ def get_remote_file_content( # several redirects and that we can ignore content there. A HEAD request may # not get us this last header print(f" DOWNLOADING: {url}") - with requests.get(url, allow_redirects=True, stream=True, headers=headers) as response: # noqa: S113 + with requests.get(url, allow_redirects=True, stream=True, headers=headers) as response: # noqa: S113 status = response.status_code if status != requests.codes.ok: # NOQA if status == 429 and _delay < 20: @@ -1951,8 +1929,7 @@ def get_remote_file_content( ) else: - raise RemoteNotFetchedException( - f"Failed HTTP request from {url} with {status}") + raise RemoteNotFetchedException(f"Failed HTTP request from {url} with {status}") if headers_only: return response.headers, None @@ -2043,8 +2020,7 @@ def get_other_dists(_package, _dist): # if has key data we may look to improve later, but we can move on if local_dist.has_key_metadata(): local_dist.save_about_and_notice_files(dest_dir=dest_dir) - local_dist.fetch_license_files( - dest_dir=dest_dir, use_cached_index=use_cached_index) + local_dist.fetch_license_files(dest_dir=dest_dir, use_cached_index=use_cached_index) continue # lets try to get from another dist of the same local package @@ -2056,8 +2032,7 @@ def get_other_dists(_package, _dist): # if has key data we may look to improve later, but we can move on if local_dist.has_key_metadata(): local_dist.save_about_and_notice_files(dest_dir=dest_dir) - local_dist.fetch_license_files( - dest_dir=dest_dir, use_cached_index=use_cached_index) + local_dist.fetch_license_files(dest_dir=dest_dir, use_cached_index=use_cached_index) continue # try to get another version of the same package that is not our version @@ -2068,8 +2043,7 @@ def get_other_dists(_package, _dist): ] other_local_version = other_local_packages and other_local_packages[-1] if other_local_version: - latest_local_dists = list( - other_local_version.get_distributions()) + latest_local_dists = list(other_local_version.get_distributions()) for latest_local_dist in latest_local_dists: latest_local_dist.load_about_data(dest_dir=dest_dir) if not latest_local_dist.has_key_metadata(): @@ -2095,8 +2069,7 @@ def get_other_dists(_package, _dist): # if has key data we may look to improve later, but we can move on if local_dist.has_key_metadata(): local_dist.save_about_and_notice_files(dest_dir=dest_dir) - local_dist.fetch_license_files( - dest_dir=dest_dir, use_cached_index=use_cached_index) + local_dist.fetch_license_files(dest_dir=dest_dir, use_cached_index=use_cached_index) continue # try to get a latest version of the same package that is not our version @@ -2137,8 +2110,7 @@ def get_other_dists(_package, _dist): # if local_dist.has_key_metadata() or not local_dist.has_key_metadata(): local_dist.save_about_and_notice_files(dest_dir) - lic_errs = local_dist.fetch_license_files( - dest_dir, use_cached_index=use_cached_index) + lic_errs = local_dist.fetch_license_files(dest_dir, use_cached_index=use_cached_index) if not local_dist.has_key_metadata(): print(f"Unable to add essential ABOUT data for: {local_dist}") @@ -2161,10 +2133,9 @@ def call(args, verbose=TRACE): """ if TRACE_DEEP: print("Calling:", " ".join(args)) - with subprocess.Popen( # noqa: S603 + with subprocess.Popen( # noqa: S603 args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8" ) as process: - stdouts = [] while True: line = process.stdout.readline() @@ -2260,7 +2231,7 @@ def download_wheels_with_pip( def check_about(dest_dir=THIRDPARTY_DIR): try: - subprocess.check_output(f"venv/bin/about check {dest_dir}".split()) # noqa: S603 + subprocess.check_output(f"venv/bin/about check {dest_dir}".split()) # noqa: S603 except subprocess.CalledProcessError as cpe: print() print("Invalid ABOUT files:") @@ -2286,8 +2257,7 @@ def find_problems( for dist in package.get_distributions(): dist.load_about_data(dest_dir=dest_dir) - abpth = os.path.abspath(os.path.join( - dest_dir, dist.about_filename)) + abpth = os.path.abspath(os.path.join(dest_dir, dist.about_filename)) if not dist.has_key_metadata(): print(f" Missing key ABOUT data in file://{abpth}") if "classifiers" in dist.extra_data: From 1189dda52570ed018f52eacd38c4990e8be8ff1a Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 22:02:19 +0100 Subject: [PATCH 38/78] Apply cosmetic refactorings Signed-off-by: Philippe Ombredanne --- docs/source/conf.py | 3 ++- etc/scripts/check_thirdparty.py | 4 +--- etc/scripts/fetch_thirdparty.py | 17 ++++++++--------- etc/scripts/gen_pypi_simple.py | 15 +++++++-------- etc/scripts/gen_requirements.py | 4 ++-- etc/scripts/gen_requirements_dev.py | 4 ++-- .../test_utils_pip_compatibility_tags.py | 9 +++++---- etc/scripts/utils_dejacode.py | 9 +++++---- etc/scripts/utils_pip_compatibility_tags.py | 8 +++++--- etc/scripts/utils_requirements.py | 3 +-- etc/scripts/utils_thirdparty.py | 3 ++- 11 files changed, 40 insertions(+), 39 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 8c88fa2..8aad829 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -94,7 +94,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/scripts/check_thirdparty.py b/etc/scripts/check_thirdparty.py index 1aa4e28..bb8347a 100644 --- a/etc/scripts/check_thirdparty.py +++ b/etc/scripts/check_thirdparty.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # # Copyright (c) nexB Inc. and others. All rights reserved. # ScanCode is a trademark of nexB Inc. @@ -41,8 +40,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 c224683..76a19a6 100644 --- a/etc/scripts/fetch_thirdparty.py +++ b/etc/scripts/fetch_thirdparty.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # # Copyright (c) nexB Inc. and others. All rights reserved. # ScanCode is a trademark of nexB Inc. @@ -10,7 +9,6 @@ # import itertools -import os import sys from collections import defaultdict @@ -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", @@ -261,7 +260,7 @@ 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 TRACE: @@ -276,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}") @@ -295,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 cfe68e6..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. @@ -132,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 @@ -177,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) diff --git a/etc/scripts/gen_requirements.py b/etc/scripts/gen_requirements.py index 2b65ae8..1b87944 100644 --- a/etc/scripts/gen_requirements.py +++ b/etc/scripts/gen_requirements.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # # Copyright (c) nexB Inc. and others. All rights reserved. # ScanCode is a trademark of nexB Inc. @@ -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 5db1c48..8548205 100644 --- a/etc/scripts/gen_requirements_dev.py +++ b/etc/scripts/gen_requirements_dev.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # # Copyright (c) nexB Inc. and others. All rights reserved. # ScanCode is a trademark of nexB Inc. @@ -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 a33b8b3..de4b706 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 @@ -50,7 +51,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. @@ -81,7 +82,7 @@ def test_no_hyphen_tag(self): assert "-" not in tag.platform -class TestManylinux2010Tags(object): +class TestManylinux2010Tags: @pytest.mark.parametrize( "manylinux2010,manylinux1", [ @@ -104,7 +105,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/utils_dejacode.py b/etc/scripts/utils_dejacode.py index cd39cda..b6bff51 100644 --- a/etc/scripts/utils_dejacode.py +++ b/etc/scripts/utils_dejacode.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # # Copyright (c) nexB Inc. and others. All rights reserved. # ScanCode is a trademark of nexB Inc. @@ -25,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", } @@ -50,6 +49,7 @@ def fetch_dejacode_packages(params): DEJACODE_API_URL_PACKAGES, params=params, headers=DEJACODE_API_HEADERS, + timeout=10, ) return response.json()["results"] @@ -93,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) @@ -113,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: @@ -201,6 +201,7 @@ 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: diff --git a/etc/scripts/utils_pip_compatibility_tags.py b/etc/scripts/utils_pip_compatibility_tags.py index de0ac95..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 @@ -130,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( @@ -140,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 167bc9f..b9b2c0e 100644 --- a/etc/scripts/utils_requirements.py +++ b/etc/scripts/utils_requirements.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # # Copyright (c) nexB Inc. and others. All rights reserved. # ScanCode is a trademark of nexB Inc. @@ -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) diff --git a/etc/scripts/utils_thirdparty.py b/etc/scripts/utils_thirdparty.py index 4ea1bab..aafc1d6 100644 --- a/etc/scripts/utils_thirdparty.py +++ b/etc/scripts/utils_thirdparty.py @@ -91,7 +91,8 @@ - parse requirement file - create a TODO queue of requirements to process -- done: create an empty map of processed binary requirements as {package name: (list of versions/tags} +- done: create an empty map of processed binary requirements as + {package name: (list of versions/tags} - while we have package reqs in TODO queue, process one requirement: From 84257fbe200e5780cb13536a5c8eb56a88539e6a Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 22:05:23 +0100 Subject: [PATCH 39/78] Reformat test code Signed-off-by: Philippe Ombredanne --- .gitignore | 1 + pyproject.toml | 19 +++++++++++-------- tests/test_skeleton_codestyle.py | 25 ++++++++++++++++--------- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index 2d48196..8a93c94 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,4 @@ tcl # Ignore Jupyter Notebook related temp files .ipynb_checkpoints/ +/.ruff_cache/ diff --git a/pyproject.toml b/pyproject.toml index ba55770..a872ab3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,16 +67,17 @@ include = [ [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 +# "E", # pycodestyle +# "W", # pycodestyle warnings +# "D", # pydocstyle +# "F", # Pyflakes +# "UP", # pyupgrade +# "S", # flake8-bandit "I", # isort - "C9", # McCabe complexity +# "C9", # McCabe complexity ] -ignore = ["D1", "D200", "D203", "D205", "D212", "D400", "D415"] +ignore = ["D1", "D200", "D202", "D203", "D205", "D212", "D400", "D415"] + [tool.ruff.lint.isort] force-single-line = true @@ -100,3 +101,5 @@ max-complexity = 10 [tool.ruff.lint.per-file-ignores] # Place paths of files to be ignored by ruff here +"tests/*" = ["S101"] +"test_*.py" = ["S101"] diff --git a/tests/test_skeleton_codestyle.py b/tests/test_skeleton_codestyle.py index b4ce8c1..8cd85c9 100644 --- a/tests/test_skeleton_codestyle.py +++ b/tests/test_skeleton_codestyle.py @@ -7,30 +7,37 @@ # See https://aboutcode.org for more information about nexB OSS projects. # +import configparser import subprocess import unittest -import configparser - class BaseTests(unittest.TestCase): def test_skeleton_codestyle(self): - """ - This test shouldn't run in proliferated repositories. - """ + # This test shouldn't run in proliferated repositories. + + # TODO: update with switch to pyproject.toml 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" + commands = [ + ["venv/bin/ruff", "--check"], + ["venv/bin/ruff", "format", "--check"], + ] + command = None try: - subprocess.check_output(args.split()) + for command in commands: + subprocess.check_output(command) # noqa: S603 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", + f"Code style and linting command check failed: {' '.join(command)!r}.\n" + "You can check and format the code using:\n" + " make valid\n", + "OR:\n ruff format\n", + " ruff check --fix\n", e.output, ) from e From 00684f733a0873bd837af85471814907ba93f456 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 22:08:25 +0100 Subject: [PATCH 40/78] Format code Signed-off-by: Philippe Ombredanne --- etc/scripts/check_thirdparty.py | 1 + etc/scripts/test_utils_pip_compatibility_tags.py | 1 + tests/test_skeleton_codestyle.py | 1 + 3 files changed, 3 insertions(+) diff --git a/etc/scripts/check_thirdparty.py b/etc/scripts/check_thirdparty.py index bb8347a..65ae595 100644 --- a/etc/scripts/check_thirdparty.py +++ b/etc/scripts/check_thirdparty.py @@ -11,6 +11,7 @@ import utils_thirdparty + @click.command() @click.option( "-d", diff --git a/etc/scripts/test_utils_pip_compatibility_tags.py b/etc/scripts/test_utils_pip_compatibility_tags.py index de4b706..0e9c360 100644 --- a/etc/scripts/test_utils_pip_compatibility_tags.py +++ b/etc/scripts/test_utils_pip_compatibility_tags.py @@ -33,6 +33,7 @@ import utils_pip_compatibility_tags + @pytest.mark.parametrize( "version_info, expected", [ diff --git a/tests/test_skeleton_codestyle.py b/tests/test_skeleton_codestyle.py index 8cd85c9..7135ac0 100644 --- a/tests/test_skeleton_codestyle.py +++ b/tests/test_skeleton_codestyle.py @@ -11,6 +11,7 @@ import subprocess import unittest + class BaseTests(unittest.TestCase): def test_skeleton_codestyle(self): # This test shouldn't run in proliferated repositories. From 7c4278df4e8acf04888a188d115b4c687060f1e5 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 22:10:45 +0100 Subject: [PATCH 41/78] Refine ruff configuration Signed-off-by: Philippe Ombredanne --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a872ab3..0f8bd58 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,11 +72,11 @@ select = [ # "D", # pydocstyle # "F", # Pyflakes # "UP", # pyupgrade -# "S", # flake8-bandit + "S", # flake8-bandit "I", # isort # "C9", # McCabe complexity ] -ignore = ["D1", "D200", "D202", "D203", "D205", "D212", "D400", "D415"] +ignore = ["D1", "D200", "D202", "D203", "D205", "D212", "D400", "D415", "I001"] [tool.ruff.lint.isort] From 47cb840db13d3e7328dba8d8e62197cda82e48ec Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 22:54:01 +0100 Subject: [PATCH 42/78] Format doc Signed-off-by: Philippe Ombredanne --- AUTHORS.rst | 2 +- README.rst | 29 ++++++++++++++--------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 51a19cc..16e2046 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -1,3 +1,3 @@ The following organizations or individuals have contributed to this repo: -- +- diff --git a/README.rst b/README.rst index f848b4b..01d0210 100644 --- a/README.rst +++ b/README.rst @@ -1,11 +1,10 @@ A Simple Python Project Skeleton ================================ -This repo attempts to standardize the structure of the Python-based project's repositories using -modern Python packaging and configuration techniques that can then be applied to many repos. - -Using this `blog post`_ as inspiration, this repository serves as the base for all new Python -projects and is mergeable in existing repositories as well. +This repo attempts to standardize the structure of the Python-based project's +repositories using modern Python packaging and configuration techniques. +Using this `blog post`_ as inspiration, this repository serves as the base for +all new Python projects and is mergeable in existing repositories as well. .. _blog post: https://blog.jaraco.com/a-project-skeleton-for-python-projects/ @@ -69,7 +68,7 @@ Release Notes - Drop support for Python 3.8 - Drop support for macOS-11, add support for macOS-14 - + - 2024-02-19: - Replace support in CI of default ubuntu-20.04 by ubuntu-22.04 @@ -86,19 +85,19 @@ Release Notes - Synchronize configure and configure.bat scripts for sanity - Update CI operating system support with latest Azure OS images - - Streamline utility scripts in etc/scripts/ to create, fetch and manage third-party dependencies - There are now fewer scripts. See etc/scripts/README.rst for details + - Streamline utility scripts in etc/scripts/ to create, fetch and manage third-party + dependencies. There are now fewer scripts. See etc/scripts/README.rst for details - 2021-09-03: - - - ``configure`` now requires pinned dependencies via the use of ``requirements.txt`` and ``requirements-dev.txt`` - + - ``configure`` now requires pinned dependencies via the use of ``requirements.txt`` + and ``requirements-dev.txt`` - ``configure`` can now accept multiple options at once - Add utility scripts from scancode-toolkit/etc/release/ for use in generating project files - Rename virtual environment directory from ``tmp`` to ``venv`` - - Update README.rst with instructions for generating ``requirements.txt`` and ``requirements-dev.txt``, - as well as collecting dependencies as wheels and generating ABOUT files for them. + - Update README.rst with instructions for generating ``requirements.txt`` + and ``requirements-dev.txt``, as well as collecting dependencies as wheels and generating + ABOUT files for them. - 2021-05-11: - - - Adopt new configure scripts from ScanCode TK that allows correct configuration of which Python version is used. + - Adopt new configure scripts from ScanCode TK that allows correct configuration of which + Python version is used. From 7b29b5914a443069a7ff967eb4ef096034333248 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 22:54:35 +0100 Subject: [PATCH 43/78] Run doc8 on all rst files Signed-off-by: Philippe Ombredanne --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 930e801..debc404 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ dev: doc8: @echo "-> Run doc8 validation" - @${ACTIVATE} doc8 --max-line-length 100 --ignore-path docs/_build/ --quiet docs/ + @${ACTIVATE} doc8 --max-line-length 100 --ignore-path docs/_build/ --quiet docs/ *.rst valid: @echo "-> Run Ruff format" From 86c7ca45d3132e5f6873658ab1743b6e27cfeb58 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sat, 29 Mar 2025 22:55:20 +0100 Subject: [PATCH 44/78] Enable doc style checks Signed-off-by: Philippe Ombredanne --- pyproject.toml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0f8bd58..51761ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,8 @@ include = [ "etc/**/*.py", "test/**/*.py", "doc/**/*", - "*.py" + "*.py", + "." ] [tool.ruff.lint] @@ -69,10 +70,10 @@ include = [ select = [ # "E", # pycodestyle # "W", # pycodestyle warnings -# "D", # pydocstyle + "D", # pydocstyle # "F", # Pyflakes # "UP", # pyupgrade - "S", # flake8-bandit +# "S", # flake8-bandit "I", # isort # "C9", # McCabe complexity ] From 71583c5ecdfe5dd352b7f2bb9d26deaad971e151 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sun, 30 Mar 2025 14:40:36 +0200 Subject: [PATCH 45/78] Do not format more test data Signed-off-by: Philippe Ombredanne --- pyproject.toml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 51761ff..7d807eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,10 +60,25 @@ include = [ "src/**/*.py", "etc/**/*.py", "test/**/*.py", + "tests/**/*.py", "doc/**/*", + "docs/**/*", "*.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/**/*", +] + [tool.ruff.lint] # Rules: https://docs.astral.sh/ruff/rules/ From 0f1a40382bdcadf82512395787faab50927256f6 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sun, 30 Mar 2025 14:58:36 +0200 Subject: [PATCH 46/78] Do not treat rst as Python Signed-off-by: Philippe Ombredanne --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7d807eb..5e16b56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,8 +61,8 @@ include = [ "etc/**/*.py", "test/**/*.py", "tests/**/*.py", - "doc/**/*", - "docs/**/*", + "doc/**/*.py", + "docs/**/*.py", "*.py", "." ] From 6bea6577864bf432438dabaed9e46a721aae2961 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sun, 30 Mar 2025 16:41:57 +0200 Subject: [PATCH 47/78] Combine testing and docs extra for simplicity Signed-off-by: Philippe Ombredanne --- configure | 2 -- configure.bat | 4 ---- setup.cfg | 3 --- 3 files changed, 9 deletions(-) diff --git a/configure b/configure index 22d9288..83fd203 100755 --- a/configure +++ b/configure @@ -30,7 +30,6 @@ 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" # where we create a virtualenv VIRTUALENV_DIR=venv @@ -185,7 +184,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 5b9a9d6..18b3703 100644 --- a/configure.bat +++ b/configure.bat @@ -28,7 +28,6 @@ @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" @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 ) diff --git a/setup.cfg b/setup.cfg index aaec643..ad8e0d8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -55,8 +55,6 @@ testing = pycodestyle >= 2.8.0 twine ruff - -docs = Sphinx>=5.0.2 sphinx-rtd-theme>=1.0.0 sphinx-reredirects >= 0.1.2 @@ -64,4 +62,3 @@ docs = sphinx-autobuild sphinx-rtd-dark-mode>=1.3.0 sphinx-copybutton - From c615589b54bfac74b6f17b2233b2afe60bf1d0f6 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sun, 30 Mar 2025 17:18:19 +0200 Subject: [PATCH 48/78] Refine checking of docs with doc8 Signed-off-by: Philippe Ombredanne --- Makefile | 2 +- pyproject.toml | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index debc404..d21a2f9 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ dev: doc8: @echo "-> Run doc8 validation" - @${ACTIVATE} doc8 --max-line-length 100 --ignore-path docs/_build/ --quiet docs/ *.rst + @${ACTIVATE} doc8 docs/ *.rst valid: @echo "-> Run Ruff format" diff --git a/pyproject.toml b/pyproject.toml index 5e16b56..bfb1d35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -119,3 +119,10 @@ max-complexity = 10 # 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 +verbose=0 From 04e0a89a3bbf5359f27b6e3ef9a5026638e63de8 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sun, 30 Mar 2025 18:41:00 +0200 Subject: [PATCH 49/78] Refine doc handling * remove CI scripts and use Makefile targets instead * ensure doc8 runs quiet * add new docs-check make target to run documentation and links checks * update oudated doc for docs contribution Signed-off-by: Philippe Ombredanne --- .github/workflows/docs-ci.yml | 12 +++++------- Makefile | 10 +++++++--- docs/scripts/doc8_style_check.sh | 5 ----- docs/scripts/sphinx_build_link_check.sh | 5 ----- docs/source/conf.py | 2 +- docs/source/contribute/contrib_doc.rst | 8 ++++---- pyproject.toml | 2 -- 7 files changed, 17 insertions(+), 27 deletions(-) delete mode 100755 docs/scripts/doc8_style_check.sh delete mode 100644 docs/scripts/sphinx_build_link_check.sh diff --git a/.github/workflows/docs-ci.yml b/.github/workflows/docs-ci.yml index 621de4b..10ba5fa 100644 --- a/.github/workflows/docs-ci.yml +++ b/.github/workflows/docs-ci.yml @@ -21,14 +21,12 @@ jobs: 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/Makefile b/Makefile index d21a2f9..413399e 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ dev: doc8: @echo "-> Run doc8 validation" - @${ACTIVATE} doc8 docs/ *.rst + @${ACTIVATE} doc8 --quiet docs/ *.rst valid: @echo "-> Run Ruff format" @@ -46,6 +46,10 @@ test: docs: rm -rf docs/_build/ - @${ACTIVATE} sphinx-build docs/ docs/_build/ + @${ACTIVATE} sphinx-build docs/source docs/_build/ -.PHONY: conf dev check valid clean test docs +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 clean test docs docs-check 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 8aad829..056ca6e 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -18,7 +18,7 @@ # -- Project information ----------------------------------------------------- project = "nexb-skeleton" -copyright = "nexB Inc. and others." +copyright = "nexB Inc., AboutCode and others." author = "AboutCode.org authors and contributors" diff --git a/docs/source/contribute/contrib_doc.rst b/docs/source/contribute/contrib_doc.rst index 5640db2..041b358 100644 --- a/docs/source/contribute/contrib_doc.rst +++ b/docs/source/contribute/contrib_doc.rst @@ -147,7 +147,7 @@ What is Checked? ^^^^^^^^^^^^^^^^ PyCQA is an Organization for code quality tools (and plugins) for the Python programming language. -Doc8 is a sub-project of the same Organization. Refer this `README `_ for more details. +Doc8 is a sub-project of the same Organization. Refer this `README `_ for more details. What is checked: @@ -169,11 +169,11 @@ What is checked: Interspinx ---------- -ScanCode toolkit documentation uses `Intersphinx `_ +ScanCode toolkit documentation uses `Intersphinx `_ to link to other Sphinx Documentations, to maintain links to other Aboutcode Projects. To link sections in the same documentation, standart reST labels are used. Refer -`Cross-Referencing `_ for more information. +`Cross-Referencing `_ for more information. For example:: @@ -230,7 +230,7 @@ Style Conventions for the Documentaion 1. Headings - (`Refer `_) + (`Refer `_) Normally, there are no heading levels assigned to certain characters as the structure is determined from the succession of headings. However, this convention is used in Python’s Style Guide for documenting which you may follow: diff --git a/pyproject.toml b/pyproject.toml index bfb1d35..c9e6772 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -122,7 +122,5 @@ max-complexity = 10 [tool.doc8] - ignore-path = ["docs/build", "doc/build", "docs/_build", "doc/_build"] max-line-length=100 -verbose=0 From 8897cc63eb9ef9b06a1fdc77ebfe21289c69961b Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sun, 30 Mar 2025 18:49:01 +0200 Subject: [PATCH 50/78] Add twine check to release publication Signed-off-by: Philippe Ombredanne --- .github/workflows/pypi-release.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index a66c9c8..cf0579a 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -30,12 +30,15 @@ jobs: with: 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 build twine - name: Build a binary wheel and a source tarball run: python -m build --sdist --wheel --outdir dist/ + - name: Validate wheel and sdis for Pypi + run: python -m twine check dist/* + - name: Upload built archives uses: actions/upload-artifact@v4 with: From 3d42985990860188c5fbf9f64f3fd4d14c590a65 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Sun, 30 Mar 2025 19:16:31 +0200 Subject: [PATCH 51/78] Refine doc contribution docs Signed-off-by: Philippe Ombredanne --- docs/source/contribute/contrib_doc.rst | 119 ++++++++----------------- 1 file changed, 38 insertions(+), 81 deletions(-) diff --git a/docs/source/contribute/contrib_doc.rst b/docs/source/contribute/contrib_doc.rst index 041b358..dee9296 100644 --- a/docs/source/contribute/contrib_doc.rst +++ b/docs/source/contribute/contrib_doc.rst @@ -8,109 +8,59 @@ Contributing to the Documentation Setup Local Build ----------------- -To get started, create or identify a working directory on your local machine. +To get started, check out and configure the repository for development:: -Open that directory and execute the following command in a terminal session:: + git clone https://github.com/aboutcode-org/.git - git clone https://github.com/aboutcode-org/skeleton.git + cd your-repo + ./configure --dev -That will create an ``/skeleton`` directory in your working directory. -Now you can install the dependencies in a virtualenv:: - - cd skeleton - ./configure --docs +(Or use "make dev") .. note:: - In case of windows, run ``configure --docs`` instead of this. - -Now, this will install the following prerequisites: - -- Sphinx -- sphinx_rtd_theme (the format theme used by ReadTheDocs) -- docs8 (style linter) + In case of windows, run ``configure --dev``. -These requirements are already present in setup.cfg and `./configure --docs` installs them. +This will install and configure all requirements foer development including for docs development. -Now you can build the HTML documents locally:: +Now you can build the HTML documentation locally:: source venv/bin/activate - cd docs - make html - -Assuming that your Sphinx installation was successful, Sphinx should build a local instance of the -documentation .html files:: - - open build/html/index.html - -.. note:: - - In case this command did not work, for example on Ubuntu 18.04 you may get a message like “Couldn’t - get a file descriptor referring to the console”, try: - - :: - - see build/html/index.html + make docs -You now have a local build of the AboutCode documents. +This will build a local instance of the ``docs/_build`` directory:: -.. _contrib_doc_share_improvements: + open docs/_build/index.html -Share Document Improvements ---------------------------- - -Ensure that you have the latest files:: - - git pull - git status -Before commiting changes run Continious Integration Scripts locally to run tests. Refer -:ref:`doc_ci` for instructions on the same. +To validate the documentation style and content, use:: -Follow standard git procedures to upload your new and modified files. The following commands are -examples:: - - git status - git add source/index.rst - git add source/how-to-scan.rst - git status - git commit -m "New how-to document that explains how to scan" - git status - git push - git status - -The Scancode-Toolkit webhook with ReadTheDocs should rebuild the documentation after your -Pull Request is Merged. + source venv/bin/activate + make doc8 + make docs-check -Refer the `Pro Git Book `_ available online for Git tutorials -covering more complex topics on Branching, Merging, Rebasing etc. .. _doc_ci: Continuous Integration ---------------------- -The documentations are checked on every new commit through Travis-CI, so that common errors are -avoided and documentation standards are enforced. Travis-CI presently checks for these 3 aspects -of the documentation : +The documentations are checked on every new commit, so that common errors are avoided and +documentation standards are enforced. We checks for these aspects of the documentation: 1. Successful Builds (By using ``sphinx-build``) -2. No Broken Links (By Using ``link-check``) -3. Linting Errors (By Using ``Doc8``) +2. No Broken Links (By Using ``linkcheck``) +3. Linting Errors (By Using ``doc8``) -So run these scripts at your local system before creating a Pull Request:: +You myst run these scripts locally before creating a pull request:: - cd docs - ./scripts/sphinx_build_link_check.sh - ./scripts/doc8_style_check.sh + make doc8 + make check-docs -If you don't have permission to run the scripts, run:: - - chmod u+x ./scripts/doc8_style_check.sh .. _doc_style_docs8: -Style Checks Using ``Doc8`` +Style Checks Using ``doc8`` --------------------------- How To Run Style Tests @@ -118,8 +68,7 @@ How To Run Style Tests In the project root, run the following commands:: - $ cd docs - $ ./scripts/doc8_style_check.sh + make doc8 A sample output is:: @@ -143,11 +92,13 @@ A sample output is:: Now fix the errors and run again till there isn't any style error in the documentation. + What is Checked? ^^^^^^^^^^^^^^^^ PyCQA is an Organization for code quality tools (and plugins) for the Python programming language. -Doc8 is a sub-project of the same Organization. Refer this `README `_ for more details. +Doc8 is a sub-project of the same Organization. Refer this +`README `_ for more details. What is checked: @@ -164,16 +115,19 @@ What is checked: - no carriage returns (use UNIX newlines) - D004 - no newline at end of file - D005 + .. _doc_interspinx: Interspinx ---------- -ScanCode toolkit documentation uses `Intersphinx `_ +AboutCode documentation uses +`Intersphinx `_ to link to other Sphinx Documentations, to maintain links to other Aboutcode Projects. To link sections in the same documentation, standart reST labels are used. Refer -`Cross-Referencing `_ for more information. +`Cross-Referencing `_ +for more information. For example:: @@ -223,6 +177,7 @@ Intersphinx, and you link to that label, it will create a link to the local labe For more information, refer this tutorial named `Using Intersphinx `_. + .. _doc_style_conv: Style Conventions for the Documentaion @@ -303,12 +258,14 @@ Style Conventions for the Documentaion ``rst_snippets/warning_snippets/`` and then included to eliminate redundancy, as these are frequently used in multiple files. + Converting from Markdown ------------------------ -If you want to convert a ``.md`` file to a ``.rst`` file, this `tool `_ -does it pretty well. You'd still have to clean up and check for errors as this contains a lot of -bugs. But this is definitely better than converting everything by yourself. +If you want to convert a ``.md`` file to a ``.rst`` file, this +`tool `_ does it pretty well. +You will still have to clean up and check for errors as this contains a lot of bugs. But this is +definitely better than converting everything by yourself. This will be helpful in converting GitHub wiki's (Markdown Files) to reStructuredtext files for Sphinx/ReadTheDocs hosting. From f428366859a143add93160bd0b1ae685be89fcbb Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Mon, 31 Mar 2025 13:35:36 -0700 Subject: [PATCH 52/78] Update codestyle command * Remove trailing whitespace Signed-off-by: Jono Yang --- docs/source/contribute/contrib_doc.rst | 4 ++-- tests/test_skeleton_codestyle.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/contribute/contrib_doc.rst b/docs/source/contribute/contrib_doc.rst index dee9296..2a719a5 100644 --- a/docs/source/contribute/contrib_doc.rst +++ b/docs/source/contribute/contrib_doc.rst @@ -97,7 +97,7 @@ What is Checked? ^^^^^^^^^^^^^^^^ PyCQA is an Organization for code quality tools (and plugins) for the Python programming language. -Doc8 is a sub-project of the same Organization. Refer this +Doc8 is a sub-project of the same Organization. Refer this `README `_ for more details. What is checked: @@ -263,7 +263,7 @@ Converting from Markdown ------------------------ If you want to convert a ``.md`` file to a ``.rst`` file, this -`tool `_ does it pretty well. +`tool `_ does it pretty well. You will still have to clean up and check for errors as this contains a lot of bugs. But this is definitely better than converting everything by yourself. diff --git a/tests/test_skeleton_codestyle.py b/tests/test_skeleton_codestyle.py index 7135ac0..6060c08 100644 --- a/tests/test_skeleton_codestyle.py +++ b/tests/test_skeleton_codestyle.py @@ -23,7 +23,7 @@ def test_skeleton_codestyle(self): return commands = [ - ["venv/bin/ruff", "--check"], + ["venv/bin/ruff", "check"], ["venv/bin/ruff", "format", "--check"], ] command = None From f0d0e21d5e6f98645b02ff9a8fee6ee3def1be75 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Mon, 31 Mar 2025 13:44:30 -0700 Subject: [PATCH 53/78] Update README.rst Signed-off-by: Jono Yang --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index 01d0210..11a4dfb 100644 --- a/README.rst +++ b/README.rst @@ -44,6 +44,10 @@ More usage instructions can be found in ``docs/skeleton-usage.rst``. Release Notes ============= +- 2025-03-31: + + - Use ruff as the main code formatting tool, add ruff rules to pyproject.toml + - 2025-03-29: - Add support for beta macOS-15 From f3a8aa6cee5f645a668750ab7e6bf0cdc774e041 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Mon, 31 Mar 2025 14:31:37 -0700 Subject: [PATCH 54/78] Update BUILDDIR envvar in docs/Makefile Signed-off-by: Jono Yang --- docs/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: From 5b0f4d6b4079719caa9ed97efb2ba776bc2bbac1 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Tue, 1 Apr 2025 14:42:52 +0200 Subject: [PATCH 55/78] Fix doc line length Signed-off-by: Philippe Ombredanne --- docs/source/contribute/contrib_doc.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/contribute/contrib_doc.rst b/docs/source/contribute/contrib_doc.rst index dee9296..2a719a5 100644 --- a/docs/source/contribute/contrib_doc.rst +++ b/docs/source/contribute/contrib_doc.rst @@ -97,7 +97,7 @@ What is Checked? ^^^^^^^^^^^^^^^^ PyCQA is an Organization for code quality tools (and plugins) for the Python programming language. -Doc8 is a sub-project of the same Organization. Refer this +Doc8 is a sub-project of the same Organization. Refer this `README `_ for more details. What is checked: @@ -263,7 +263,7 @@ Converting from Markdown ------------------------ If you want to convert a ``.md`` file to a ``.rst`` file, this -`tool `_ does it pretty well. +`tool `_ does it pretty well. You will still have to clean up and check for errors as this contains a lot of bugs. But this is definitely better than converting everything by yourself. From e776fef5ad595378752d39425109a4cbd2cb5175 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Tue, 1 Apr 2025 14:49:40 +0200 Subject: [PATCH 56/78] Format code Signed-off-by: Philippe Ombredanne --- etc/scripts/update_skeleton.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/etc/scripts/update_skeleton.py b/etc/scripts/update_skeleton.py index 5705fc4..374c06f 100644 --- a/etc/scripts/update_skeleton.py +++ b/etc/scripts/update_skeleton.py @@ -15,7 +15,7 @@ import click -ABOUTCODE_PUBLIC_REPO_NAMES=[ +ABOUTCODE_PUBLIC_REPO_NAMES = [ "aboutcode-toolkit", "ahocode", "bitcode", @@ -87,7 +87,9 @@ def update_skeleton_files(repo_names=ABOUTCODE_PUBLIC_REPO_NAMES): 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"]) + subprocess.run( + ["git", "remote", "add", "skeleton", "git@github.com:aboutcode-org/skeleton.git"] + ) # Fetch skeleton files subprocess.run(["git", "fetch", "skeleton"]) From 2a43f4cdc8105473b279eea873db00addaa14551 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Tue, 1 Apr 2025 14:59:05 +0200 Subject: [PATCH 57/78] Correct supported runner on Azure See for details: https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops&tabs=yaml macOS ARM images do not seem to be supported there Signed-off-by: Philippe Ombredanne --- azure-pipelines.yml | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 80ae45b..fb03c09 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -26,14 +26,6 @@ jobs: - template: etc/ci/azure-posix.yml parameters: job_name: macos13_cpython - image_name: macOS-13-xlarge - 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_arm64 image_name: macOS-13 python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] test_suites: @@ -42,14 +34,6 @@ jobs: - template: etc/ci/azure-posix.yml parameters: job_name: macos14_cpython - image_name: macOS-14-large - 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: macos14_cpython_arm64 image_name: macOS-14 python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] test_suites: @@ -63,14 +47,6 @@ jobs: test_suites: all: venv/bin/pytest -n 2 -vvs - - template: etc/ci/azure-posix.yml - parameters: - job_name: macos15_cpython_arm64 - image_name: macOS-15-large - 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 From 4a15550b7bcea5ec949a5049fe1a501d3bb888ff Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Tue, 1 Apr 2025 19:34:14 +0200 Subject: [PATCH 58/78] Add code checks to CI Remove running "make check" as a test Signed-off-by: Philippe Ombredanne --- azure-pipelines.yml | 8 ++++++ tests/test_skeleton_codestyle.py | 44 -------------------------------- 2 files changed, 8 insertions(+), 44 deletions(-) delete mode 100644 tests/test_skeleton_codestyle.py diff --git a/azure-pipelines.yml b/azure-pipelines.yml index fb03c09..ad18b28 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -7,6 +7,14 @@ jobs: + - template: etc/ci/azure-posix.yml + parameters: + job_name: run_code_checks + image_name: ubuntu-24.04 + python_versions: ['3.12'] + test_suites: + all: make check + - template: etc/ci/azure-posix.yml parameters: job_name: ubuntu22_cpython diff --git a/tests/test_skeleton_codestyle.py b/tests/test_skeleton_codestyle.py deleted file mode 100644 index 6060c08..0000000 --- a/tests/test_skeleton_codestyle.py +++ /dev/null @@ -1,44 +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/aboutcode-org/skeleton for support or download. -# See https://aboutcode.org for more information about nexB OSS projects. -# - -import configparser -import subprocess -import unittest - - -class BaseTests(unittest.TestCase): - def test_skeleton_codestyle(self): - # This test shouldn't run in proliferated repositories. - - # TODO: update with switch to pyproject.toml - setup_cfg = configparser.ConfigParser() - setup_cfg.read("setup.cfg") - if setup_cfg["metadata"]["name"] != "skeleton": - return - - commands = [ - ["venv/bin/ruff", "check"], - ["venv/bin/ruff", "format", "--check"], - ] - command = None - try: - for command in commands: - subprocess.check_output(command) # noqa: S603 - except subprocess.CalledProcessError as e: - print("===========================================================") - print(e.output) - print("===========================================================") - raise Exception( - f"Code style and linting command check failed: {' '.join(command)!r}.\n" - "You can check and format the code using:\n" - " make valid\n", - "OR:\n ruff format\n", - " ruff check --fix\n", - e.output, - ) from e From b2d7512735ca257088d2cac4b55590fc5d7b20b4 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Tue, 1 Apr 2025 20:15:18 +0200 Subject: [PATCH 59/78] Revert support for Python 3.13 This is not yet supported everywhere Signed-off-by: Philippe Ombredanne --- azure-pipelines.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ad18b28..7a2d4d9 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -19,7 +19,7 @@ jobs: parameters: job_name: ubuntu22_cpython image_name: ubuntu-22.04 - python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] + python_versions: ['3.9', '3.10', '3.11', '3.12'] test_suites: all: venv/bin/pytest -n 2 -vvs @@ -27,7 +27,7 @@ jobs: parameters: job_name: ubuntu24_cpython image_name: ubuntu-24.04 - python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] + python_versions: ['3.9', '3.10', '3.11', '3.12'] test_suites: all: venv/bin/pytest -n 2 -vvs @@ -35,7 +35,7 @@ jobs: parameters: job_name: macos13_cpython image_name: macOS-13 - python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] + python_versions: ['3.9', '3.10', '3.11', '3.12'] test_suites: all: venv/bin/pytest -n 2 -vvs @@ -43,7 +43,7 @@ jobs: parameters: job_name: macos14_cpython image_name: macOS-14 - python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] + python_versions: ['3.9', '3.10', '3.11', '3.12'] test_suites: all: venv/bin/pytest -n 2 -vvs @@ -51,7 +51,7 @@ jobs: parameters: job_name: macos15_cpython image_name: macOS-15 - python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] + python_versions: ['3.9', '3.10', '3.11', '3.12'] test_suites: all: venv/bin/pytest -n 2 -vvs @@ -59,7 +59,7 @@ jobs: parameters: job_name: win2019_cpython image_name: windows-2019 - python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] + python_versions: ['3.9', '3.10', '3.11', '3.12'] test_suites: all: venv\Scripts\pytest -n 2 -vvs @@ -67,7 +67,7 @@ jobs: parameters: job_name: win2022_cpython image_name: windows-2022 - python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] + python_versions: ['3.9', '3.10', '3.11', '3.12'] test_suites: all: venv\Scripts\pytest -n 2 -vvs @@ -75,6 +75,6 @@ jobs: parameters: job_name: win2025_cpython image_name: windows-2025 - python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] + python_versions: ['3.9', '3.10', '3.11', '3.12'] test_suites: all: venv\Scripts\pytest -n 2 -vvs From 2e3464b79811bcf505d93692da2418e2444150ed Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Wed, 2 Apr 2025 16:52:32 +0200 Subject: [PATCH 60/78] Ignore local .env file Signed-off-by: Philippe Ombredanne --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8a93c94..4818bb3 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,4 @@ tcl # Ignore Jupyter Notebook related temp files .ipynb_checkpoints/ /.ruff_cache/ +.env \ No newline at end of file From d4af79f0da82ab0d16dcf60b363a6a5290cd9403 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Wed, 2 Apr 2025 16:54:29 +0200 Subject: [PATCH 61/78] Add correct extras for documentation Signed-off-by: Philippe Ombredanne --- .readthedocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 8ab2368..7e399c8 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -26,4 +26,4 @@ python: - method: pip path: . extra_requirements: - - docs + - testing From 49bfd37c7273f2118d35585c67e53f0cf7642f43 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Wed, 2 Apr 2025 16:58:37 +0200 Subject: [PATCH 62/78] Improve MANIFEST Signed-off-by: Philippe Ombredanne --- MANIFEST.in | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index ef3721e..0f19707 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 .git* +include .dockerignore +include .gitignore +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__ *.*~ From 5bc987a16cb3ae0f6de101a9c9e277df431f4317 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Wed, 2 Apr 2025 17:12:58 +0200 Subject: [PATCH 63/78] Improve cleaning on POSIX Signed-off-by: Philippe Ombredanne --- configure | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/configure b/configure index 83fd203..3dd9a0a 100755 --- a/configure +++ b/configure @@ -35,7 +35,7 @@ DEV_REQUIREMENTS="--editable .[testing] --constraint requirements.txt --constrai 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=" " @@ -167,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 } From 887779a9bd36650ffc6751f0069b492e80dd2f08 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Wed, 2 Apr 2025 17:19:44 +0200 Subject: [PATCH 64/78] Rename dev extra to "dev" Instead of testing ... and update references accordingly Signed-off-by: Philippe Ombredanne --- .readthedocs.yml | 2 +- Makefile | 7 ++++++- configure | 2 +- configure.bat | 2 +- setup.cfg | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 7e399c8..683f3a8 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -26,4 +26,4 @@ python: - method: pip path: . extra_requirements: - - testing + - dev diff --git a/Makefile b/Makefile index 413399e..3041547 100644 --- a/Makefile +++ b/Makefile @@ -13,8 +13,13 @@ PYTHON_EXE?=python3 VENV=venv ACTIVATE?=. ${VENV}/bin/activate; + +conf: + @echo "-> Install dependencies" + ./configure + dev: - @echo "-> Configure the development envt." + @echo "-> Configure and install development dependencies" ./configure --dev doc8: diff --git a/configure b/configure index 3dd9a0a..5ef0e06 100755 --- a/configure +++ b/configure @@ -29,7 +29,7 @@ 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" +DEV_REQUIREMENTS="--editable .[dev] --constraint requirements.txt --constraint requirements-dev.txt" # where we create a virtualenv VIRTUALENV_DIR=venv diff --git a/configure.bat b/configure.bat index 18b3703..3e9881f 100644 --- a/configure.bat +++ b/configure.bat @@ -27,7 +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 "DEV_REQUIREMENTS=--editable .[dev] --constraint requirements.txt --constraint requirements-dev.txt" @rem # where we create a virtualenv set "VIRTUALENV_DIR=venv" diff --git a/setup.cfg b/setup.cfg index ad8e0d8..99ba260 100644 --- a/setup.cfg +++ b/setup.cfg @@ -48,7 +48,7 @@ where = src [options.extras_require] -testing = +dev = pytest >= 6, != 7.0.0 pytest-xdist >= 2 aboutcode-toolkit >= 7.0.2 From 209231f0d27de0b0cfcc51eeb0fbaf9393d3df1c Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Wed, 2 Apr 2025 17:50:44 +0200 Subject: [PATCH 65/78] Add more excludes from tests Signed-off-by: Philippe Ombredanne --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c9e6772..bcca1a8 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" From 47bce2da33db6b3ce3bb16831ddca89a65494e23 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Wed, 2 Apr 2025 17:54:23 +0200 Subject: [PATCH 66/78] Do not lint django migrations Signed-off-by: Philippe Ombredanne --- pyproject.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bcca1a8..d79574e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,6 +66,7 @@ include = [ "docs/**/*.py", "*.py", "." + ] # ignore test data and testfiles: they should never be linted nor formatted exclude = [ @@ -78,9 +79,10 @@ exclude = [ # vulnerablecode, fetchcode "**/tests/*/test_data/**/*", "**/tests/test_data/**/*", +# django migrations + "**/migrations/**/*" ] - [tool.ruff.lint] # Rules: https://docs.astral.sh/ruff/rules/ select = [ From 5025cfb59f0555bf4b40cd75e75ce41188e19e11 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Wed, 2 Apr 2025 17:58:07 +0200 Subject: [PATCH 67/78] Add README.rst to list of "license files" Signed-off-by: Philippe Ombredanne --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 99ba260..e5b56da 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,6 +28,7 @@ license_files = AUTHORS.rst CHANGELOG.rst CODE_OF_CONDUCT.rst + README.rst [options] package_dir = From 548a72eac69e4400e4b01f22941d38fe1cb4648d Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Wed, 2 Apr 2025 18:59:08 +0200 Subject: [PATCH 68/78] Use Python 3.9 as lowest suupported version Signed-off-by: Philippe Ombredanne --- setup.cfg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index e5b56da..a9c5dbc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,6 +31,8 @@ license_files = README.rst [options] +python_requires = >=3.9 + package_dir = =src packages = find: @@ -39,7 +41,6 @@ zip_safe = false setup_requires = setuptools_scm[toml] >= 4 -python_requires = >=3.8 install_requires = From 3d256b4ac7976b46c23424e86bb62a38f0e4a095 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Wed, 2 Apr 2025 19:01:22 +0200 Subject: [PATCH 69/78] Drop pycodestyle Not used anymore Signed-off-by: Philippe Ombredanne --- setup.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index a9c5dbc..6d0b648 100644 --- a/setup.cfg +++ b/setup.cfg @@ -54,7 +54,6 @@ dev = pytest >= 6, != 7.0.0 pytest-xdist >= 2 aboutcode-toolkit >= 7.0.2 - pycodestyle >= 2.8.0 twine ruff Sphinx>=5.0.2 From 645052974bf7e6f45c1e55a24a2acaa0cee24523 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Wed, 2 Apr 2025 19:26:17 +0200 Subject: [PATCH 70/78] Bump pytest minimal version Signed-off-by: Philippe Ombredanne --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 6d0b648..69f850c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -51,7 +51,7 @@ where = src [options.extras_require] dev = - pytest >= 6, != 7.0.0 + pytest >= 7.0.1 pytest-xdist >= 2 aboutcode-toolkit >= 7.0.2 twine From af87cfab2d06fb034a90412a87e0d4e660e214ee Mon Sep 17 00:00:00 2001 From: Ayan Sinha Mahapatra Date: Wed, 25 Jun 2025 00:40:47 +0530 Subject: [PATCH 71/78] Update CI runners and scripts Signed-off-by: Ayan Sinha Mahapatra --- .github/workflows/docs-ci.yml | 2 +- .github/workflows/pypi-release.yml | 4 +- azure-pipelines.yml | 24 ++++-------- configure | 2 +- configure.bat | 4 +- etc/ci/azure-container-deb.yml | 2 +- etc/ci/azure-container-rpm.yml | 2 +- etc/scripts/utils_thirdparty.py | 61 +++++++++++++++--------------- 8 files changed, 46 insertions(+), 55 deletions(-) diff --git a/.github/workflows/docs-ci.yml b/.github/workflows/docs-ci.yml index 10ba5fa..8d8aa55 100644 --- a/.github/workflows/docs-ci.yml +++ b/.github/workflows/docs-ci.yml @@ -9,7 +9,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: [3.12] + python-version: [3.13] steps: - name: Checkout code diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index cf0579a..7f81361 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -31,10 +31,10 @@ jobs: python-version: 3.12 - name: Install pypa/build and twine - run: python -m pip install --user build 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 --sdist --outdir dist/ - name: Validate wheel and sdis for Pypi run: python -m twine check dist/* diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7a2d4d9..4d347b7 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -11,7 +11,7 @@ jobs: parameters: job_name: run_code_checks image_name: ubuntu-24.04 - python_versions: ['3.12'] + python_versions: ['3.13'] test_suites: all: make check @@ -19,7 +19,7 @@ jobs: parameters: job_name: ubuntu22_cpython image_name: ubuntu-22.04 - python_versions: ['3.9', '3.10', '3.11', '3.12'] + python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] test_suites: all: venv/bin/pytest -n 2 -vvs @@ -27,7 +27,7 @@ jobs: parameters: job_name: ubuntu24_cpython image_name: ubuntu-24.04 - python_versions: ['3.9', '3.10', '3.11', '3.12'] + python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] test_suites: all: venv/bin/pytest -n 2 -vvs @@ -35,7 +35,7 @@ jobs: parameters: job_name: macos13_cpython image_name: macOS-13 - python_versions: ['3.9', '3.10', '3.11', '3.12'] + python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] test_suites: all: venv/bin/pytest -n 2 -vvs @@ -43,7 +43,7 @@ jobs: parameters: job_name: macos14_cpython image_name: macOS-14 - python_versions: ['3.9', '3.10', '3.11', '3.12'] + python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] test_suites: all: venv/bin/pytest -n 2 -vvs @@ -51,23 +51,15 @@ jobs: parameters: job_name: macos15_cpython image_name: macOS-15 - python_versions: ['3.9', '3.10', '3.11', '3.12'] + 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.9', '3.10', '3.11', '3.12'] - test_suites: - all: venv\Scripts\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'] + python_versions: ['3.9', '3.10', '3.11', '3.12', '3.13'] test_suites: all: venv\Scripts\pytest -n 2 -vvs @@ -75,6 +67,6 @@ jobs: parameters: job_name: win2025_cpython image_name: windows-2025 - python_versions: ['3.9', '3.10', '3.11', '3.12'] + 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 5ef0e06..6d317d4 100755 --- a/configure +++ b/configure @@ -110,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 \ diff --git a/configure.bat b/configure.bat index 3e9881f..15ab701 100644 --- a/configure.bat +++ b/configure.bat @@ -110,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 ^ @@ -126,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/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/utils_thirdparty.py b/etc/scripts/utils_thirdparty.py index aafc1d6..6f812f0 100644 --- a/etc/scripts/utils_thirdparty.py +++ b/etc/scripts/utils_thirdparty.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- # # Copyright (c) nexB Inc. and others. All rights reserved. # ScanCode is a trademark of nexB Inc. @@ -24,13 +25,14 @@ import packageurl import requests import saneyaml -import utils_pip_compatibility_tags from commoncode import fileutils from commoncode.hash import multi_checksums from commoncode.text import python_safe_name from packvers import tags as packaging_tags from packvers import version as packaging_version +import utils_pip_compatibility_tags + """ Utilities to manage Python thirparty libraries source, binaries and metadata in local directories and remote repositories. @@ -91,8 +93,7 @@ - parse requirement file - create a TODO queue of requirements to process -- done: create an empty map of processed binary requirements as - {package name: (list of versions/tags} +- done: create an empty map of processed binary requirements as {package name: (list of versions/tags} - while we have package reqs in TODO queue, process one requirement: @@ -114,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", } @@ -132,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 = { @@ -553,8 +556,7 @@ def download(self, dest_dir=THIRDPARTY_DIR): Download this distribution into `dest_dir` directory. Return the fetched filename. """ - if not self.filename: - raise ValueError(f"self.filename has no value but is required: {self.filename!r}") + assert self.filename if TRACE_DEEP: print( f"Fetching distribution of {self.name}=={self.version}:", @@ -822,9 +824,9 @@ def fetch_license_files(self, dest_dir=THIRDPARTY_DIR, use_cached_index=False): """ urls = LinksRepository.from_url(use_cached_index=use_cached_index).links errors = [] - extra_lic_names = [lic.get("file") for lic in self.extra_data.get("licenses", {})] + extra_lic_names = [l.get("file") for l in self.extra_data.get("licenses", {})] extra_lic_names += [self.extra_data.get("license_file")] - extra_lic_names = [eln for eln in extra_lic_names if eln] + extra_lic_names = [ln for ln in extra_lic_names if ln] lic_names = [f"{key}.LICENSE" for key in self.get_license_keys()] for filename in lic_names + extra_lic_names: floc = os.path.join(dest_dir, filename) @@ -844,7 +846,7 @@ def fetch_license_files(self, dest_dir=THIRDPARTY_DIR, use_cached_index=False): if TRACE: print(f"Fetched license from remote: {lic_url}") - except Exception: + except: try: # try licensedb second lic_url = f"{LICENSEDB_API_URL}/{filename}" @@ -857,9 +859,8 @@ def fetch_license_files(self, dest_dir=THIRDPARTY_DIR, use_cached_index=False): if TRACE: print(f"Fetched license from licensedb: {lic_url}") - except Exception: - msg = f"No text for license {filename} in expression " - f"{self.license_expression!r} from {self}" + except: + msg = f'No text for license {filename} in expression "{self.license_expression}" from {self}' print(msg) errors.append(msg) @@ -999,7 +1000,7 @@ def get_license_link_for_filename(filename, urls): exception if no link is found or if there are more than one link for that file name. """ - path_or_url = [url for url in urls if url.endswith(f"/{filename}")] + path_or_url = [l for l in urls if l.endswith(f"/{filename}")] if not path_or_url: raise Exception(f"Missing link to file: {filename}") if not len(path_or_url) == 1: @@ -1288,7 +1289,7 @@ def is_pure(self): def is_pure_wheel(filename): try: return Wheel.from_filename(filename).is_pure() - except Exception: + except: return False @@ -1484,7 +1485,8 @@ def get_distributions(self): """ if self.sdist: yield self.sdist - yield from self.wheels + for wheel in self.wheels: + yield wheel def get_url_for_filename(self, filename): """ @@ -1613,8 +1615,7 @@ class PypiSimpleRepository: type=dict, default=attr.Factory(lambda: defaultdict(dict)), metadata=dict( - help="Mapping of {name: {version: PypiPackage, version: PypiPackage, etc} " - "available in this repo" + help="Mapping of {name: {version: PypiPackage, version: PypiPackage, etc} available in this repo" ), ) @@ -1628,8 +1629,7 @@ class PypiSimpleRepository: type=bool, default=False, metadata=dict( - help="If True, use any existing on-disk cached PyPI index files. " - "Otherwise, fetch and cache." + help="If True, use any existing on-disk cached PyPI index files. Otherwise, fetch and cache." ), ) @@ -1638,8 +1638,7 @@ def _get_package_versions_map(self, name): Return a mapping of all available PypiPackage version for this package name. The mapping may be empty. It is ordered by version from oldest to newest """ - if not name: - raise ValueError(f"name is required: {name!r}") + assert name normalized_name = NameVer.normalize_name(name) versions = self.packages[normalized_name] if not versions and normalized_name not in self.fetched_package_normalized_names: @@ -1694,7 +1693,7 @@ def fetch_links(self, normalized_name): ) links = collect_urls(text) # TODO: keep sha256 - links = [link.partition("#sha256=") for link in links] + links = [l.partition("#sha256=") for l in links] links = [url for url, _, _sha256 in links] return links @@ -1915,7 +1914,7 @@ def get_remote_file_content( # several redirects and that we can ignore content there. A HEAD request may # not get us this last header print(f" DOWNLOADING: {url}") - with requests.get(url, allow_redirects=True, stream=True, headers=headers) as response: # noqa: S113 + with requests.get(url, allow_redirects=True, stream=True, headers=headers) as response: status = response.status_code if status != requests.codes.ok: # NOQA if status == 429 and _delay < 20: @@ -2134,7 +2133,7 @@ def call(args, verbose=TRACE): """ if TRACE_DEEP: print("Calling:", " ".join(args)) - with subprocess.Popen( # noqa: S603 + with subprocess.Popen( args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8" ) as process: stdouts = [] @@ -2199,7 +2198,7 @@ def download_wheels_with_pip( cli_args.extend(["--requirement", req_file]) if TRACE: - print("Downloading wheels using command:", " ".join(cli_args)) + print(f"Downloading wheels using command:", " ".join(cli_args)) existing = set(os.listdir(dest_dir)) error = False @@ -2232,7 +2231,7 @@ def download_wheels_with_pip( def check_about(dest_dir=THIRDPARTY_DIR): try: - subprocess.check_output(f"venv/bin/about check {dest_dir}".split()) # noqa: S603 + subprocess.check_output(f"venv/bin/about check {dest_dir}".split()) except subprocess.CalledProcessError as cpe: print() print("Invalid ABOUT files:") @@ -2283,5 +2282,5 @@ def get_license_expression(declared_licenses): return get_only_expression_from_extracted_license(declared_licenses) except ImportError: # Scancode is not installed, clean and join all the licenses - lics = [python_safe_name(lic).lower() for lic in declared_licenses] + lics = [python_safe_name(l).lower() for l in declared_licenses] return " AND ".join(lics).lower() From 72c7d266275a472073d2829a25d730ada9436ab3 Mon Sep 17 00:00:00 2001 From: Ayan Sinha Mahapatra Date: Wed, 25 Jun 2025 18:16:30 +0530 Subject: [PATCH 72/78] Add missing wheel builds on release CI Signed-off-by: Ayan Sinha Mahapatra --- .github/workflows/pypi-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index 7f81361..d41fbf2 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -34,7 +34,7 @@ jobs: run: python -m pip install --user --upgrade build twine pkginfo - name: Build a binary wheel and a source tarball - run: python -m build --sdist --outdir dist/ + run: python -m build --wheel --sdist --outdir dist/ - name: Validate wheel and sdis for Pypi run: python -m twine check dist/* From 4b96eff9ae0fdbe168ee70120604a1e48efd8c39 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Wed, 1 Oct 2025 17:46:23 -0700 Subject: [PATCH 73/78] Bump version to 31.1.1 Signed-off-by: Jono Yang --- CHANGELOG.rst | 4 ++++ setup.cfg | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7f44d0a..8e38aa2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,10 @@ Changelog ========= +v31.1.1 - 2025-10-01 +------------------------ + +- Add ~ to valid characters in ``_is_valid_version`` v31.1.0 - 2024-02-01 ------------------------ diff --git a/setup.cfg b/setup.cfg index 520ace8..882732b 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 From 1c612a9b93a8b8b0771b16351dd05494927d988a Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Wed, 1 Oct 2025 17:58:58 -0700 Subject: [PATCH 74/78] Update requirements files Signed-off-by: Jono Yang --- etc/scripts/utils_requirements.py | 2 +- requirements-dev.txt | 109 ++++++++++++++++++++++-------- requirements.txt | 80 ++-------------------- 3 files changed, 84 insertions(+), 107 deletions(-) diff --git a/etc/scripts/utils_requirements.py b/etc/scripts/utils_requirements.py index b9b2c0e..424bed2 100644 --- a/etc/scripts/utils_requirements.py +++ b/etc/scripts/utils_requirements.py @@ -153,7 +153,7 @@ def split_req(req): if not req: raise ValueError("req is required") # do not allow multiple constraints and tags - if 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): diff --git a/requirements-dev.txt b/requirements-dev.txt index bd08a7b..a835705 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,31 +1,80 @@ -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==1.0.0 +anyio==4.11.0 +babel==2.17.0 +beautifulsoup4==4.14.2 +boolean.py==5.0 +certifi==2025.8.3 +cffi==2.0.0 +charset-normalizer==3.4.3 +click==8.3.0 +colorama==0.4.6 +commoncode==32.3.0 +cryptography==46.0.2 +doc8==2.0.0 +docutils==0.21.2 +et_xmlfile==2.0.0 +execnet==2.1.1 +h11==0.16.0 +id==1.5.0 +idna==3.10 +imagesize==1.4.1 +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==4.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 +roman-numerals-py==3.1.0 +ruff==0.13.2 +saneyaml==0.6.1 +secretstorage==3.4.0 +sniffio==1.3.1 +snowballstemmer==3.0.1 +soupsieve==2.8 +sphinx==8.2.3 +sphinx-autobuild==2025.8.25 +sphinx-copybutton==0.5.2 +sphinx-reredirects==1.0.0 +sphinx-rtd-dark-mode==1.3.0 +sphinx-rtd-theme==3.0.2 +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 +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 \ 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 From d93e083895f8341f50314cc4e0ffe3105ee8e6af Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Wed, 1 Oct 2025 18:03:01 -0700 Subject: [PATCH 75/78] Update code style Signed-off-by: Jono Yang --- docs/source/conf.py | 8 +- src/debian_inspector/contents.py | 41 +-- src/debian_inspector/copyright.py | 175 ++++++------ src/debian_inspector/coverage.py | 3 +- src/debian_inspector/deb822.py | 37 ++- src/debian_inspector/debcon.py | 186 +++++++------ src/debian_inspector/deps.py | 53 ++-- src/debian_inspector/package.py | 56 ++-- src/debian_inspector/unsign.py | 16 +- src/debian_inspector/utils.py | 14 +- src/debian_inspector/version.py | 228 ++++++++-------- tests/dpkg-tests/test_version_dpkg.py | 37 ++- .../dpkg-tests/test_version_python_debian.py | 87 +++--- tests/test_contents.py | 14 +- tests/test_copyright.py | 105 ++++--- tests/test_deb822.py | 117 +++++--- tests/test_debcon.py | 257 +++++++++--------- tests/test_deps.py | 214 ++++++++------- tests/test_package.py | 254 +++++++++-------- tests/test_unsign.py | 51 ++-- tests/test_utils.py | 18 +- tests/test_version.py | 165 ++++++----- tests/test_version_python_deb_pkg_tools.py | 51 ++-- 23 files changed, 1144 insertions(+), 1043 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 747b40c..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 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..96e1501 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: @@ -349,6 +352,7 @@ class CopyrightHeaderParagraph(BaseParagraph): https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#header-paragraph """ + # 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 @@ -395,6 +401,7 @@ class CopyrightFilesParagraph(BaseParagraph): https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#files-paragraph """ + 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 @@ -439,6 +450,7 @@ class CopyrightLicenseParagraph(BaseParagraph): https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#stand-alone-license-paragraph """ + 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..2ce7f4a 100644 --- a/src/debian_inspector/utils.py +++ b/src/debian_inspector/utils.py @@ -48,11 +48,11 @@ def find_debian_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..84394ae 100644 --- a/src/debian_inspector/version.py +++ b/src/debian_inspector/version.py @@ -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_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") From b098698940dfc2a7d9fcea3e6bb24be0a55e370a Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Wed, 1 Oct 2025 18:07:13 -0700 Subject: [PATCH 76/78] Update CHANGELOG.rst Signed-off-by: Jono Yang --- CHANGELOG.rst | 1 + requirements-dev.txt | 25 +++++++++++++++---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8e38aa2..705b850 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,7 @@ 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/requirements-dev.txt b/requirements-dev.txt index a835705..3d6486d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,24 +1,28 @@ aboutcode-toolkit==11.1.1 -alabaster==1.0.0 +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.3.0 +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==2.0.0 +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 @@ -27,7 +31,7 @@ jeepney==0.9.0 jinja2==3.1.6 keyring==25.6.0 license-expression==30.4.4 -markdown-it-py==4.0.0 +markdown-it-py==3.0.0 markupsafe==3.0.3 mdurl==0.1.2 more-itertools==10.8.0 @@ -48,19 +52,18 @@ requests-toolbelt==1.0.0 restructuredtext_lint==1.4.0 rfc3986==2.0.0 rich==14.1.0 -roman-numerals-py==3.1.0 ruff==0.13.2 saneyaml==0.6.1 -secretstorage==3.4.0 +secretstorage==3.3.3 sniffio==1.3.1 snowballstemmer==3.0.1 soupsieve==2.8 -sphinx==8.2.3 -sphinx-autobuild==2025.8.25 +sphinx==7.4.7 +sphinx-autobuild==2024.10.3 sphinx-copybutton==0.5.2 -sphinx-reredirects==1.0.0 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 @@ -72,9 +75,11 @@ 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 \ No newline at end of file +websockets==15.0.1 +zipp==3.23.0 \ No newline at end of file From 50cf7c1eae7c41fa01a4b4b31e46f69c23825243 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Wed, 1 Oct 2025 18:16:47 -0700 Subject: [PATCH 77/78] Update links in docstrings Signed-off-by: Jono Yang --- src/debian_inspector/utils.py | 2 +- src/debian_inspector/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/debian_inspector/utils.py b/src/debian_inspector/utils.py index 2ce7f4a..fa5a16b 100644 --- a/src/debian_inspector/utils.py +++ b/src/debian_inspector/utils.py @@ -44,7 +44,7 @@ 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() diff --git a/src/debian_inspector/version.py b/src/debian_inspector/version.py index 84394ae..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 From d0b5a52447cbec38d4e5d72f3da023a0e93b42c6 Mon Sep 17 00:00:00 2001 From: Jono Yang Date: Wed, 1 Oct 2025 18:21:14 -0700 Subject: [PATCH 78/78] Update links in docstrings Signed-off-by: Jono Yang --- src/debian_inspector/copyright.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/debian_inspector/copyright.py b/src/debian_inspector/copyright.py index 96e1501..309bc19 100644 --- a/src/debian_inspector/copyright.py +++ b/src/debian_inspector/copyright.py @@ -350,7 +350,7 @@ 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: @@ -399,7 +399,7 @@ 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) @@ -448,7 +448,7 @@ 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)