diff --git a/.ci/gen_certs.py b/.ci/gen_certs.py index 289cd9593..d55b0b912 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 index d90642511..ca8e11e26 100755 --- a/.ci/scripts/calc_constraints.py +++ b/.ci/scripts/calc_constraints.py @@ -1,4 +1,11 @@ #!/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 diff --git a/.ci/scripts/check_click_for_mypy.py b/.ci/scripts/check_click_for_mypy.py index 408a7c6af..33ecf4ca1 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 e6cb0da7a..499265cca 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 9d637b6b2..49eb4ada1 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 a067e66a2..70d08710b 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 ddd4fa460..c6b45669a 100644 --- a/Makefile +++ b/Makefile @@ -3,29 +3,28 @@ LANGUAGES=de GLUE_PLUGINS=$(notdir $(wildcard pulp-glue/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; pyproject-build -n pyproject-build -n -black: format - +.PHONY: format format: - isort . - cd pulp-glue; 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; isort -c --diff . - black --diff --check . - flake8 + ruff format --check --diff + ruff check --diff .ci/scripts/check_click_for_mypy.py MYPYPATH=pulp-glue mypy cd pulp-glue; mypy @@ -35,21 +34,27 @@ 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/tests cookiecutter/pulp_filter_extension.py +.PHONY: livetest livetest: | tests/cli.toml python3 -m pytest -v tests pulp-glue/tests -m live +.PHONY: unittest unittest: python3 -m pytest -v tests pulp-glue/tests cookiecutter/pulp_filter_extension.py -m "not live" +.PHONY: unittest_glue unittest_glue: python3 -m pytest -v pulp-glue/tests -m "not live" +.PHONY: docs docs: pulp-docs build +.PHONY: servedocs servedocs: pulp-docs serve -w CHANGES.md -w pulp-glue/pulp_glue -w pulp_cli/generic.py @@ -61,6 +66,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/pulp_glue/$(GLUE_PLUGIN)/locale/messages.pot) $(foreach CLI_PLUGIN,$(CLI_PLUGINS),pulpcore/cli/$(CLI_PLUGIN)/locale/messages.pot) $(foreach LANGUAGE,$(LANGUAGES),pulp-glue/pulp_glue/%/locale/$(LANGUAGE)/LC_MESSAGES/messages.po): pulp-glue/pulp_glue/%/locale/messages.pot @@ -76,6 +82,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/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 docs servedocs + .PRECIOUS: $(foreach LANGUAGE,$(LANGUAGES),$(foreach GLUE_PLUGIN,$(GLUE_PLUGINS),pulp-glue/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/cookiecutter/apply_templates.py b/cookiecutter/apply_templates.py index e72ce3a0b..8ef27eff6 100755 --- a/cookiecutter/apply_templates.py +++ b/cookiecutter/apply_templates.py @@ -1,10 +1,18 @@ #!/bin/env python3 +# /// script +# requires-python = ">=3.11" +# dependencies = [ +# "click>=8.3.1,<8.4", +# "cookiecutter>=2.6.0,<2.7", +# "pyyaml>=6.0.3,<6.1", +# "tomlkit>=0.13.3,<0.14", +# ] +# /// -# This script requires click, cookiecutter, tomlkit and pyyaml import os -import tomllib from pathlib import Path +import tomllib import click import yaml diff --git a/cookiecutter/ci/hooks/post_gen_project.py b/cookiecutter/ci/hooks/post_gen_project.py index fdc068b88..ec314fbad 100644 --- a/cookiecutter/ci/hooks/post_gen_project.py +++ b/cookiecutter/ci/hooks/post_gen_project.py @@ -1,5 +1,3 @@ -# flake8: noqa - import os import shutil import sys @@ -17,6 +15,10 @@ # Is merging on the tool level appropriate? pyproject_toml["tool"].update(pyproject_toml_update["tool"]) +# Remove legacy tools. +for tool in ["flake8", "black", "isort"]: + pyproject_toml["tool"].pop(tool, None) + with open("pyproject.toml", "w") as fp: tomlkit.dump(pyproject_toml, fp) diff --git a/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/gen_certs.py b/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/gen_certs.py index 289cd9593..d55b0b912 100644 --- a/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/gen_certs.py +++ b/cookiecutter/ci/{{ cookiecutter.__project_name }}/.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/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/calc_constraints.py b/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/calc_constraints.py new file mode 100755 index 000000000..ca8e11e26 --- /dev/null +++ b/cookiecutter/ci/{{ cookiecutter.__project_name }}/.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/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/check_cli_dependencies.py b/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/check_cli_dependencies.py index 23321413c..07073be77 100755 --- a/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/check_cli_dependencies.py +++ b/cookiecutter/ci/{{ cookiecutter.__project_name }}/.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{{ cookiecutter.__app_label_suffix }}" diff --git a/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/check_click_for_mypy.py b/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/check_click_for_mypy.py index 408a7c6af..33ecf4ca1 100755 --- a/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/check_click_for_mypy.py +++ b/cookiecutter/ci/{{ cookiecutter.__project_name }}/.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/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/collect_changes.py b/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/collect_changes.py index e6cb0da7a..499265cca 100755 --- a/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/collect_changes.py +++ b/cookiecutter/ci/{{ cookiecutter.__project_name }}/.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/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/pr_labels.py b/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/pr_labels.py index 9d637b6b2..49eb4ada1 100755 --- a/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/pr_labels.py +++ b/cookiecutter/ci/{{ cookiecutter.__project_name }}/.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/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/validate_commit_message.py b/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/validate_commit_message.py index 68e362ae8..7bf23a8ff 100644 --- a/cookiecutter/ci/{{ cookiecutter.__project_name }}/.ci/scripts/validate_commit_message.py +++ b/cookiecutter/ci/{{ cookiecutter.__project_name }}/.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/cookiecutter/ci/{{ cookiecutter.__project_name }}/Makefile b/cookiecutter/ci/{{ cookiecutter.__project_name }}/Makefile index 28c3304ef..fb234f6cd 100644 --- a/cookiecutter/ci/{{ cookiecutter.__project_name }}/Makefile +++ b/cookiecutter/ci/{{ cookiecutter.__project_name }}/Makefile @@ -6,6 +6,7 @@ GLUE_PLUGINS=$(notdir $(wildcard pulp-glue{{ cookiecutter.__app_label_suffix }}/ {%- endif %} CLI_PLUGINS=$(notdir $(wildcard pulpcore/cli/*)) +.PHONY: info info: {%- if cookiecutter.glue %} @echo Pulp glue @@ -14,29 +15,23 @@ info: @echo Pulp CLI @echo plugins: $(CLI_PLUGINS) +.PHONY: build build: {%- if cookiecutter.glue %} cd pulp-glue{{ cookiecutter.__app_label_suffix }}; pyproject-build -n {%- endif %} pyproject-build -n -black: format - +.PHONY: format format: - isort . -{%- if cookiecutter.glue %} - cd pulp-glue{{ cookiecutter.__app_label_suffix }}; isort . -{%- endif %} - black . + ruff format + ruff check --fix +.PHONY: lint lint: find tests .ci -name '*.sh' -print0 | xargs -0 shellcheck -x - isort -c --diff . -{%- if cookiecutter.glue %} - cd pulp-glue{{ cookiecutter.__app_label_suffix }}; isort -c --diff . -{%- endif %} - black --diff --check . - flake8 + ruff format --check --diff + ruff check --diff {%- if cookiecutter.glue and cookiecutter.app_label %} .ci/scripts/check_cli_dependencies.py {%- endif %} @@ -53,26 +48,32 @@ 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 {%- if cookiecutter.glue %} pulp-glue{{ cookiecutter.__app_label_suffix }}/tests {%- endif %} {%- if cookiecutter.__app_label_suffix == "" %} cookiecutter/pulp_filter_extension.py {%- endif %} +.PHONY: livetest livetest: | tests/cli.toml python3 -m pytest -v tests {%- if cookiecutter.glue %} pulp-glue{{ cookiecutter.__app_label_suffix }}/tests {%- endif %} -m live +.PHONY: unittest unittest: python3 -m pytest -v tests {%- if cookiecutter.glue %} pulp-glue{{ cookiecutter.__app_label_suffix }}/tests {%- endif %} {%- if cookiecutter.__app_label_suffix == "" %} cookiecutter/pulp_filter_extension.py {%- endif %} -m "not live" {%- if cookiecutter.glue %} +.PHONY: unittest_glue unittest_glue: python3 -m pytest -v pulp-glue{{ cookiecutter.__app_label_suffix }}/tests -m "not live" {%- endif %} {%- if cookiecutter.docs %} +.PHONY: docs docs: pulp-docs build +.PHONY: servedocs servedocs: pulp-docs serve -w CHANGES.md -w pulp-glue/pulp_glue -w pulp_cli/generic.py {%- endif %} @@ -88,6 +89,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: {%- if cookiecutter.glue %} $(foreach GLUE_PLUGIN,$(GLUE_PLUGINS),pulp-glue{{ cookiecutter.__app_label_suffix }}/pulp_glue/$(GLUE_PLUGIN)/locale/messages.pot) {%- endif %} $(foreach CLI_PLUGIN,$(CLI_PLUGINS),pulpcore/cli/$(CLI_PLUGIN)/locale/messages.pot) {%- if cookiecutter.glue %} @@ -105,10 +107,11 @@ $(foreach LANGUAGE,$(LANGUAGES),pulpcore/cli/%/locale/$(LANGUAGE)/LC_MESSAGES/me %.mo: %.po msgfmt -o $@ $< +.PHONY: compile_messages compile_messages: {%- if cookiecutter.glue %} $(foreach LANGUAGE,$(LANGUAGES),$(foreach GLUE_PLUGIN,$(GLUE_PLUGINS),pulp-glue{{ cookiecutter.__app_label_suffix }}/pulp_glue/$(GLUE_PLUGIN)/locale/$(LANGUAGE)/LC_MESSAGES/messages.mo)) {%- endif %} $(foreach LANGUAGE,$(LANGUAGES),$(foreach CLI_PLUGIN,$(CLI_PLUGINS),pulpcore/cli/$(CLI_PLUGIN)/locale/$(LANGUAGE)/LC_MESSAGES/messages.mo)) {%- endif %} -.PHONY: build info black lint test {%- if cookiecutter.docs %} docs servedocs {%- endif %} {%- if cookiecutter.translations %} + .PRECIOUS: {%- if cookiecutter.glue %} $(foreach LANGUAGE,$(LANGUAGES),$(foreach GLUE_PLUGIN,$(GLUE_PLUGINS),pulp-glue{{ cookiecutter.__app_label_suffix }}/pulp_glue/$(GLUE_PLUGIN)/locale/$(LANGUAGE)/LC_MESSAGES/messages.po)) {%- endif %} $(foreach LANGUAGE,$(LANGUAGES),$(foreach CLI_PLUGIN,$(CLI_PLUGINS),pulpcore/cli/$(CLI_PLUGIN)/locale/$(LANGUAGE)/LC_MESSAGES/messages.po)) {%- endif %} diff --git a/cookiecutter/ci/{{ cookiecutter.__project_name }}/pyproject.toml.update b/cookiecutter/ci/{{ cookiecutter.__project_name }}/pyproject.toml.update index dab691e6e..5acc2a304 100644 --- a/cookiecutter/ci/{{ cookiecutter.__project_name }}/pyproject.toml.update +++ b/cookiecutter/ci/{{ cookiecutter.__project_name }}/pyproject.toml.update @@ -168,14 +168,14 @@ profile = "black" line_length = 100 extend_skip = ["pulp-glue{{ cookiecutter.__app_label_suffix }}"{% if cookiecutter.app_label == "" %}, "cookiecutter"{% endif %}] -[tool.flake8] -# This section is managed by the cookiecutter templates. -exclude = ["./docs/*"{% if cookiecutter.app_label == "" %}, "./cookiecutter/*"{% endif %}] -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 +[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. +extend-select = ["I"] [tool.mypy] # This section is managed by the cookiecutter templates. diff --git a/cookiecutter/update_pulp_cli.py b/cookiecutter/update_pulp_cli.py index 602f2c51a..d6e7f6a6e 100755 --- a/cookiecutter/update_pulp_cli.py +++ b/cookiecutter/update_pulp_cli.py @@ -1,8 +1,11 @@ #!/usr/bin/env python3 - -# Requirements: -# packaging -# tomlkit +# /// script +# requires-python = ">=3.11" +# dependencies = [ +# "packaging>=25.0,<25.1", +# "tomlkit>=0.13.3,<0.13.4", +# ] +# /// import json import logging diff --git a/docs/dev/guides/contributing.md b/docs/dev/guides/contributing.md index b92716695..0c60d7048 100644 --- a/docs/dev/guides/contributing.md +++ b/docs/dev/guides/contributing.md @@ -19,9 +19,7 @@ If you are using the Pulp CLI and have written end-to-end steps for Pulp workflo If you are interested in contributing code, note that we have styling and formatting conventions for both the code and our PRs: -- Code formatting is done with `isort` and `black`. - -- Static analysis is performed with `flake8`. +- Code formatting and static analysis is done with `ruff`. - `pulp-cli` utilizes strict python type annotations, checked with `mypy`. @@ -40,8 +38,8 @@ note that we have styling and formatting conventions for both the code and our P We recommend running these before committing: ```bash pip install -r lint_requirements.txt # setup, needed only once - make format # reformatting with isort & black - make lint # checking with shellcheck, isort, black, flake8 and mypy + make format # reformatting with ruff + make lint # checking with shellcheck, ruff and mypy make format lint # both in one command ``` diff --git a/lint_requirements.txt b/lint_requirements.txt index adf5ff859..c17cc785d 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.10 mypy==1.19.1 shellcheck-py==0.11.0.1 diff --git a/pulp-glue/pulp_glue/common/context.py b/pulp-glue/pulp_glue/common/context.py index fff028eff..f6ad7e244 100644 --- a/pulp-glue/pulp_glue/common/context.py +++ b/pulp-glue/pulp_glue/common/context.py @@ -8,7 +8,6 @@ from contextlib import ExitStack from packaging.specifiers import SpecifierSet - from pulp_glue.common.exceptions import ( NotImplementedFake, OpenAPIError, diff --git a/pulp-glue/pulp_glue/common/openapi.py b/pulp-glue/pulp_glue/common/openapi.py index 48ae71fc7..8ee5ce5d1 100644 --- a/pulp-glue/pulp_glue/common/openapi.py +++ b/pulp-glue/pulp_glue/common/openapi.py @@ -14,7 +14,6 @@ import requests import urllib3 from multidict import CIMultiDict, MutableMultiMapping - from pulp_glue.common import __version__ from pulp_glue.common.exceptions import ( OpenAPIError, @@ -96,7 +95,6 @@ def __call__( security: list[dict[str, list[str]]], security_schemes: dict[str, dict[str, t.Any]], ) -> requests.auth.AuthBase | None: - # Reorder the proposals by their type to prioritize properly. # Select only single mechanism proposals on the way. proposed_schemes: dict[str, dict[str, list[str]]] = defaultdict(dict) @@ -527,9 +525,9 @@ def _send_request( ) ) if content_type: - assert request.headers["content-type"].startswith( - content_type - ), f"{request.headers['content-type']} != {content_type}" + assert request.headers["content-type"].startswith(content_type), ( + f"{request.headers['content-type']} != {content_type}" + ) for key, value in request.headers.items(): self._debug_callback(2, f" {key}: {value}") if request.body is not None: diff --git a/pulp-glue/tests/conftest.py b/pulp-glue/tests/conftest.py index bc8d56678..4adb117ca 100644 --- a/pulp-glue/tests/conftest.py +++ b/pulp-glue/tests/conftest.py @@ -3,7 +3,6 @@ import typing as t import pytest - from pulp_glue.common.context import PulpContext from pulp_glue.common.openapi import BasicAuthProvider, OpenAPI diff --git a/pulp-glue/tests/test_api_quirks.py b/pulp-glue/tests/test_api_quirks.py index 206eaa524..2aa360e55 100644 --- a/pulp-glue/tests/test_api_quirks.py +++ b/pulp-glue/tests/test_api_quirks.py @@ -1,7 +1,6 @@ from copy import deepcopy import pytest - from pulp_glue.common.context import _REGISTERED_API_QUIRKS, PulpContext pytestmark = pytest.mark.glue diff --git a/pulp-glue/tests/test_auth_provider.py b/pulp-glue/tests/test_auth_provider.py index f8e2f2441..dd65c5191 100644 --- a/pulp-glue/tests/test_auth_provider.py +++ b/pulp-glue/tests/test_auth_provider.py @@ -1,10 +1,9 @@ import typing as t import pytest -from requests.auth import AuthBase - from pulp_glue.common.exceptions import OpenAPIError from pulp_glue.common.openapi import AuthProviderBase +from requests.auth import AuthBase pytestmark = pytest.mark.glue diff --git a/pulp-glue/tests/test_authentication.py b/pulp-glue/tests/test_authentication.py index c497ca4af..689fd6207 100644 --- a/pulp-glue/tests/test_authentication.py +++ b/pulp-glue/tests/test_authentication.py @@ -1,14 +1,12 @@ import typing as t import pytest - from pulp_glue.common.authentication import OAuth2ClientCredentialsAuth pytestmark = pytest.mark.glue def test_sending_no_scope_when_empty(monkeypatch: pytest.MonkeyPatch) -> None: - class OAuth2MockResponse: def raise_for_status(self) -> None: return None diff --git a/pulp-glue/tests/test_entity_converge.py b/pulp-glue/tests/test_entity_converge.py index ce348e2dd..bb0034012 100644 --- a/pulp-glue/tests/test_entity_converge.py +++ b/pulp-glue/tests/test_entity_converge.py @@ -3,7 +3,6 @@ import typing as t import pytest - from pulp_glue.common.context import PulpContext from pulp_glue.file.context import PulpFileRemoteContext, PulpFileRepositoryContext diff --git a/pulp-glue/tests/test_entity_list.py b/pulp-glue/tests/test_entity_list.py index 3148015fa..77a840f2b 100644 --- a/pulp-glue/tests/test_entity_list.py +++ b/pulp-glue/tests/test_entity_list.py @@ -3,7 +3,6 @@ import typing as t import pytest - from pulp_glue.common.context import PulpContext from pulp_glue.file.context import PulpFileRepositoryContext diff --git a/pulp-glue/tests/test_fake_mode.py b/pulp-glue/tests/test_fake_mode.py index 9d7b7fced..250b59a77 100644 --- a/pulp-glue/tests/test_fake_mode.py +++ b/pulp-glue/tests/test_fake_mode.py @@ -3,7 +3,6 @@ import typing as t import pytest - from pulp_glue.common.context import PulpContext from pulp_glue.common.exceptions import NotImplementedFake from pulp_glue.file.context import PulpFileRepositoryContext diff --git a/pulp-glue/tests/test_openapi.py b/pulp-glue/tests/test_openapi.py index 41b53a346..c1bfb8108 100644 --- a/pulp-glue/tests/test_openapi.py +++ b/pulp-glue/tests/test_openapi.py @@ -2,7 +2,6 @@ import json import pytest - from pulp_glue.common.openapi import OpenAPI, _Response pytestmark = pytest.mark.glue diff --git a/pulp-glue/tests/test_openapi_logging.py b/pulp-glue/tests/test_openapi_logging.py index 6f5cb2ca8..4d0ecd78b 100644 --- a/pulp-glue/tests/test_openapi_logging.py +++ b/pulp-glue/tests/test_openapi_logging.py @@ -3,7 +3,6 @@ import typing as t import pytest - from pulp_glue.common.openapi import OpenAPI TEST_SCHEMA = json.dumps( diff --git a/pulp-glue/tests/test_plugin_requirement.py b/pulp-glue/tests/test_plugin_requirement.py index 3aab8a131..f3bfdf921 100644 --- a/pulp-glue/tests/test_plugin_requirement.py +++ b/pulp-glue/tests/test_plugin_requirement.py @@ -1,5 +1,4 @@ import pytest - from pulp_glue.common.context import PluginRequirement, PulpContext from pulp_glue.common.exceptions import PulpException diff --git a/pulp-glue/tests/test_schema.py b/pulp-glue/tests/test_schema.py index 4ac037c20..da4ca9bd3 100644 --- a/pulp-glue/tests/test_schema.py +++ b/pulp-glue/tests/test_schema.py @@ -3,7 +3,6 @@ import typing as t import pytest - from pulp_glue.common.schema import ( SchemaError, ValidationError, diff --git a/pulp_cli/generic.py b/pulp_cli/generic.py index 67386000a..afe99e293 100644 --- a/pulp_cli/generic.py +++ b/pulp_cli/generic.py @@ -969,8 +969,7 @@ def _option_callback( if context_class is None: raise click.ClickException( _( - "The type '{plugin}:{resource_type}' " - "is not valid for the {option_name} option." + "The type '{plugin}:{resource_type}' is not valid for the {option_name} option." ).format(plugin=plugin, resource_type=resource_type, option_name=param.name) ) entity_ctx: PulpEntityContext = context_class(pulp_ctx, pulp_href=pulp_href, entity=entity) @@ -1259,9 +1258,7 @@ def _type_callback(ctx: click.Context, param: click.Parameter, value: str | None pulp_labels_option = pulp_option( "--labels", "pulp_labels", - help=_( - "JSON dictionary of labels to set on {entity} (or " "@file containing a JSON dictionary)" - ), + help=_("JSON dictionary of labels to set on {entity} (or @file containing a JSON dictionary)"), callback=load_labels_callback, ) diff --git a/pulpcore/cli/ansible/distribution.py b/pulpcore/cli/ansible/distribution.py index e3fe8a391..08b46b1d0 100644 --- a/pulpcore/cli/ansible/distribution.py +++ b/pulpcore/cli/ansible/distribution.py @@ -138,7 +138,7 @@ def update( if version is not None: if dist_body["repository"]: distribution_ctx.update(body={"repository": ""}, non_blocking=True) - body["repository_version"] = f'{repo["versions_href"]}{version}/' + body["repository_version"] = f"{repo['versions_href']}{version}/" else: if dist_body["repository_version"]: distribution_ctx.update(body={"repository_version": ""}, non_blocking=True) @@ -147,7 +147,7 @@ def update( # keep current repository, change version if dist_body["repository"]: distribution_ctx.update(body={"repository": ""}, non_blocking=True) - body["repository_version"] = f'{dist_body["repository"]}versions/{version}/' + body["repository_version"] = f"{dist_body['repository']}versions/{version}/" elif dist_body["repository_version"]: # 'dummy' vars are to get us around a mypy/1.2 complaint about '_' repository_href, dummy, dummy = dist_body["repository_version"].partition("versions") diff --git a/pulpcore/cli/container/distribution.py b/pulpcore/cli/container/distribution.py index 975402596..1e4ba6d6f 100644 --- a/pulpcore/cli/container/distribution.py +++ b/pulpcore/cli/container/distribution.py @@ -144,7 +144,7 @@ def update( # keep current repository, change version if distribution["repository"]: distribution_ctx.update(body={"repository": ""}, non_blocking=True) - body["repository_version"] = f'{distribution["repository"]}versions/{version}/' + body["repository_version"] = f"{distribution['repository']}versions/{version}/" elif distribution["repository_version"]: # 'dummy' vars are to get us around a mypy/1.2 complaint about '_' repository_href, dummy, dummy = distribution["repository_version"].partition("versions") diff --git a/pulpcore/cli/core/export.py b/pulpcore/cli/core/export.py index 5adeb4f58..e35e2e723 100644 --- a/pulpcore/cli/core/export.py +++ b/pulpcore/cli/core/export.py @@ -58,8 +58,7 @@ def _version_list_callback( if context_class is None: raise click.ClickException( _( - "The type '{plugin}:{resource_type}' " - "is not valid for the {option_name} option." + "The type '{plugin}:{resource_type}' is not valid for the {option_name} option." ).format(plugin=plugin, resource_type=resource_type, option_name=param.name) ) repository_ctx: PulpRepositoryContext = context_class( diff --git a/pulpcore/cli/core/repository.py b/pulpcore/cli/core/repository.py index 738190e45..5c58b4754 100644 --- a/pulpcore/cli/core/repository.py +++ b/pulpcore/cli/core/repository.py @@ -57,8 +57,7 @@ def _version_list_callback( if context_class is None: raise click.ClickException( _( - "The type '{plugin}:{resource_type}' " - "is not valid for the {option_name} option." + "The type '{plugin}:{resource_type}' is not valid for the {option_name} option." ).format(plugin=plugin, resource_type=resource_type, option_name=param.name) ) repository_ctx: PulpRepositoryContext = context_class( diff --git a/pulpcore/cli/rpm/publication.py b/pulpcore/cli/rpm/publication.py index b5cf225b4..e6f3aab1e 100644 --- a/pulpcore/cli/rpm/publication.py +++ b/pulpcore/cli/rpm/publication.py @@ -61,8 +61,7 @@ def publication(ctx: click.Context, pulp_ctx: PulpCLIContext, /, publication_typ "repo_config", needs_plugins=[PluginRequirement("rpm", specifier=">=3.24.0")], help=_( - "A JSON dictionary describing config.repo file (or " - "@file containing a JSON dictionary)" + "A JSON dictionary describing config.repo file (or @file containing a JSON dictionary)" ), callback=load_json_callback, ), diff --git a/pulpcore/cli/rpm/repository.py b/pulpcore/cli/rpm/repository.py index 864127a6c..be7a7f2c8 100644 --- a/pulpcore/cli/rpm/repository.py +++ b/pulpcore/cli/rpm/repository.py @@ -210,8 +210,7 @@ def repository(ctx: click.Context, pulp_ctx: PulpCLIContext, /, repo_type: str) "repo_config", needs_plugins=[PluginRequirement("rpm", specifier=">=3.24.0")], help=_( - "A JSON dictionary describing config.repo file (or " - "@file containing a JSON dictionary)" + "A JSON dictionary describing config.repo file (or @file containing a JSON dictionary)" ), callback=load_json_callback, ), diff --git a/pyproject.toml b/pyproject.toml index 648e00d65..435cd8d88 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -195,25 +195,15 @@ name = "Misc" showcontent = true -[tool.black] +[tool.ruff] # This section is managed by the cookiecutter templates. line-length = 100 -exclude = "cookiecutter" +extend-exclude = ["cookiecutter"] -[tool.isort] +[tool.ruff.lint] # This section is managed by the cookiecutter templates. -profile = "black" -line_length = 100 -extend_skip = ["pulp-glue", "cookiecutter"] +extend-select = ["I"] -[tool.flake8] -# This section is managed by the cookiecutter templates. -exclude = ["./docs/*", "./cookiecutter/*"] -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 [tool.pytest.ini_options] markers = [ @@ -247,3 +237,4 @@ module = [ "schema.*", ] ignore_missing_imports = true + diff --git a/pytest_pulp_cli/__init__.py b/pytest_pulp_cli/__init__.py index a4ef26d1f..0f549e9ae 100644 --- a/pytest_pulp_cli/__init__.py +++ b/pytest_pulp_cli/__init__.py @@ -209,7 +209,6 @@ def pulp_container_log(pulp_container_log_stream: t.IO[bytes]) -> t.Iterator[Non print(logs.decode()) else: - # MyPy complains about an incompatible redefinition. # I don't think this quite applies to the fixtures do dependency injection. @pytest.fixture