diff --git a/.ci/gen_certs.py b/.ci/gen_certs.py index 289cd95..d55b0b9 100644 --- a/.ci/gen_certs.py +++ b/.ci/gen_certs.py @@ -1,3 +1,10 @@ +# /// script +# requires-python = ">=3.10" +# dependencies = [ +# "trustme>=1.2.1,<1.3.0", +# ] +# /// + import argparse import os import sys @@ -6,7 +13,6 @@ def main() -> None: - parser = argparse.ArgumentParser(prog="gen_certs") parser.add_argument( "-d", diff --git a/.ci/scripts/calc_constraints.py b/.ci/scripts/calc_constraints.py new file mode 100755 index 0000000..ca8e11e --- /dev/null +++ b/.ci/scripts/calc_constraints.py @@ -0,0 +1,119 @@ +#!/bin/python3 +# /// script +# requires-python = ">=3.10" +# dependencies = [ +# "packaging>=25.0,<25.1", +# "tomli>=2.3.0,<2.4.0;python_version<'3.11'", +# ] +# /// + +import argparse +import fileinput +import sys + +from packaging.requirements import Requirement +from packaging.version import Version + +try: + import tomllib +except ImportError: + import tomli as tomllib + + +def split_comment(line): + split_line = line.split("#", maxsplit=1) + try: + comment = " # " + split_line[1].strip() + except IndexError: + comment = "" + return split_line[0].strip(), comment + + +def to_upper_bound(req): + try: + requirement = Requirement(req) + except ValueError: + return f"# UNPARSABLE: {req}" + else: + for spec in requirement.specifier: + if spec.operator == "~=": + return f"# NO BETTER CONSTRAINT: {req}" + if spec.operator == "<=": + operator = "==" + max_version = spec.version + return f"{requirement.name}{operator}{max_version}" + if spec.operator == "<": + operator = "~=" + version = Version(spec.version) + if version.micro != 0: + max_version = f"{version.major}.{version.minor}.{version.micro - 1}" + elif version.minor != 0: + max_version = f"{version.major}.{version.minor - 1}" + elif version.major != 0: + max_version = f"{version.major - 1}.0" + else: + return f"# NO BETTER CONSTRAINT: {req}" + return f"{requirement.name}{operator}{max_version}" + return f"# NO UPPER BOUND: {req}" + + +def to_lower_bound(req): + try: + requirement = Requirement(req) + except ValueError: + return f"# UNPARSABLE: {req}" + else: + for spec in requirement.specifier: + if spec.operator == ">=": + if requirement.name == "pulpcore": + # Currently an exception to allow for pulpcore bugfix releases. + # TODO Semver libraries should be allowed too. + operator = "~=" + else: + operator = "==" + min_version = spec.version + return f"{requirement.name}{operator}{min_version}" + return f"# NO LOWER BOUND: {req}" + + +def main(): + """Calculate constraints for the lower bound of dependencies where possible.""" + parser = argparse.ArgumentParser( + prog=sys.argv[0], + description="Calculate constraints for the lower or upper bound of dependencies where " + "possible.", + ) + parser.add_argument("-u", "--upper", action="store_true") + parser.add_argument("filename", nargs="*") + args = parser.parse_args() + + modifier = to_upper_bound if args.upper else to_lower_bound + + req_files = [filename for filename in args.filename if not filename.endswith("pyproject.toml")] + pyp_files = [filename for filename in args.filename if filename.endswith("pyproject.toml")] + if req_files: + with fileinput.input(files=req_files) as req_file: + for line in req_file: + if line.strip().startswith("#"): + # Shortcut comment only lines + print(line.strip()) + else: + req, comment = split_comment(line) + new_req = modifier(req) + print(new_req + comment) + for filename in pyp_files: + with open(filename, "rb") as fp: + pyproject = tomllib.load(fp) + for req in pyproject["project"]["dependencies"]: + new_req = modifier(req) + print(new_req) + optional_dependencies = pyproject["project"].get("optional-dependencies") + if optional_dependencies: + for opt in optional_dependencies.values(): + for req in opt: + new_req = modifier(req) + print(new_req) + + +if __name__ == "__main__": + main() diff --git a/.ci/scripts/check_cli_dependencies.py b/.ci/scripts/check_cli_dependencies.py index 714eea4..cd99f60 100755 --- a/.ci/scripts/check_cli_dependencies.py +++ b/.ci/scripts/check_cli_dependencies.py @@ -1,8 +1,15 @@ #!/bin/env python3 -import tomllib +# /// script +# requires-python = ">=3.11" +# dependencies = [ +# "packaging>=25.0,<25.1", +# ] +# /// + import typing as t from pathlib import Path +import tomllib from packaging.requirements import Requirement GLUE_DIR = "pulp-glue-deb" diff --git a/.ci/scripts/check_click_for_mypy.py b/.ci/scripts/check_click_for_mypy.py index 408a7c6..33ecf4c 100755 --- a/.ci/scripts/check_click_for_mypy.py +++ b/.ci/scripts/check_click_for_mypy.py @@ -1,4 +1,10 @@ #!/bin/env python3 +# /// script +# requires-python = ">=3.11" +# dependencies = [ +# "packaging>=25.0,<25.1", +# ] +# /// from importlib import metadata diff --git a/.ci/scripts/collect_changes.py b/.ci/scripts/collect_changes.py index e6cb0da..499265c 100755 --- a/.ci/scripts/collect_changes.py +++ b/.ci/scripts/collect_changes.py @@ -1,10 +1,17 @@ #!/bin/env python3 +# /// script +# requires-python = ">=3.11" +# dependencies = [ +# "gitpython>=3.1.46,<3.2.0", +# "packaging>=25.0,<25.1", +# ] +# /// import itertools import os import re -import tomllib +import tomllib from git import GitCommandError, Repo from packaging.version import parse as parse_version diff --git a/.ci/scripts/pr_labels.py b/.ci/scripts/pr_labels.py index 9d637b6..49eb4ad 100755 --- a/.ci/scripts/pr_labels.py +++ b/.ci/scripts/pr_labels.py @@ -1,12 +1,18 @@ #!/bin/env python3 +# /// script +# requires-python = ">=3.11" +# dependencies = [ +# "gitpython>=3.1.46,<3.2.0", +# ] +# /// # This script is running with elevated privileges from the main branch against pull requests. import re import sys -import tomllib from pathlib import Path +import tomllib from git import Repo diff --git a/.ci/scripts/validate_commit_message.py b/.ci/scripts/validate_commit_message.py index 99d9a45..ac19868 100644 --- a/.ci/scripts/validate_commit_message.py +++ b/.ci/scripts/validate_commit_message.py @@ -1,10 +1,17 @@ +# /// script +# requires-python = ">=3.11" +# dependencies = [ +# "gitpython>=3.1.46,<3.2.0", +# ] +# /// + import os import re import subprocess import sys -import tomllib from pathlib import Path +import tomllib from github import Github with open("pyproject.toml", "rb") as fp: diff --git a/Makefile b/Makefile index ad84c4b..f3ba9ee 100644 --- a/Makefile +++ b/Makefile @@ -3,29 +3,28 @@ LANGUAGES=de GLUE_PLUGINS=$(notdir $(wildcard pulp-glue-deb/pulp_glue/*)) CLI_PLUGINS=$(notdir $(wildcard pulpcore/cli/*)) +.PHONY: info info: @echo Pulp glue @echo plugins: $(GLUE_PLUGINS) @echo Pulp CLI @echo plugins: $(CLI_PLUGINS) +.PHONY: build build: cd pulp-glue-deb; pyproject-build -n pyproject-build -n -black: format - +.PHONY: format format: - isort . - cd pulp-glue-deb; isort . - black . + ruff format + ruff check --fix +.PHONY: lint lint: find tests .ci -name '*.sh' -print0 | xargs -0 shellcheck -x - isort -c --diff . - cd pulp-glue-deb; isort -c --diff . - black --diff --check . - flake8 + ruff format --check --diff + ruff check --diff .ci/scripts/check_cli_dependencies.py .ci/scripts/check_click_for_mypy.py MYPYPATH=pulp-glue-deb mypy @@ -36,15 +35,19 @@ tests/cli.toml: cp $@.example $@ @echo "In order to configure the tests to talk to your test server, you might need to edit $@ ." +.PHONY: test test: | tests/cli.toml python3 -m pytest -v tests pulp-glue-deb/tests +.PHONY: livetest livetest: | tests/cli.toml python3 -m pytest -v tests pulp-glue-deb/tests -m live +.PHONY: unittest unittest: python3 -m pytest -v tests pulp-glue-deb/tests -m "not live" +.PHONY: unittest_glue unittest_glue: python3 -m pytest -v pulp-glue-deb/tests -m "not live" @@ -56,6 +59,7 @@ pulpcore/cli/%/locale/messages.pot: pulpcore/cli/%/*.py xgettext -d $* -o $@ pulpcore/cli/$*/*.py sed -i 's/charset=CHARSET/charset=UTF-8/g' $@ +.PHONY: extract_messages extract_messages: $(foreach GLUE_PLUGIN,$(GLUE_PLUGINS),pulp-glue-deb/pulp_glue/$(GLUE_PLUGIN)/locale/messages.pot) $(foreach CLI_PLUGIN,$(CLI_PLUGINS),pulpcore/cli/$(CLI_PLUGIN)/locale/messages.pot) $(foreach LANGUAGE,$(LANGUAGES),pulp-glue-deb/pulp_glue/%/locale/$(LANGUAGE)/LC_MESSAGES/messages.po): pulp-glue-deb/pulp_glue/%/locale/messages.pot @@ -71,6 +75,7 @@ $(foreach LANGUAGE,$(LANGUAGES),pulpcore/cli/%/locale/$(LANGUAGE)/LC_MESSAGES/me %.mo: %.po msgfmt -o $@ $< +.PHONY: compile_messages compile_messages: $(foreach LANGUAGE,$(LANGUAGES),$(foreach GLUE_PLUGIN,$(GLUE_PLUGINS),pulp-glue-deb/pulp_glue/$(GLUE_PLUGIN)/locale/$(LANGUAGE)/LC_MESSAGES/messages.mo)) $(foreach LANGUAGE,$(LANGUAGES),$(foreach CLI_PLUGIN,$(CLI_PLUGINS),pulpcore/cli/$(CLI_PLUGIN)/locale/$(LANGUAGE)/LC_MESSAGES/messages.mo)) -.PHONY: build info black lint test + .PRECIOUS: $(foreach LANGUAGE,$(LANGUAGES),$(foreach GLUE_PLUGIN,$(GLUE_PLUGINS),pulp-glue-deb/pulp_glue/$(GLUE_PLUGIN)/locale/$(LANGUAGE)/LC_MESSAGES/messages.po)) $(foreach LANGUAGE,$(LANGUAGES),$(foreach CLI_PLUGIN,$(CLI_PLUGINS),pulpcore/cli/$(CLI_PLUGIN)/locale/$(LANGUAGE)/LC_MESSAGES/messages.po)) diff --git a/lint_requirements.txt b/lint_requirements.txt index adf5ff8..804f227 100644 --- a/lint_requirements.txt +++ b/lint_requirements.txt @@ -1,8 +1,5 @@ # Lint requirements -black==25.12.0 -flake8==7.3.0 -flake8-pyproject==1.2.4 -isort==7.0.0 +ruff==0.14.11 mypy==1.19.1 shellcheck-py==0.11.0.1 diff --git a/pulp-glue-deb/pyproject.toml b/pulp-glue-deb/pyproject.toml index c1122a4..3e76324 100644 --- a/pulp-glue-deb/pyproject.toml +++ b/pulp-glue-deb/pyproject.toml @@ -31,29 +31,38 @@ repository = "https://github.com/pulp/pulp-cli-deb" changelog = "https://github.com/pulp/pulp-cli-deb/blob/main/CHANGES.md" [tool.setuptools.packages.find] +# This section is managed by the cookiecutter templates. where = ["."] include = ["pulp_glue.*"] namespaces = true [tool.setuptools.package-data] +# This section is managed by the cookiecutter templates. "*" = ["py.typed", "locale/*/LC_MESSAGES/*.mo"] -[tool.black] -line-length = 100 - -[tool.isort] -profile = "black" -line_length = 100 [tool.mypy] +# This section is managed by the cookiecutter templates. strict = true +warn_unused_ignores = false show_error_codes = true -files = "pulp_glue/**/*.py" +files = "pulp_glue/**/*.py, tests/**/*.py" namespace_packages = true explicit_package_bases = true [[tool.mypy.overrides]] +# This section is managed by the cookiecutter templates. module = [ "schema.*", ] ignore_missing_imports = true + + +[tool.ruff] +# This section is managed by the cookiecutter templates. +line-length = 100 + +[tool.ruff.lint] +# This section is managed by the cookiecutter templates. +extend-select = ["I"] + diff --git a/pulp-glue-deb/tests/.gitkeep b/pulp-glue-deb/tests/conftest.py similarity index 100% rename from pulp-glue-deb/tests/.gitkeep rename to pulp-glue-deb/tests/conftest.py diff --git a/pyproject.toml b/pyproject.toml index 400ca7f..9f0fd11 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -114,16 +114,6 @@ name = "Misc" showcontent = true -[tool.black] -# This section is managed by the cookiecutter templates. -line-length = 100 - -[tool.isort] -# This section is managed by the cookiecutter templates. -profile = "black" -line_length = 100 -extend_skip = ["pulp-glue-deb"] - [tool.pytest.ini_options] markers = [ "script: tests provided as shell scripts", @@ -200,12 +190,12 @@ search = "\"pulp-glue-deb=={current_version}\"" replace = "\"pulp-glue-deb=={new_version}\"" -[tool.flake8] +[tool.ruff] +# This section is managed by the cookiecutter templates. +line-length = 100 +extend-exclude = ["cookiecutter"] + +[tool.ruff.lint] # This section is managed by the cookiecutter templates. -exclude = ["./docs/*"] -ignore = ["W503", "Q000", "Q003", "D100", "D104", "D106", "D200", "D202", "D205", "D400", "D401", "D402"] -# E203: whitespace before ':'; https://github.com/psf/black/issues/279 -# E401: multiple imports on one line -extend-ignore = ["E203", "E401"] -max-line-length = 100 +extend-select = ["I"]