From b5511d68f2a17a4f7dfd9a8ab0249c655e660117 Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Wed, 26 Jun 2024 14:02:40 +0200 Subject: [PATCH 01/74] [python] Create our first python bazel quality rule This commit: - create a generic python aspect that can interface multiple runners; - create our first python runner that runs pylint; - create the aspect user interface at defs.bzl; - create a default pylint runner config at quality/BUILD; - adds `quality/private/python` to our poetry/tox setup; - adds pylint aspect config to .bazelrc; - create a target for pyproject.toml. --- quality/private/python/BUILD | 17 ++ quality/private/python/python_tool_aspect.bzl | 141 +++++++++++++ quality/private/python/tools/BUILD | 14 ++ quality/private/python/tools/pylint_runner.py | 56 +++++ .../python/tools/python_tool_common.py | 193 ++++++++++++++++++ 5 files changed, 421 insertions(+) create mode 100644 quality/private/python/BUILD create mode 100644 quality/private/python/python_tool_aspect.bzl create mode 100644 quality/private/python/tools/BUILD create mode 100644 quality/private/python/tools/pylint_runner.py create mode 100644 quality/private/python/tools/python_tool_common.py diff --git a/quality/private/python/BUILD b/quality/private/python/BUILD new file mode 100644 index 0000000..fdbf7f6 --- /dev/null +++ b/quality/private/python/BUILD @@ -0,0 +1,17 @@ +load("@swf_bazel_rules_quality//third_party/pip:requirements.bzl", "requirement") +load("@swf_bazel_rules_quality_rules_python//python/entry_points:py_console_script_binary.bzl", "py_console_script_binary") + +py_console_script_binary( + name = "pylint", + pkg = requirement("pylint"), + script = "pylint", + visibility = ["//visibility:public"], + deps = [ + requirement("astroid"), + requirement("dill"), + requirement("isort"), + requirement("mccabe"), + requirement("platformdirs"), + requirement("tomlkit"), + ], +) diff --git a/quality/private/python/python_tool_aspect.bzl b/quality/private/python/python_tool_aspect.bzl new file mode 100644 index 0000000..7c64afa --- /dev/null +++ b/quality/private/python/python_tool_aspect.bzl @@ -0,0 +1,141 @@ +"""Aspect that collects python targets dependencies and hands them to a tool runner.""" + +_excluded_main_label_names = [ + "rules_python_entry_point_", +] + +_excluded_workspaces_roots = [ + "external/", +] + +PythonToolInfo = provider( + doc = "Configuration structure for the python tool aspect.", + fields = { + "config": "Configuration file for the respective python tool tool.", + }, +) + +def _python_tool_config_impl(ctx): + return [PythonToolInfo(config = ctx.file.config)] + +python_tool_config = rule( + implementation = _python_tool_config_impl, + attrs = { + "config": attr.label( + allow_single_file = True, + ), + }, +) + +def _is_valid_label(label, excluded_labels, excluded_workspaces): + """Check if a given label is valid. + + To validate a given label this functions checks its name and workspace. + """ + + for excluded_workspace_root in excluded_workspaces: + if excluded_workspace_root in label.workspace_root: + return False + for excluded_label in excluded_labels: + if excluded_label in label.name: + return False + return True + +def _python_tool_aspect_implementation(target, ctx): + """Python tool aspect implementation.""" + + output = [] + + if not PyInfo in target: + return [OutputGroupInfo(python_tool_output = depset([]))] + + for excluded_path in _excluded_workspaces_roots: + if excluded_path in target.label.workspace_root: + return [OutputGroupInfo(python_tool_output = depset([]))] + + config = ctx.attr._config[PythonToolInfo].config + + sources_to_run = [] + for source in ctx.rule.attr.srcs: + if _is_valid_label(source.label, _excluded_main_label_names, _excluded_workspaces_roots) and source.label.name.endswith(".py"): + if source.label.package: + sources_to_run.append(source.label.package + "/" + source.label.name) + else: + sources_to_run.append(source.label.name) + + if sources_to_run: + output_file = ctx.actions.declare_file(ctx.executable._tool.basename + "_output_" + target.label.name) + output.append(output_file) + + imports = target[PyInfo].imports.to_list() + + dependencies = ["."] + for dep in ctx.rule.attr.deps: + if _is_valid_label(dep.label, [], []): + dependencies.append(dep.label.workspace_root) + + args = ctx.actions.args() + args.use_param_file("@%s", use_always = True) + args.set_param_file_format("multiline") + + args.add_all("--target-imports", imports, format_each = "%s") + args.add_all("--target-dependencies", dependencies, format_each = "%s") + args.add_all("--target-files", sources_to_run, format_each = "%s") + args.add("--tool", ctx.executable._tool.path, format = "%s") + args.add("--tool-config", config.path, format = "%s") + args.add("--tool-output", output_file.path, format = "%s") + args.add("--tool-root", ctx.expand_location(ctx.workspace_name), format = "%s") + + ctx.actions.run( + inputs = depset([config], transitive = [target[DefaultInfo].default_runfiles.files]), + outputs = [output_file], + tools = [ctx.executable._runner, ctx.executable._tool, target[DefaultInfo].files_to_run], + executable = ctx.executable._runner, + arguments = [args], + progress_message = "Running {tool} on: {target_name}".format(tool = ctx.executable._tool.basename, target_name = target.label.name), + ) + + return [OutputGroupInfo(python_tool_output = depset(output))] + +def _python_tool_aspect(tool, runner, config): + """Python tool aspect. + + Provides a python tool aspect instance that will call the given runner + with the given config. The runner can then prepare its enviroment and + call the given tool. This allows us to use this aspect with a variety + of different python tools runners, e.g., pylint, black, ruff etc. + + Args: + tool: tool executable, e.g., pylint, black, ruff etc. + runner: tool runner that is invoked by this aspect and calls tool + with a config. Each tool requires a different runner. + config: a provider that holds the tool config, e.g., pyproject.toml. + Returns: + A python tool aspect instance + """ + return aspect( + implementation = _python_tool_aspect_implementation, + attrs = { + "_config": attr.label( + default = Label(config), + providers = [PythonToolInfo], + ), + "_runner": attr.label( + executable = True, + cfg = "exec", + default = Label(runner), + ), + "_tool": attr.label( + executable = True, + cfg = "exec", + default = Label(tool), + ), + }, + required_providers = [PyInfo], + ) + +pylint_aspect = _python_tool_aspect( + tool = "@swf_bazel_rules_quality//quality/private/python:pylint", + runner = "@swf_bazel_rules_quality//quality/private/python/tools:pylint_runner", + config = "@swf_bazel_rules_quality//quality:quality_pylint_config", +) diff --git a/quality/private/python/tools/BUILD b/quality/private/python/tools/BUILD new file mode 100644 index 0000000..ffb8139 --- /dev/null +++ b/quality/private/python/tools/BUILD @@ -0,0 +1,14 @@ +load("@swf_bazel_rules_quality_rules_python//python:defs.bzl", "py_binary", "py_library") + +py_library( + name = "python_tool_common", + srcs = ["python_tool_common.py"], +) + +py_binary( + name = "pylint_runner", + srcs = ["pylint_runner.py"], + data = ["@swf_bazel_rules_quality//quality/private/python:pylint"], + visibility = ["//visibility:public"], + deps = ["@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common"], +) diff --git a/quality/private/python/tools/pylint_runner.py b/quality/private/python/tools/pylint_runner.py new file mode 100644 index 0000000..17dfd15 --- /dev/null +++ b/quality/private/python/tools/pylint_runner.py @@ -0,0 +1,56 @@ +"""A runner that interfaces python tool aspect and runs pylint on a list of files.""" + +import logging +import os + +from quality.private.python.tools import python_tool_common + + +def check_with_pylint(aspect_arguments: python_tool_common.AspectArguments) -> None: + """Run a pylint subprocess, check its output and write its findings to a file.""" + + pylint_env = os.environ + for target_import in aspect_arguments.target_imports | aspect_arguments.target_dependencies: + if "PYTHONPATH" not in pylint_env: + pylint_env["PYTHONPATH"] = str(target_import) + else: + pylint_env["PYTHONPATH"] += ":" + str(target_import) + + try: + python_tool_common.execute_subprocess( + [ + # Binary executable path. + f"{aspect_arguments.tool}", + # Configuration flag and path. + "--rcfile", + f"{aspect_arguments.tool_config}", + # Putput a colorized text to a given path. + f"--output-format=text:{aspect_arguments.tool_output},colorized", + # Text content template. + "--msg-template", + "{path}:{line}:{column}: {msg} [{msg_id}, {symbol}]", + # Exclude pylint persistent output as this would be both action input and output. + "--persistent", + "no", + # Files to lint + "--", + *aspect_arguments.target_files, + ], + env=pylint_env, + ) + finally: + logging.info("Created pylint output at: %s", aspect_arguments.tool_output) + + +def main(): + """Interfaces python tool aspect and use pylint to check a given set of files.""" + + args = python_tool_common.parse_args() + + logging.basicConfig(level=logging.DEBUG) + + check_with_pylint(aspect_arguments=args) + + +if __name__ == "__main__": + main() diff --git a/quality/private/python/tools/python_tool_common.py b/quality/private/python/tools/python_tool_common.py new file mode 100644 index 0000000..3600798 --- /dev/null +++ b/quality/private/python/tools/python_tool_common.py @@ -0,0 +1,193 @@ +"""Common features for runners that interface the python tool aspect.""" + +import argparse +import dataclasses +import itertools +import os +import pathlib +import subprocess +import typing + + +class PythonPathNotFoundError(Exception): + """Raised when it is not possible to find a target path.""" + + def __init__(self, path: str, tool: str): + self.path = path + self.tool = tool + super().__init__(f'The path "{self.path}" was not found. Therefore {self.tool} cannot properly run.') + + +class LinterFindingAsError(Exception): + """Raised when a linter finds a finding treats it as an error.""" + + def __init__(self, path: str, tool: str): + self.path = path + self.tool = tool + super().__init__(f'At least one {self.tool} finding was treated as error. See its output at "{self.path}"') + + +class LinterSubprocessError(Exception): + """Raised when a linter subprocess fails.""" + + def __init__( + self, + commands: str, + return_code: str, + message: str, + ): + self.commands = commands + self.return_code = return_code + self.message = message + super().__init__( + f'The command "{self.commands}" returned code "{self.return_code}"' + f" and the following error message:\n{self.message}" + ) + + +@dataclasses.dataclass +class SubprocessInfo: + """Class that provides a clean interface to the subprocess output.""" + + stdout: str + stderr: str + return_code: int + + +def execute_subprocess( + commands: list[str], + cwd: pathlib.Path = pathlib.Path.cwd(), + env: typing.Mapping[str, str] = None, +) -> SubprocessInfo: + """Function that calls a subprocess and expects a zero return code.""" + try: + result = subprocess.run( + commands, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=True, + text=True, + shell=False, + env=env or os.environ, + cwd=cwd, + universal_newlines=True, + ) + except subprocess.CalledProcessError as exception: + raise LinterSubprocessError( + commands=commands, + return_code=exception.returncode, + message=exception.stderr or exception.stdout, + ) from None + + return SubprocessInfo( + stdout=result.stdout, + stderr=result.stderr, + return_code=result.returncode, + ) + + +@dataclasses.dataclass +class AspectArguments: + """Class that provides a clean and verified interface between aspect and runner.""" + + target_imports: list[str] + target_dependencies: list[str] + target_files: list[str] + tool: pathlib.Path + tool_config: pathlib.Path + tool_output: pathlib.Path + tool_root: str + + def __post_init__(self): + def resolve_paths(paths: list[str], prepend_path: str = ""): + resolved_paths = set() + for path in paths: + try: + if path.startswith(self.tool_root): + # This is the usual branch for local files or libraries. + # The code go through here when path is relative to the sandbox root. + resolved_paths.add(pathlib.Path(path).relative_to(self.tool_root).resolve(strict=True)) + else: + # This is the usual branch for third-party files or libraries. + # The code go through here when path is not directly relative to sandbox root. + # Instead, a given prepend_path is prepended to path. + resolved_paths.add((pathlib.Path(prepend_path) / path).resolve(strict=True)) + except FileNotFoundError as exception: + # Before throwing an exception, a check for bazel generated files is made. + # For that, a glob for every Bazel compilation mode pattern prepended to path is done. + modes = ["fastbuild", "dbg", "opt"] + try: + resolved_path = next( + itertools.chain.from_iterable( + pathlib.Path.cwd().glob(f"bazel-out/k8-{mode}/bin/{path}") for mode in modes + ) + ) + except StopIteration as _: + # If it doesn't exists, throw an exception. + raise PythonPathNotFoundError( + path=exception.filename, + tool=self.tool.name, + ) from None + # If it exists, add it to the resolved_paths list and continue. + resolved_paths.add(resolved_path) + return resolved_paths + + self.target_imports = resolve_paths(self.target_imports, "external") + self.target_dependencies = resolve_paths(self.target_dependencies) + self.target_files = resolve_paths(self.target_files) + + +def parse_args() -> AspectArguments: + """Parse and return arguments.""" + parser = argparse.ArgumentParser(fromfile_prefix_chars="@") + + parser.add_argument( + "--target-imports", + type=str, + action="extend", + nargs="+", + default=[], + help="", + ) + parser.add_argument( + "--target-dependencies", + type=str, + action="extend", + nargs="+", + default=[], + help="", + ) + parser.add_argument( + "--target-files", + type=str, + action="extend", + nargs="+", + default=[], + help="", + ) + parser.add_argument( + "--tool", + type=pathlib.Path, + required=True, + help="", + ) + parser.add_argument( + "--tool-config", + type=pathlib.Path, + required=True, + help="", + ) + parser.add_argument( + "--tool-output", + type=pathlib.Path, + required=True, + help="", + ) + parser.add_argument( + "--tool-root", + type=str, + required=True, + help="", + ) + + return AspectArguments(**vars(parser.parse_args())) From 55813b95631b4c7e22205058d99d1ff2da19ca14 Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Thu, 27 Jun 2024 10:07:27 +0200 Subject: [PATCH 02/74] [black] Add black aspect and runner This commit: - adds a functional black aspect and runner; - offers the aspect though quality/defs.bzl; - as planned, make use of both python_tool_aspect.bzl and python_tool_common.py; - add a black config to .bazelrc. --- quality/private/python/BUILD | 7 +++ quality/private/python/python_tool_aspect.bzl | 6 +++ quality/private/python/tools/BUILD | 8 +++ quality/private/python/tools/black_runner.py | 51 +++++++++++++++++++ 4 files changed, 72 insertions(+) create mode 100644 quality/private/python/tools/black_runner.py diff --git a/quality/private/python/BUILD b/quality/private/python/BUILD index fdbf7f6..233e577 100644 --- a/quality/private/python/BUILD +++ b/quality/private/python/BUILD @@ -15,3 +15,10 @@ py_console_script_binary( requirement("tomlkit"), ], ) + +py_console_script_binary( + name = "black", + pkg = requirement("black"), + script = "black", + visibility = ["//visibility:public"], +) diff --git a/quality/private/python/python_tool_aspect.bzl b/quality/private/python/python_tool_aspect.bzl index 7c64afa..3f61d0f 100644 --- a/quality/private/python/python_tool_aspect.bzl +++ b/quality/private/python/python_tool_aspect.bzl @@ -139,3 +139,9 @@ pylint_aspect = _python_tool_aspect( runner = "@swf_bazel_rules_quality//quality/private/python/tools:pylint_runner", config = "@swf_bazel_rules_quality//quality:quality_pylint_config", ) + +black_aspect = _python_tool_aspect( + tool = "@swf_bazel_rules_quality//quality/private/python:black", + runner = "@swf_bazel_rules_quality//quality/private/python/tools:black_runner", + config = "@swf_bazel_rules_quality//quality:quality_black_config", +) diff --git a/quality/private/python/tools/BUILD b/quality/private/python/tools/BUILD index ffb8139..190a6eb 100644 --- a/quality/private/python/tools/BUILD +++ b/quality/private/python/tools/BUILD @@ -12,3 +12,11 @@ py_binary( visibility = ["//visibility:public"], deps = ["@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common"], ) + +py_binary( + name = "black_runner", + srcs = ["black_runner.py"], + data = ["@swf_bazel_rules_quality//quality/private/python:black"], + visibility = ["//visibility:public"], + deps = ["@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common"], +) diff --git a/quality/private/python/tools/black_runner.py b/quality/private/python/tools/black_runner.py new file mode 100644 index 0000000..f5bea17 --- /dev/null +++ b/quality/private/python/tools/black_runner.py @@ -0,0 +1,51 @@ +"""A runner that interfaces python tool aspect and runs black on a list of files.""" + +import logging + +from quality.private.python.tools import python_tool_common + +BLACK_DEFAULT_ERROR_MSG = "file would be reformatted" + + +def check_with_black(aspect_arguments: python_tool_common.AspectArguments) -> None: + """Run a black subprocess, check its output and write its findings to a file. + + :param aspect_arguments: + The arguments received from the python_tool_aspect and already processed by + python_tool_common module. + :raises LinterFindingAsError: + If black finds a file to be formatted. + """ + + black_output = python_tool_common.execute_subprocess( + [ + f"{aspect_arguments.tool}", + "--diff", + "--config", + f"{aspect_arguments.tool_config}", + *aspect_arguments.target_files, + ], + ) + + aspect_arguments.tool_output.write_text(black_output.stdout) + logging.info("Created black output at: %s", aspect_arguments.tool_output) + + if BLACK_DEFAULT_ERROR_MSG in black_output.stderr: + raise python_tool_common.LinterFindingAsError( + path=aspect_arguments.tool_output, + tool=aspect_arguments.tool.name, + ) + + +def main(): + """Interfaces python tool aspect and use black to check a given set of files.""" + + args = python_tool_common.parse_args() + + logging.basicConfig(level=logging.DEBUG) + + check_with_black(aspect_arguments=args) + + +if __name__ == "__main__": + main() From 0ea92d40d6f6a6fcbbcfb91ca3b6c9fb117ca1f4 Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Thu, 11 Jul 2024 16:24:37 +0200 Subject: [PATCH 03/74] Toolchains from rules_python and support for both bzlmod and workspace We currently have our own python toolchain and support only bazel workspace, even though we have bzlmod activated. This commit removes our python toolchain in favor of rules_python toolchain. By doing this we increase toolchain support for multiple system, add support for both bzlmod and workspace, add support for multiple OSs, and easily update our toolchain version when a new one is released. Other minor changes were made: - change bazel labels to fit the new python toolchain; - add a tools availability to our README.md --- quality/private/python/BUILD | 2 +- quality/private/python/tools/BUILD | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/quality/private/python/BUILD b/quality/private/python/BUILD index 233e577..519b74a 100644 --- a/quality/private/python/BUILD +++ b/quality/private/python/BUILD @@ -1,5 +1,5 @@ load("@swf_bazel_rules_quality//third_party/pip:requirements.bzl", "requirement") -load("@swf_bazel_rules_quality_rules_python//python/entry_points:py_console_script_binary.bzl", "py_console_script_binary") +load("@swf_bazel_rules_quality_python//:defs.bzl", "py_console_script_binary") py_console_script_binary( name = "pylint", diff --git a/quality/private/python/tools/BUILD b/quality/private/python/tools/BUILD index 190a6eb..81e897d 100644 --- a/quality/private/python/tools/BUILD +++ b/quality/private/python/tools/BUILD @@ -1,4 +1,5 @@ -load("@swf_bazel_rules_quality_rules_python//python:defs.bzl", "py_binary", "py_library") +load("@swf_bazel_rules_quality_python//:defs.bzl", "py_binary") +load("@swf_bazel_rules_quality_rules_python//python:defs.bzl", "py_library") py_library( name = "python_tool_common", From 620cb40eed02716a6d9639a0eee6f838efdd3a56 Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Thu, 27 Jun 2024 16:26:46 +0200 Subject: [PATCH 04/74] [python] Improve LinterSubprocessError content This modification allows the caller to have access to the actual subprocess stdout and stderr. This is important because the caller may rely on that information in case of a non-zero return code. Example, isort returns 1 when it finds something, but we still want execute_subprocess to throw so we, on the application level, may be able to catch and evaluate it. Each application will do its own evaluation. --- quality/private/python/tools/python_tool_common.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/quality/private/python/tools/python_tool_common.py b/quality/private/python/tools/python_tool_common.py index 3600798..c1cd3c8 100644 --- a/quality/private/python/tools/python_tool_common.py +++ b/quality/private/python/tools/python_tool_common.py @@ -34,14 +34,16 @@ def __init__( self, commands: str, return_code: str, - message: str, + stdout: str, + stderr: str, ): self.commands = commands self.return_code = return_code - self.message = message + self.stdout = stdout + self.stderr = stderr super().__init__( f'The command "{self.commands}" returned code "{self.return_code}"' - f" and the following error message:\n{self.message}" + f" and the following error message:\n{stderr or stdout}" ) @@ -76,7 +78,8 @@ def execute_subprocess( raise LinterSubprocessError( commands=commands, return_code=exception.returncode, - message=exception.stderr or exception.stdout, + stdout=exception.stdout, + stderr=exception.stderr, ) from None return SubprocessInfo( From adb21f0ab007ce4ffc1abf35926ceac164d760dd Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Thu, 27 Jun 2024 16:27:32 +0200 Subject: [PATCH 05/74] [isort] Add isort aspect and runner This commit: - adds a functional isort aspect and runner; - offers the aspect though quality/defs.bzl; - as planned, make use of both python_tool_aspect.bzl and python_tool_common.py; - add a isort config to .bazelrc. --- quality/private/python/BUILD | 7 +++ quality/private/python/python_tool_aspect.bzl | 6 ++ quality/private/python/tools/BUILD | 8 +++ quality/private/python/tools/isort_runner.py | 62 +++++++++++++++++++ 4 files changed, 83 insertions(+) create mode 100644 quality/private/python/tools/isort_runner.py diff --git a/quality/private/python/BUILD b/quality/private/python/BUILD index 519b74a..d3ae1a4 100644 --- a/quality/private/python/BUILD +++ b/quality/private/python/BUILD @@ -22,3 +22,10 @@ py_console_script_binary( script = "black", visibility = ["//visibility:public"], ) + +py_console_script_binary( + name = "isort", + pkg = requirement("isort"), + script = "isort", + visibility = ["//visibility:public"], +) diff --git a/quality/private/python/python_tool_aspect.bzl b/quality/private/python/python_tool_aspect.bzl index 3f61d0f..7434840 100644 --- a/quality/private/python/python_tool_aspect.bzl +++ b/quality/private/python/python_tool_aspect.bzl @@ -145,3 +145,9 @@ black_aspect = _python_tool_aspect( runner = "@swf_bazel_rules_quality//quality/private/python/tools:black_runner", config = "@swf_bazel_rules_quality//quality:quality_black_config", ) + +isort_aspect = _python_tool_aspect( + tool = "@swf_bazel_rules_quality//quality/private/python:isort", + runner = "@swf_bazel_rules_quality//quality/private/python/tools:isort_runner", + config = "@swf_bazel_rules_quality//quality:quality_isort_config", +) diff --git a/quality/private/python/tools/BUILD b/quality/private/python/tools/BUILD index 81e897d..81e40fe 100644 --- a/quality/private/python/tools/BUILD +++ b/quality/private/python/tools/BUILD @@ -21,3 +21,11 @@ py_binary( visibility = ["//visibility:public"], deps = ["@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common"], ) + +py_binary( + name = "isort_runner", + srcs = ["isort_runner.py"], + data = ["@swf_bazel_rules_quality//quality/private/python:isort"], + visibility = ["//visibility:public"], + deps = ["@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common"], +) diff --git a/quality/private/python/tools/isort_runner.py b/quality/private/python/tools/isort_runner.py new file mode 100644 index 0000000..aaf04d8 --- /dev/null +++ b/quality/private/python/tools/isort_runner.py @@ -0,0 +1,62 @@ +"""A runner that interfaces python tool aspect and runs isort on a list of files.""" + +import logging + +from quality.private.python.tools import python_tool_common + +ISORT_BAD_CHECK_ERROR_CODE = 1 + + +def check_with_isort(aspect_arguments: python_tool_common.AspectArguments) -> None: + """Run a isort subprocess, check its output and write its findings to a file. + + :param aspect_arguments: + The arguments received from the python_tool_aspect and already processed by + python_tool_common module. + :raises LinterFindingAsError: + If isort finds a file to be formatted. + """ + + try: + isort_output = python_tool_common.execute_subprocess( + [ + f"{aspect_arguments.tool}", + "--check-only", + "--diff", + "--sp", + f"{aspect_arguments.tool_config}", + "--", + *aspect_arguments.target_files, + ], + ) + except python_tool_common.LinterSubprocessError as exception: + if exception.return_code != ISORT_BAD_CHECK_ERROR_CODE: + raise + isort_output = python_tool_common.SubprocessInfo( + exception.stdout, + exception.stderr, + exception.return_code, + ) + + aspect_arguments.tool_output.write_text(isort_output.stdout) + logging.info("Created isort output at: %s", aspect_arguments.tool_output) + + if isort_output.return_code: + raise python_tool_common.LinterFindingAsError( + path=aspect_arguments.tool_output, + tool=aspect_arguments.tool.name, + ) + + +def main(): + """Interfaces python tool aspect and use isort to check a given set of files.""" + + args = python_tool_common.parse_args() + + logging.basicConfig(level=logging.DEBUG) + + check_with_isort(aspect_arguments=args) + + +if __name__ == "__main__": + main() From ab8fd424c40a31c0ac9cf7c27c3c9c0a19770259 Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Thu, 25 Jul 2024 12:34:45 +0200 Subject: [PATCH 06/74] [python] Fix how python_tool_common check for relative paths The current approach is wrong because it relies on the string `startswith` method. This work on some cases but throws an exception on another ones, for example, "myapp" is not relative to "myapp_lib/path/..." but "myapp_lib/path/.." startswiuth "myapp". To avoid this we should rely on pathlib `is_relative_to`. This ensures that a path is actually relative to the other one instead of just comparing the string. --- quality/private/python/tools/python_tool_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quality/private/python/tools/python_tool_common.py b/quality/private/python/tools/python_tool_common.py index c1cd3c8..bf79830 100644 --- a/quality/private/python/tools/python_tool_common.py +++ b/quality/private/python/tools/python_tool_common.py @@ -106,7 +106,7 @@ def resolve_paths(paths: list[str], prepend_path: str = ""): resolved_paths = set() for path in paths: try: - if path.startswith(self.tool_root): + if pathlib.Path(path).is_relative_to(self.tool_root): # This is the usual branch for local files or libraries. # The code go through here when path is relative to the sandbox root. resolved_paths.add(pathlib.Path(path).relative_to(self.tool_root).resolve(strict=True)) From 75286798d1cc281e0865daf27e8858c1b6a14ad4 Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Wed, 7 Aug 2024 09:25:58 +0200 Subject: [PATCH 07/74] [python] Add mypy aspect and runner This commit: - adds a functional mypy aspect and runner; - offers the aspect though quality/defs.bzl; - as planned, make use of both python_tool_aspect.bzl and python_tool_common.py; - add a mypy config to .bazelrc. --- quality/private/python/BUILD | 11 +++ quality/private/python/python_tool_aspect.bzl | 6 ++ quality/private/python/tools/BUILD | 8 +++ quality/private/python/tools/mypy_runner.py | 67 +++++++++++++++++++ 4 files changed, 92 insertions(+) create mode 100644 quality/private/python/tools/mypy_runner.py diff --git a/quality/private/python/BUILD b/quality/private/python/BUILD index d3ae1a4..b91e4ff 100644 --- a/quality/private/python/BUILD +++ b/quality/private/python/BUILD @@ -29,3 +29,14 @@ py_console_script_binary( script = "isort", visibility = ["//visibility:public"], ) + +py_console_script_binary( + name = "mypy", + pkg = requirement("mypy"), + script = "mypy", + visibility = ["//visibility:public"], + deps = [ + requirement("types-jsonschema"), + requirement("types-PyYAML"), + ], +) diff --git a/quality/private/python/python_tool_aspect.bzl b/quality/private/python/python_tool_aspect.bzl index 7434840..b3645e3 100644 --- a/quality/private/python/python_tool_aspect.bzl +++ b/quality/private/python/python_tool_aspect.bzl @@ -151,3 +151,9 @@ isort_aspect = _python_tool_aspect( runner = "@swf_bazel_rules_quality//quality/private/python/tools:isort_runner", config = "@swf_bazel_rules_quality//quality:quality_isort_config", ) + +mypy_aspect = _python_tool_aspect( + tool = "@swf_bazel_rules_quality//quality/private/python:mypy", + runner = "@swf_bazel_rules_quality//quality/private/python/tools:mypy_runner", + config = "@swf_bazel_rules_quality//quality:quality_mypy_config", +) diff --git a/quality/private/python/tools/BUILD b/quality/private/python/tools/BUILD index 81e40fe..2ee6691 100644 --- a/quality/private/python/tools/BUILD +++ b/quality/private/python/tools/BUILD @@ -29,3 +29,11 @@ py_binary( visibility = ["//visibility:public"], deps = ["@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common"], ) + +py_binary( + name = "mypy_runner", + srcs = ["mypy_runner.py"], + data = ["@swf_bazel_rules_quality//quality/private/python:mypy"], + visibility = ["//visibility:public"], + deps = ["@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common"], +) diff --git a/quality/private/python/tools/mypy_runner.py b/quality/private/python/tools/mypy_runner.py new file mode 100644 index 0000000..b8ab9d9 --- /dev/null +++ b/quality/private/python/tools/mypy_runner.py @@ -0,0 +1,67 @@ +"""A runner that interfaces python tool aspect and runs mypy on a list of files.""" + +import logging +import os + +from quality.private.python.tools import python_tool_common + +MYPY_BAD_CHECK_ERROR_CODE = 1 + + +def check_with_mypy(aspect_arguments: python_tool_common.AspectArguments) -> None: + """Run a mypy subprocess, check its output and write its findings to a file. + + :param aspect_arguments: + The arguments received from the python_tool_aspect and already processed by + python_tool_common module. + :raises LinterFindingAsError: + If mypy finds a file to be formatted. + """ + + pylint_env = os.environ + for target_import in aspect_arguments.target_imports | aspect_arguments.target_dependencies: + if "PYTHONPATH" not in pylint_env: + pylint_env["PYTHONPATH"] = str(target_import) + else: + pylint_env["PYTHONPATH"] += ":" + str(target_import) + + try: + mypy_output = python_tool_common.execute_subprocess( + [ + f"{aspect_arguments.tool}", + "--config-file", + f"{aspect_arguments.tool_config}", + *map(str, aspect_arguments.target_files), + ], + ) + except python_tool_common.LinterSubprocessError as exception: + if exception.return_code != MYPY_BAD_CHECK_ERROR_CODE: + raise + mypy_output = python_tool_common.SubprocessInfo( + exception.stdout, + exception.stderr, + exception.return_code, + ) + + aspect_arguments.tool_output.write_text(mypy_output.stdout) + logging.info("Created mypy output at: %s", aspect_arguments.tool_output) + + if mypy_output.return_code: + raise python_tool_common.LinterFindingAsError( + path=aspect_arguments.tool_output, + tool=aspect_arguments.tool.name, + ) + + +def main(): + """Interfaces python tool aspect and use mypy to check a given set of files.""" + + args = python_tool_common.parse_args() + + logging.basicConfig(level=logging.DEBUG) + + check_with_mypy(aspect_arguments=args) + + +if __name__ == "__main__": + main() From b497b7bcf535f19d7fa408026e9b3ffa616c49f3 Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Tue, 6 Aug 2024 18:21:10 +0200 Subject: [PATCH 08/74] [python] Fix mypy findings This commit fixes every finding that doesn't come from clang-tidy or qac targets. Later PRs can fix those as well. --- quality/private/python/tools/black_runner.py | 2 +- quality/private/python/tools/isort_runner.py | 2 +- quality/private/python/tools/pylint_runner.py | 2 +- .../python/tools/python_tool_common.py | 20 +++++++++---------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/quality/private/python/tools/black_runner.py b/quality/private/python/tools/black_runner.py index f5bea17..d43eb42 100644 --- a/quality/private/python/tools/black_runner.py +++ b/quality/private/python/tools/black_runner.py @@ -23,7 +23,7 @@ def check_with_black(aspect_arguments: python_tool_common.AspectArguments) -> No "--diff", "--config", f"{aspect_arguments.tool_config}", - *aspect_arguments.target_files, + *map(str, aspect_arguments.target_files), ], ) diff --git a/quality/private/python/tools/isort_runner.py b/quality/private/python/tools/isort_runner.py index aaf04d8..4d38731 100644 --- a/quality/private/python/tools/isort_runner.py +++ b/quality/private/python/tools/isort_runner.py @@ -26,7 +26,7 @@ def check_with_isort(aspect_arguments: python_tool_common.AspectArguments) -> No "--sp", f"{aspect_arguments.tool_config}", "--", - *aspect_arguments.target_files, + *map(str, aspect_arguments.target_files), ], ) except python_tool_common.LinterSubprocessError as exception: diff --git a/quality/private/python/tools/pylint_runner.py b/quality/private/python/tools/pylint_runner.py index 17dfd15..0aef2a1 100644 --- a/quality/private/python/tools/pylint_runner.py +++ b/quality/private/python/tools/pylint_runner.py @@ -34,7 +34,7 @@ def check_with_pylint(aspect_arguments: python_tool_common.AspectArguments) -> N "no", # Files to lint "--", - *aspect_arguments.target_files, + *map(str, aspect_arguments.target_files), ], env=pylint_env, ) diff --git a/quality/private/python/tools/python_tool_common.py b/quality/private/python/tools/python_tool_common.py index bf79830..4f5e5d3 100644 --- a/quality/private/python/tools/python_tool_common.py +++ b/quality/private/python/tools/python_tool_common.py @@ -6,7 +6,7 @@ import os import pathlib import subprocess -import typing +import typing as t class PythonPathNotFoundError(Exception): @@ -21,7 +21,7 @@ def __init__(self, path: str, tool: str): class LinterFindingAsError(Exception): """Raised when a linter finds a finding treats it as an error.""" - def __init__(self, path: str, tool: str): + def __init__(self, path: t.Union[str, pathlib.Path], tool: str): self.path = path self.tool = tool super().__init__(f'At least one {self.tool} finding was treated as error. See its output at "{self.path}"') @@ -33,7 +33,7 @@ class LinterSubprocessError(Exception): def __init__( self, commands: str, - return_code: str, + return_code: t.Union[str, int], stdout: str, stderr: str, ): @@ -53,13 +53,13 @@ class SubprocessInfo: stdout: str stderr: str - return_code: int + return_code: t.Union[str, int] def execute_subprocess( commands: list[str], cwd: pathlib.Path = pathlib.Path.cwd(), - env: typing.Mapping[str, str] = None, + env: t.Optional[t.Mapping[str, str]] = None, ) -> SubprocessInfo: """Function that calls a subprocess and expects a zero return code.""" try: @@ -76,7 +76,7 @@ def execute_subprocess( ) except subprocess.CalledProcessError as exception: raise LinterSubprocessError( - commands=commands, + commands=str(commands), return_code=exception.returncode, stdout=exception.stdout, stderr=exception.stderr, @@ -93,16 +93,16 @@ def execute_subprocess( class AspectArguments: """Class that provides a clean and verified interface between aspect and runner.""" - target_imports: list[str] - target_dependencies: list[str] - target_files: list[str] + target_imports: set[pathlib.Path] + target_dependencies: set[pathlib.Path] + target_files: set[pathlib.Path] tool: pathlib.Path tool_config: pathlib.Path tool_output: pathlib.Path tool_root: str def __post_init__(self): - def resolve_paths(paths: list[str], prepend_path: str = ""): + def resolve_paths(paths: list[str], prepend_path: str = "") -> set[pathlib.Path]: resolved_paths = set() for path in paths: try: From 8e7dc9b1cbc8e62b1a5f30488709d7764e5478b1 Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Wed, 7 Aug 2024 13:19:34 +0200 Subject: [PATCH 09/74] [python] Change how we create python aspect output file name Multiple python runners may use the same entry_point, for example, ruff. With that in mind one might expect that, as the output file name is based on the tool and not on the runner, a conflict will happen when both runners are triggered. This commit prevents that by changing how we name the output file, which will from now on be based on the runner name. This commit also renamed runners, for example, pylint_runner to pylint, and also add an "_entry_point" to the py_console_script_binaries to avoid confusion. With this, for example, the pylint output file will still be called pylint_output_target_name. --- quality/private/python/BUILD | 6 +++--- quality/private/python/python_tool_aspect.bzl | 16 ++++++++-------- quality/private/python/tools/BUILD | 18 +++++++++--------- .../python/tools/{black_runner.py => black.py} | 0 .../python/tools/{isort_runner.py => isort.py} | 0 .../tools/{pylint_runner.py => pylint.py} | 0 6 files changed, 20 insertions(+), 20 deletions(-) rename quality/private/python/tools/{black_runner.py => black.py} (100%) rename quality/private/python/tools/{isort_runner.py => isort.py} (100%) rename quality/private/python/tools/{pylint_runner.py => pylint.py} (100%) diff --git a/quality/private/python/BUILD b/quality/private/python/BUILD index b91e4ff..adfce7a 100644 --- a/quality/private/python/BUILD +++ b/quality/private/python/BUILD @@ -2,7 +2,7 @@ load("@swf_bazel_rules_quality//third_party/pip:requirements.bzl", "requirement" load("@swf_bazel_rules_quality_python//:defs.bzl", "py_console_script_binary") py_console_script_binary( - name = "pylint", + name = "pylint_entry_point", pkg = requirement("pylint"), script = "pylint", visibility = ["//visibility:public"], @@ -17,14 +17,14 @@ py_console_script_binary( ) py_console_script_binary( - name = "black", + name = "black_entry_point", pkg = requirement("black"), script = "black", visibility = ["//visibility:public"], ) py_console_script_binary( - name = "isort", + name = "isort_entry_point", pkg = requirement("isort"), script = "isort", visibility = ["//visibility:public"], diff --git a/quality/private/python/python_tool_aspect.bzl b/quality/private/python/python_tool_aspect.bzl index b3645e3..85c065f 100644 --- a/quality/private/python/python_tool_aspect.bzl +++ b/quality/private/python/python_tool_aspect.bzl @@ -64,7 +64,7 @@ def _python_tool_aspect_implementation(target, ctx): sources_to_run.append(source.label.name) if sources_to_run: - output_file = ctx.actions.declare_file(ctx.executable._tool.basename + "_output_" + target.label.name) + output_file = ctx.actions.declare_file(ctx.executable._runner.basename + "_output_" + target.label.name) output.append(output_file) imports = target[PyInfo].imports.to_list() @@ -92,7 +92,7 @@ def _python_tool_aspect_implementation(target, ctx): tools = [ctx.executable._runner, ctx.executable._tool, target[DefaultInfo].files_to_run], executable = ctx.executable._runner, arguments = [args], - progress_message = "Running {tool} on: {target_name}".format(tool = ctx.executable._tool.basename, target_name = target.label.name), + progress_message = "Running {tool} on: {target_name}".format(tool = ctx.executable._runner.basename, target_name = target.label.name), ) return [OutputGroupInfo(python_tool_output = depset(output))] @@ -135,20 +135,20 @@ def _python_tool_aspect(tool, runner, config): ) pylint_aspect = _python_tool_aspect( - tool = "@swf_bazel_rules_quality//quality/private/python:pylint", - runner = "@swf_bazel_rules_quality//quality/private/python/tools:pylint_runner", + tool = "@swf_bazel_rules_quality//quality/private/python:pylint_entry_point", + runner = "@swf_bazel_rules_quality//quality/private/python/tools:pylint", config = "@swf_bazel_rules_quality//quality:quality_pylint_config", ) black_aspect = _python_tool_aspect( - tool = "@swf_bazel_rules_quality//quality/private/python:black", - runner = "@swf_bazel_rules_quality//quality/private/python/tools:black_runner", + tool = "@swf_bazel_rules_quality//quality/private/python:black_entry_point", + runner = "@swf_bazel_rules_quality//quality/private/python/tools:black", config = "@swf_bazel_rules_quality//quality:quality_black_config", ) isort_aspect = _python_tool_aspect( - tool = "@swf_bazel_rules_quality//quality/private/python:isort", - runner = "@swf_bazel_rules_quality//quality/private/python/tools:isort_runner", + tool = "@swf_bazel_rules_quality//quality/private/python:isort_entry_point", + runner = "@swf_bazel_rules_quality//quality/private/python/tools:isort", config = "@swf_bazel_rules_quality//quality:quality_isort_config", ) diff --git a/quality/private/python/tools/BUILD b/quality/private/python/tools/BUILD index 2ee6691..49aa0f2 100644 --- a/quality/private/python/tools/BUILD +++ b/quality/private/python/tools/BUILD @@ -7,25 +7,25 @@ py_library( ) py_binary( - name = "pylint_runner", - srcs = ["pylint_runner.py"], - data = ["@swf_bazel_rules_quality//quality/private/python:pylint"], + name = "pylint", + srcs = ["pylint.py"], + data = ["@swf_bazel_rules_quality//quality/private/python:pylint_entry_point"], visibility = ["//visibility:public"], deps = ["@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common"], ) py_binary( - name = "black_runner", - srcs = ["black_runner.py"], - data = ["@swf_bazel_rules_quality//quality/private/python:black"], + name = "black", + srcs = ["black.py"], + data = ["@swf_bazel_rules_quality//quality/private/python:black_entry_point"], visibility = ["//visibility:public"], deps = ["@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common"], ) py_binary( - name = "isort_runner", - srcs = ["isort_runner.py"], - data = ["@swf_bazel_rules_quality//quality/private/python:isort"], + name = "isort", + srcs = ["isort.py"], + data = ["@swf_bazel_rules_quality//quality/private/python:isort_entry_point"], visibility = ["//visibility:public"], deps = ["@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common"], ) diff --git a/quality/private/python/tools/black_runner.py b/quality/private/python/tools/black.py similarity index 100% rename from quality/private/python/tools/black_runner.py rename to quality/private/python/tools/black.py diff --git a/quality/private/python/tools/isort_runner.py b/quality/private/python/tools/isort.py similarity index 100% rename from quality/private/python/tools/isort_runner.py rename to quality/private/python/tools/isort.py diff --git a/quality/private/python/tools/pylint_runner.py b/quality/private/python/tools/pylint.py similarity index 100% rename from quality/private/python/tools/pylint_runner.py rename to quality/private/python/tools/pylint.py From 027c80c88f330060dc32d078915131a4c8e7fcaf Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Wed, 14 Aug 2024 12:35:07 +0200 Subject: [PATCH 10/74] [python] Add ruff aspect and runner This commit: - adds two functional ruff aspects and runners, one to check files and another to format files; - offers the aspects through quality/defs.bzl; - as planned, make use of both python_tool_aspect.bzl and python_tool_common.py; - add a ruff config to .bazelrc. Two runners were created because ruff has both a check and a formatter. Each one of those must be invoked individually and therefore we have two output files. Knowing that the aspect requires one output file, ruff runner was splitted into two. Also, as ruff python library doesn't provide a default entry point, a ruff_entry_point.py had to be created. --- quality/private/python/BUILD | 10 ++- quality/private/python/python_tool_aspect.bzl | 12 ++++ quality/private/python/ruff_entry_point.py | 20 ++++++ quality/private/python/tools/BUILD | 20 ++++++ quality/private/python/tools/ruff_check.py | 62 +++++++++++++++++++ quality/private/python/tools/ruff_format.py | 61 ++++++++++++++++++ 6 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 quality/private/python/ruff_entry_point.py create mode 100644 quality/private/python/tools/ruff_check.py create mode 100644 quality/private/python/tools/ruff_format.py diff --git a/quality/private/python/BUILD b/quality/private/python/BUILD index adfce7a..8854e5c 100644 --- a/quality/private/python/BUILD +++ b/quality/private/python/BUILD @@ -1,5 +1,5 @@ load("@swf_bazel_rules_quality//third_party/pip:requirements.bzl", "requirement") -load("@swf_bazel_rules_quality_python//:defs.bzl", "py_console_script_binary") +load("@swf_bazel_rules_quality_python//:defs.bzl", "py_binary", "py_console_script_binary") py_console_script_binary( name = "pylint_entry_point", @@ -40,3 +40,11 @@ py_console_script_binary( requirement("types-PyYAML"), ], ) + +py_binary( + name = "ruff_entry_point", + srcs = ["ruff_entry_point.py"], + main = "ruff_entry_point.py", + visibility = ["//visibility:public"], + deps = [requirement("ruff")], +) diff --git a/quality/private/python/python_tool_aspect.bzl b/quality/private/python/python_tool_aspect.bzl index 85c065f..f0f221c 100644 --- a/quality/private/python/python_tool_aspect.bzl +++ b/quality/private/python/python_tool_aspect.bzl @@ -157,3 +157,15 @@ mypy_aspect = _python_tool_aspect( runner = "@swf_bazel_rules_quality//quality/private/python/tools:mypy_runner", config = "@swf_bazel_rules_quality//quality:quality_mypy_config", ) + +ruff_check_aspect = _python_tool_aspect( + tool = "@swf_bazel_rules_quality//quality/private/python:ruff_entry_point", + runner = "@swf_bazel_rules_quality//quality/private/python/tools:ruff_check", + config = "@swf_bazel_rules_quality//quality:quality_ruff_config", +) + +ruff_format_aspect = _python_tool_aspect( + tool = "@swf_bazel_rules_quality//quality/private/python:ruff_entry_point", + runner = "@swf_bazel_rules_quality//quality/private/python/tools:ruff_format", + config = "@swf_bazel_rules_quality//quality:quality_ruff_config", +) diff --git a/quality/private/python/ruff_entry_point.py b/quality/private/python/ruff_entry_point.py new file mode 100644 index 0000000..762fc2b --- /dev/null +++ b/quality/private/python/ruff_entry_point.py @@ -0,0 +1,20 @@ +"""Entry point for ruff python library. + +This executes ruff as a subprocess by finding its binary, forwarding every + argument and finally outputing its stdout, stderr and return code. +""" + +import subprocess +import sys +from pathlib import Path + +from ruff import __main__ as ruff_main # type: ignore[import-untyped] + +if __name__ == "__main__": + ruff_main_path = Path(ruff_main.__file__).resolve(strict=True) + ruff_external = ruff_main_path.parent.parent.parent + ruff_bin = ruff_external / "bin/ruff" + result = subprocess.run([ruff_bin, *sys.argv[1:]], capture_output=True, text=True, check=False) + sys.stdout.write(result.stdout) + sys.stderr.write(result.stderr) + sys.exit(result.returncode) diff --git a/quality/private/python/tools/BUILD b/quality/private/python/tools/BUILD index 49aa0f2..23e71d4 100644 --- a/quality/private/python/tools/BUILD +++ b/quality/private/python/tools/BUILD @@ -37,3 +37,23 @@ py_binary( visibility = ["//visibility:public"], deps = ["@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common"], ) + +py_binary( + name = "ruff_check", + srcs = ["ruff_check.py"], + visibility = ["//visibility:public"], + deps = [ + "@swf_bazel_rules_quality//quality/private/python:ruff_entry_point", + "@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common", + ], +) + +py_binary( + name = "ruff_format", + srcs = ["ruff_format.py"], + visibility = ["//visibility:public"], + deps = [ + "@swf_bazel_rules_quality//quality/private/python:ruff_entry_point", + "@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common", + ], +) diff --git a/quality/private/python/tools/ruff_check.py b/quality/private/python/tools/ruff_check.py new file mode 100644 index 0000000..ada012d --- /dev/null +++ b/quality/private/python/tools/ruff_check.py @@ -0,0 +1,62 @@ +"""A runner that interfaces python tool aspect and runs ruff on a list of files.""" + +import logging + +from quality.private.python.tools import python_tool_common + +RUFF_BAD_CHECK_ERROR_CODE = 1 + + +def check_with_ruff(aspect_arguments: python_tool_common.AspectArguments) -> None: + """Run a ruff check subprocess, check its output and write its findings to a file. + + :param aspect_arguments: + The arguments received from the python_tool_aspect and already processed by + python_tool_common module. + :raises LinterFindingAsError: + If ruff finds a file to be formatted. + """ + + try: + ruff_output = python_tool_common.execute_subprocess( + [ + f"{aspect_arguments.tool}", + "check", + "--config", + f"{aspect_arguments.tool_config}", + "--unsafe-fixes", + "--diff", + *map(str, aspect_arguments.target_files), + ], + ) + except python_tool_common.LinterSubprocessError as exception: + if exception.return_code != RUFF_BAD_CHECK_ERROR_CODE: + raise + ruff_output = python_tool_common.SubprocessInfo( + exception.stdout, + exception.stderr, + exception.return_code, + ) + + aspect_arguments.tool_output.write_text(ruff_output.stdout) + logging.info("Created ruff output at: %s", aspect_arguments.tool_output) + + if ruff_output.return_code: + raise python_tool_common.LinterFindingAsError( + path=aspect_arguments.tool_output, + tool=aspect_arguments.tool.name, + ) + + +def main(): + """Interfaces python tool aspect and use ruff to check a given set of files.""" + + args = python_tool_common.parse_args() + + logging.basicConfig(level=logging.DEBUG) + + check_with_ruff(aspect_arguments=args) + + +if __name__ == "__main__": + main() diff --git a/quality/private/python/tools/ruff_format.py b/quality/private/python/tools/ruff_format.py new file mode 100644 index 0000000..0b75e66 --- /dev/null +++ b/quality/private/python/tools/ruff_format.py @@ -0,0 +1,61 @@ +"""A runner that interfaces python tool aspect and runs ruff on a list of files.""" + +import logging + +from quality.private.python.tools import python_tool_common + +RUFF_BAD_CHECK_ERROR_CODE = 1 + + +def format_with_ruff(aspect_arguments: python_tool_common.AspectArguments) -> None: + """Run a ruff format subprocess, check its output and write its findings to a file. + + :param aspect_arguments: + The arguments received from the python_tool_aspect and already processed by + python_tool_common module. + :raises LinterFindingAsError: + If ruff finds a file to be formatted. + """ + + try: + ruff_output = python_tool_common.execute_subprocess( + [ + f"{aspect_arguments.tool}", + "format", + "--config", + f"{aspect_arguments.tool_config}", + "--diff", + *map(str, aspect_arguments.target_files), + ], + ) + except python_tool_common.LinterSubprocessError as exception: + if exception.return_code != RUFF_BAD_CHECK_ERROR_CODE: + raise + ruff_output = python_tool_common.SubprocessInfo( + exception.stdout, + exception.stderr, + exception.return_code, + ) + + aspect_arguments.tool_output.write_text(ruff_output.stdout) + logging.info("Created ruff output at: %s", aspect_arguments.tool_output) + + if ruff_output.return_code: + raise python_tool_common.LinterFindingAsError( + path=aspect_arguments.tool_output, + tool=aspect_arguments.tool.name, + ) + + +def main(): + """Interfaces python tool aspect and use ruff to check a given set of files.""" + + args = python_tool_common.parse_args() + + logging.basicConfig(level=logging.DEBUG) + + format_with_ruff(aspect_arguments=args) + + +if __name__ == "__main__": + main() From e3078cc0accf7c76a0f3ebf34223876eb17a484a Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Wed, 14 Aug 2024 12:37:03 +0200 Subject: [PATCH 11/74] [python] Create a support directory with a default pyproject file The default config should not be tied to the one that we have in our root. Also, as every python_tool_config was the same we only need to keep one of those. With that, label_flag can now references to the same python config. --- quality/private/python/support/BUILD | 5 +++ quality/private/python/support/pyproject.toml | 34 +++++++++++++++++++ quality/private/python/tools/isort.py | 3 ++ 3 files changed, 42 insertions(+) create mode 100644 quality/private/python/support/BUILD create mode 100644 quality/private/python/support/pyproject.toml diff --git a/quality/private/python/support/BUILD b/quality/private/python/support/BUILD new file mode 100644 index 0000000..d7c608a --- /dev/null +++ b/quality/private/python/support/BUILD @@ -0,0 +1,5 @@ +filegroup( + name = "pyproject_toml", + srcs = ["pyproject.toml"], + visibility = ["//visibility:public"], +) diff --git a/quality/private/python/support/pyproject.toml b/quality/private/python/support/pyproject.toml new file mode 100644 index 0000000..90126dc --- /dev/null +++ b/quality/private/python/support/pyproject.toml @@ -0,0 +1,34 @@ +[tool.black] +line-length = 120 + +[tool.isort] +profile = "black" + +[tool.pylint] +max-line-length=120 + +[tool.pylint.messages_control] +disable = [ + "duplicate-code", + "logging-fstring-interpolation", +] + +[tool.mypy] +disallow_untyped_defs = false +explicit_package_bases = true + +[tool.ruff] +# Same as Black. +line-length = 120 +indent-width = 4 +target-version = "py39" + +[tool.ruff.format] +# Like Black, use double quotes for strings. +quote-style = "double" +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" diff --git a/quality/private/python/tools/isort.py b/quality/private/python/tools/isort.py index 4d38731..8a3d0be 100644 --- a/quality/private/python/tools/isort.py +++ b/quality/private/python/tools/isort.py @@ -1,6 +1,7 @@ """A runner that interfaces python tool aspect and runs isort on a list of files.""" import logging +import pathlib from quality.private.python.tools import python_tool_common @@ -25,6 +26,8 @@ def check_with_isort(aspect_arguments: python_tool_common.AspectArguments) -> No "--diff", "--sp", f"{aspect_arguments.tool_config}", + "--src", + f"{pathlib.Path.cwd()}", "--", *map(str, aspect_arguments.target_files), ], From da8747ca29ea1037289e3577e12b6df218ab3dad Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Wed, 21 Aug 2024 10:24:07 +0200 Subject: [PATCH 12/74] [python] Fix mypy nomenclature Mypy is still using the old nomenclature style. This happened because while other python tools were being refactored mypy was being added to the repo. --- quality/private/python/BUILD | 2 +- quality/private/python/python_tool_aspect.bzl | 4 ++-- quality/private/python/tools/BUILD | 6 +++--- quality/private/python/tools/{mypy_runner.py => mypy.py} | 0 4 files changed, 6 insertions(+), 6 deletions(-) rename quality/private/python/tools/{mypy_runner.py => mypy.py} (100%) diff --git a/quality/private/python/BUILD b/quality/private/python/BUILD index 8854e5c..ba0beab 100644 --- a/quality/private/python/BUILD +++ b/quality/private/python/BUILD @@ -31,7 +31,7 @@ py_console_script_binary( ) py_console_script_binary( - name = "mypy", + name = "mypy_entry_point", pkg = requirement("mypy"), script = "mypy", visibility = ["//visibility:public"], diff --git a/quality/private/python/python_tool_aspect.bzl b/quality/private/python/python_tool_aspect.bzl index f0f221c..8d81cbb 100644 --- a/quality/private/python/python_tool_aspect.bzl +++ b/quality/private/python/python_tool_aspect.bzl @@ -153,8 +153,8 @@ isort_aspect = _python_tool_aspect( ) mypy_aspect = _python_tool_aspect( - tool = "@swf_bazel_rules_quality//quality/private/python:mypy", - runner = "@swf_bazel_rules_quality//quality/private/python/tools:mypy_runner", + tool = "@swf_bazel_rules_quality//quality/private/python:mypy_entry_point", + runner = "@swf_bazel_rules_quality//quality/private/python/tools:mypy", config = "@swf_bazel_rules_quality//quality:quality_mypy_config", ) diff --git a/quality/private/python/tools/BUILD b/quality/private/python/tools/BUILD index 23e71d4..6a014f6 100644 --- a/quality/private/python/tools/BUILD +++ b/quality/private/python/tools/BUILD @@ -31,9 +31,9 @@ py_binary( ) py_binary( - name = "mypy_runner", - srcs = ["mypy_runner.py"], - data = ["@swf_bazel_rules_quality//quality/private/python:mypy"], + name = "mypy", + srcs = ["mypy.py"], + data = ["@swf_bazel_rules_quality//quality/private/python:mypy_entry_point"], visibility = ["//visibility:public"], deps = ["@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common"], ) diff --git a/quality/private/python/tools/mypy_runner.py b/quality/private/python/tools/mypy.py similarity index 100% rename from quality/private/python/tools/mypy_runner.py rename to quality/private/python/tools/mypy.py From 26612160f1e244b220fa256c6c714abc38735ecf Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Tue, 13 Aug 2024 09:28:44 +0200 Subject: [PATCH 13/74] [python] Add more `mypy` types dependencies To ensure that mypy can type check more code we need to add type libraries. These libraries ususally come from https://github.com/python/typeshed, and most are daily released. This also add those libraries to mypy entry point dependencies. --- quality/private/python/BUILD | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/quality/private/python/BUILD b/quality/private/python/BUILD index ba0beab..58d8ab3 100644 --- a/quality/private/python/BUILD +++ b/quality/private/python/BUILD @@ -38,6 +38,15 @@ py_console_script_binary( deps = [ requirement("types-jsonschema"), requirement("types-PyYAML"), + requirement("types-requests"), + requirement("types-typed-ast"), + requirement("types-six"), + requirement("types-setuptools"), + requirement("types-python-dateutil"), + requirement("types-certifi"), + requirement("types-chardet"), + requirement("types-urllib3"), + requirement("types-html5lib"), ], ) From 13a55673c37b347a8e850af7413243c07900825f Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Wed, 21 Aug 2024 09:58:47 +0200 Subject: [PATCH 14/74] [python] Improve mypy configuration With this we set mypy as no incremental mode to both our repo and our default aspect config. The reason is that while it does speed up check, it also makes mypy bugprone and therefore we are disabling it. For our repo mypy config we are also fixing it to python 3.9. This means that, if we change our python version, mypy will still check against 3.9 style. --- quality/private/python/support/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/quality/private/python/support/pyproject.toml b/quality/private/python/support/pyproject.toml index 90126dc..af42ad8 100644 --- a/quality/private/python/support/pyproject.toml +++ b/quality/private/python/support/pyproject.toml @@ -16,6 +16,7 @@ disable = [ [tool.mypy] disallow_untyped_defs = false explicit_package_bases = true +incremental = false [tool.ruff] # Same as Black. From c2e88953bf72f10dc5846feff8dadc328d7a708c Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Thu, 12 Sep 2024 08:55:19 +0200 Subject: [PATCH 15/74] [python] Make python aspect py38 compatible As we aim to not provide our toolchains anymore, we, unfortunatelly, will need to support python 3.8. This mostly means replacing some built-ins typehints with typing --- .../python/tools/python_tool_common.py | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/quality/private/python/tools/python_tool_common.py b/quality/private/python/tools/python_tool_common.py index 4f5e5d3..415cb39 100644 --- a/quality/private/python/tools/python_tool_common.py +++ b/quality/private/python/tools/python_tool_common.py @@ -57,7 +57,7 @@ class SubprocessInfo: def execute_subprocess( - commands: list[str], + commands: t.List[str], cwd: pathlib.Path = pathlib.Path.cwd(), env: t.Optional[t.Mapping[str, str]] = None, ) -> SubprocessInfo: @@ -89,24 +89,36 @@ def execute_subprocess( ) +def _is_relative_to(path: pathlib.Path, root: pathlib.Path): + """Helper function that mimics what pathlib.Path.is_relative_to does. + + This is needed to ensure support for python 3.8. + """ + try: + path.relative_to(root) + return True + except ValueError: + return False + + @dataclasses.dataclass class AspectArguments: """Class that provides a clean and verified interface between aspect and runner.""" - target_imports: set[pathlib.Path] - target_dependencies: set[pathlib.Path] - target_files: set[pathlib.Path] + target_imports: t.Set[pathlib.Path] + target_dependencies: t.Set[pathlib.Path] + target_files: t.Set[pathlib.Path] tool: pathlib.Path tool_config: pathlib.Path tool_output: pathlib.Path tool_root: str def __post_init__(self): - def resolve_paths(paths: list[str], prepend_path: str = "") -> set[pathlib.Path]: + def resolve_paths(paths: t.List[str], prepend_path: str = "") -> t.Set[pathlib.Path]: resolved_paths = set() for path in paths: try: - if pathlib.Path(path).is_relative_to(self.tool_root): + if _is_relative_to(pathlib.Path(path), pathlib.Path(self.tool_root)): # This is the usual branch for local files or libraries. # The code go through here when path is relative to the sandbox root. resolved_paths.add(pathlib.Path(path).relative_to(self.tool_root).resolve(strict=True)) From 6bd22bfbd1a11044f3603ea64ef62b2a030113ab Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Thu, 12 Sep 2024 09:06:00 +0200 Subject: [PATCH 16/74] [python] Improve how python aspect collects information There was a fundamental flaw in the python aspect design. It was not collecting information from the whole dependency tree. This commit fixes it by splitting the aspect into two aspects. The `python_collect_aspect` parse the whole dependency tree and stores it at the `PythonCollectInfo` provider. The already existing `python_tool_aspect` then inherits `PythonCollectInfo` and use that information to call our tool runners. --- .../private/python/python_collect_aspect.bzl | 46 ++++++++++++++++ quality/private/python/python_helper.bzl | 28 ++++++++++ quality/private/python/python_providers.bzl | 16 ++++++ quality/private/python/python_tool_aspect.bzl | 53 ++++--------------- 4 files changed, 101 insertions(+), 42 deletions(-) create mode 100644 quality/private/python/python_collect_aspect.bzl create mode 100644 quality/private/python/python_helper.bzl create mode 100644 quality/private/python/python_providers.bzl diff --git a/quality/private/python/python_collect_aspect.bzl b/quality/private/python/python_collect_aspect.bzl new file mode 100644 index 0000000..edab4fd --- /dev/null +++ b/quality/private/python/python_collect_aspect.bzl @@ -0,0 +1,46 @@ +"""Aspect that collects python targets information and output it to a provider.""" + +load("@swf_bazel_rules_quality//quality/private/python:python_helper.bzl", "is_valid_label") +load("@swf_bazel_rules_quality//quality/private/python:python_providers.bzl", "PythonCollectInfo") + +def _get_collect_transitive_outputs(ctx): + deps = [] + imports = [] + if hasattr(ctx.rule.attr, "deps"): + for dep in ctx.rule.attr.deps: + if hasattr(dep, "PythonCollectInfo"): + deps.append(dep[PythonCollectInfo].deps) + imports.append(dep[PythonCollectInfo].imports) + return deps, imports + +def _python_collect_aspect_implementation(target, ctx): + transitive_deps, transitive_imports = _get_collect_transitive_outputs(ctx) + + if not PyInfo in target or not is_valid_label(target.label): + return [PythonCollectInfo( + deps = depset([], transitive = transitive_deps), + imports = depset([], transitive = transitive_imports), + )] + + imports = target[PyInfo].imports.to_list() + + deps = ["."] + if ctx.rule.attr.deps: + deps.append("external") + for dep in ctx.rule.attr.deps: + deps.append(dep.label.workspace_root) + + return [PythonCollectInfo( + deps = depset(deps, transitive = transitive_deps), + imports = depset(imports, transitive = transitive_imports), + )] + +def _python_collect_aspect(): + return aspect( + implementation = _python_collect_aspect_implementation, + attr_aspects = ["deps"], + attrs = {}, + provides = [PythonCollectInfo], + ) + +python_collect_aspect = _python_collect_aspect() diff --git a/quality/private/python/python_helper.bzl b/quality/private/python/python_helper.bzl new file mode 100644 index 0000000..b9bb806 --- /dev/null +++ b/quality/private/python/python_helper.bzl @@ -0,0 +1,28 @@ +"""A collection of helper functions for the python aspect.""" + +_excluded_main_label_names = ["rules_python_entry_point_"] + +_excluded_workspaces_roots = ["external/"] + +def is_valid_label(label, excluded_labels = [], excluded_workspaces = []): + """Check if a given label is valid. + + To validate a given label this functions checks its name and workspace. + It already has a pre defined label and workspace exclusion list. It's, + up to the user to provide additional exclusion lists or not. + + Args: + label: label to be checked. + excluded_labels: additional labels to exclude the given label from. + excluded_workspaces: additional workspaces to exclude the given label from. + Returns: + True if the label is valid, and false otherwise. + """ + + for excluded_label in excluded_labels + _excluded_main_label_names: + if excluded_label in label.name: + return False + for excluded_workspace_root in excluded_workspaces + _excluded_workspaces_roots: + if excluded_workspace_root in label.workspace_root: + return False + return True diff --git a/quality/private/python/python_providers.bzl b/quality/private/python/python_providers.bzl new file mode 100644 index 0000000..dd23f3a --- /dev/null +++ b/quality/private/python/python_providers.bzl @@ -0,0 +1,16 @@ +"""This module defines the offered providers of the python aspect.""" + +PythonCollectInfo = provider( + doc = "Collected info about the target.", + fields = { + "deps": ".", + "imports": ".", + }, +) + +PythonToolInfo = provider( + doc = "Configuration structure for the python tool aspect.", + fields = { + "config": "Configuration file for the respective python tool.", + }, +) diff --git a/quality/private/python/python_tool_aspect.bzl b/quality/private/python/python_tool_aspect.bzl index 8d81cbb..dac7779 100644 --- a/quality/private/python/python_tool_aspect.bzl +++ b/quality/private/python/python_tool_aspect.bzl @@ -1,19 +1,8 @@ -"""Aspect that collects python targets dependencies and hands them to a tool runner.""" +"""Aspect that call a tool runner on top of a python target.""" -_excluded_main_label_names = [ - "rules_python_entry_point_", -] - -_excluded_workspaces_roots = [ - "external/", -] - -PythonToolInfo = provider( - doc = "Configuration structure for the python tool aspect.", - fields = { - "config": "Configuration file for the respective python tool tool.", - }, -) +load("@swf_bazel_rules_quality//quality/private/python:python_collect_aspect.bzl", "python_collect_aspect") +load("@swf_bazel_rules_quality//quality/private/python:python_helper.bzl", "is_valid_label") +load("@swf_bazel_rules_quality//quality/private/python:python_providers.bzl", "PythonCollectInfo", "PythonToolInfo") def _python_tool_config_impl(ctx): return [PythonToolInfo(config = ctx.file.config)] @@ -27,37 +16,19 @@ python_tool_config = rule( }, ) -def _is_valid_label(label, excluded_labels, excluded_workspaces): - """Check if a given label is valid. - - To validate a given label this functions checks its name and workspace. - """ - - for excluded_workspace_root in excluded_workspaces: - if excluded_workspace_root in label.workspace_root: - return False - for excluded_label in excluded_labels: - if excluded_label in label.name: - return False - return True - def _python_tool_aspect_implementation(target, ctx): """Python tool aspect implementation.""" output = [] - if not PyInfo in target: + if not PyInfo in target or not is_valid_label(target.label): return [OutputGroupInfo(python_tool_output = depset([]))] - for excluded_path in _excluded_workspaces_roots: - if excluded_path in target.label.workspace_root: - return [OutputGroupInfo(python_tool_output = depset([]))] - config = ctx.attr._config[PythonToolInfo].config sources_to_run = [] for source in ctx.rule.attr.srcs: - if _is_valid_label(source.label, _excluded_main_label_names, _excluded_workspaces_roots) and source.label.name.endswith(".py"): + if is_valid_label(source.label) and source.label.name.endswith(".py"): if source.label.package: sources_to_run.append(source.label.package + "/" + source.label.name) else: @@ -67,19 +38,15 @@ def _python_tool_aspect_implementation(target, ctx): output_file = ctx.actions.declare_file(ctx.executable._runner.basename + "_output_" + target.label.name) output.append(output_file) - imports = target[PyInfo].imports.to_list() - - dependencies = ["."] - for dep in ctx.rule.attr.deps: - if _is_valid_label(dep.label, [], []): - dependencies.append(dep.label.workspace_root) + deps = getattr(target[PythonCollectInfo], "deps") + imports = getattr(target[PythonCollectInfo], "imports") args = ctx.actions.args() args.use_param_file("@%s", use_always = True) args.set_param_file_format("multiline") args.add_all("--target-imports", imports, format_each = "%s") - args.add_all("--target-dependencies", dependencies, format_each = "%s") + args.add_all("--target-dependencies", deps, format_each = "%s") args.add_all("--target-files", sources_to_run, format_each = "%s") args.add("--tool", ctx.executable._tool.path, format = "%s") args.add("--tool-config", config.path, format = "%s") @@ -132,6 +99,8 @@ def _python_tool_aspect(tool, runner, config): ), }, required_providers = [PyInfo], + required_aspect_providers = [PythonCollectInfo], + requires = [python_collect_aspect], ) pylint_aspect = _python_tool_aspect( From cbff0af659b416a2092a22ca8c44d6b5222cb5ec Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Thu, 12 Sep 2024 17:11:48 +0200 Subject: [PATCH 17/74] [python] Change python modules names that were shadowing libs Naming files with the same name as libraries is a bad practice and can lead some tools to undefined behaviour. --- quality/private/python/tools/BUILD | 18 ++++++++++++------ .../python/tools/{black.py => black_runner.py} | 0 .../python/tools/{isort.py => isort_runner.py} | 0 .../python/tools/{mypy.py => mypy_runner.py} | 0 .../tools/{pylint.py => pylint_runner.py} | 0 .../{ruff_check.py => ruff_check_runner.py} | 0 .../{ruff_format.py => ruff_format_runner.py} | 0 7 files changed, 12 insertions(+), 6 deletions(-) rename quality/private/python/tools/{black.py => black_runner.py} (100%) rename quality/private/python/tools/{isort.py => isort_runner.py} (100%) rename quality/private/python/tools/{mypy.py => mypy_runner.py} (100%) rename quality/private/python/tools/{pylint.py => pylint_runner.py} (100%) rename quality/private/python/tools/{ruff_check.py => ruff_check_runner.py} (100%) rename quality/private/python/tools/{ruff_format.py => ruff_format_runner.py} (100%) diff --git a/quality/private/python/tools/BUILD b/quality/private/python/tools/BUILD index 6a014f6..e93ef3e 100644 --- a/quality/private/python/tools/BUILD +++ b/quality/private/python/tools/BUILD @@ -8,39 +8,44 @@ py_library( py_binary( name = "pylint", - srcs = ["pylint.py"], + srcs = ["pylint_runner.py"], data = ["@swf_bazel_rules_quality//quality/private/python:pylint_entry_point"], + main = "pylint_runner.py", visibility = ["//visibility:public"], deps = ["@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common"], ) py_binary( name = "black", - srcs = ["black.py"], + srcs = ["black_runner.py"], data = ["@swf_bazel_rules_quality//quality/private/python:black_entry_point"], + main = "black_runner.py", visibility = ["//visibility:public"], deps = ["@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common"], ) py_binary( name = "isort", - srcs = ["isort.py"], + srcs = ["isort_runner.py"], data = ["@swf_bazel_rules_quality//quality/private/python:isort_entry_point"], + main = "isort_runner.py", visibility = ["//visibility:public"], deps = ["@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common"], ) py_binary( name = "mypy", - srcs = ["mypy.py"], + srcs = ["mypy_runner.py"], data = ["@swf_bazel_rules_quality//quality/private/python:mypy_entry_point"], + main = "mypy_runner.py", visibility = ["//visibility:public"], deps = ["@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common"], ) py_binary( name = "ruff_check", - srcs = ["ruff_check.py"], + srcs = ["ruff_check_runner.py"], + main = "ruff_check_runner.py", visibility = ["//visibility:public"], deps = [ "@swf_bazel_rules_quality//quality/private/python:ruff_entry_point", @@ -50,7 +55,8 @@ py_binary( py_binary( name = "ruff_format", - srcs = ["ruff_format.py"], + srcs = ["ruff_format_runner.py"], + main = "ruff_format_runner.py", visibility = ["//visibility:public"], deps = [ "@swf_bazel_rules_quality//quality/private/python:ruff_entry_point", diff --git a/quality/private/python/tools/black.py b/quality/private/python/tools/black_runner.py similarity index 100% rename from quality/private/python/tools/black.py rename to quality/private/python/tools/black_runner.py diff --git a/quality/private/python/tools/isort.py b/quality/private/python/tools/isort_runner.py similarity index 100% rename from quality/private/python/tools/isort.py rename to quality/private/python/tools/isort_runner.py diff --git a/quality/private/python/tools/mypy.py b/quality/private/python/tools/mypy_runner.py similarity index 100% rename from quality/private/python/tools/mypy.py rename to quality/private/python/tools/mypy_runner.py diff --git a/quality/private/python/tools/pylint.py b/quality/private/python/tools/pylint_runner.py similarity index 100% rename from quality/private/python/tools/pylint.py rename to quality/private/python/tools/pylint_runner.py diff --git a/quality/private/python/tools/ruff_check.py b/quality/private/python/tools/ruff_check_runner.py similarity index 100% rename from quality/private/python/tools/ruff_check.py rename to quality/private/python/tools/ruff_check_runner.py diff --git a/quality/private/python/tools/ruff_format.py b/quality/private/python/tools/ruff_format_runner.py similarity index 100% rename from quality/private/python/tools/ruff_format.py rename to quality/private/python/tools/ruff_format_runner.py From 055192be9caf2807192012ea34925a5a9eee5b79 Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Wed, 7 Aug 2024 15:10:06 +0200 Subject: [PATCH 18/74] [python] Add common classes that will standardize every tool output Ideally we want two outputs, a json using quality-tools Fidings format and also a text output so we can output findings to the terminal. Diff outputs are additional. This commit add a Findings definition that follows what quality-tools findings-converter expects. Also, a Findings and a FindingsJSONEncoder helps us to interface a list of Finding with string and json methods. Also, updates `black_runner`, to output their results using the updated Finding string. --- quality/private/python/python_tool_aspect.bzl | 2 +- quality/private/python/tools/black_runner.py | 32 ++++++--- quality/private/python/tools/isort_runner.py | 2 +- quality/private/python/tools/mypy_runner.py | 2 +- .../python/tools/python_tool_common.py | 66 ++++++++++++++++++- .../private/python/tools/ruff_check_runner.py | 2 +- .../python/tools/ruff_format_runner.py | 2 +- 7 files changed, 90 insertions(+), 18 deletions(-) diff --git a/quality/private/python/python_tool_aspect.bzl b/quality/private/python/python_tool_aspect.bzl index dac7779..20ef9c7 100644 --- a/quality/private/python/python_tool_aspect.bzl +++ b/quality/private/python/python_tool_aspect.bzl @@ -35,7 +35,7 @@ def _python_tool_aspect_implementation(target, ctx): sources_to_run.append(source.label.name) if sources_to_run: - output_file = ctx.actions.declare_file(ctx.executable._runner.basename + "_output_" + target.label.name) + output_file = ctx.actions.declare_file(ctx.executable._runner.basename + "_output_" + target.label.name + ".txt") output.append(output_file) deps = getattr(target[PythonCollectInfo], "deps") diff --git a/quality/private/python/tools/black_runner.py b/quality/private/python/tools/black_runner.py index d43eb42..ce90015 100644 --- a/quality/private/python/tools/black_runner.py +++ b/quality/private/python/tools/black_runner.py @@ -1,10 +1,11 @@ """A runner that interfaces python tool aspect and runs black on a list of files.""" import logging +import pathlib from quality.private.python.tools import python_tool_common -BLACK_DEFAULT_ERROR_MSG = "file would be reformatted" +WOULD_REFORMAT_MSG = "would reformat" def check_with_black(aspect_arguments: python_tool_common.AspectArguments) -> None: @@ -14,9 +15,11 @@ def check_with_black(aspect_arguments: python_tool_common.AspectArguments) -> No The arguments received from the python_tool_aspect and already processed by python_tool_common module. :raises LinterFindingAsError: - If black finds a file to be formatted. + If black finds at least one file to be formatted. """ + findings = python_tool_common.Findings() + black_output = python_tool_common.execute_subprocess( [ f"{aspect_arguments.tool}", @@ -27,14 +30,23 @@ def check_with_black(aspect_arguments: python_tool_common.AspectArguments) -> No ], ) - aspect_arguments.tool_output.write_text(black_output.stdout) - logging.info("Created black output at: %s", aspect_arguments.tool_output) - - if BLACK_DEFAULT_ERROR_MSG in black_output.stderr: - raise python_tool_common.LinterFindingAsError( - path=aspect_arguments.tool_output, - tool=aspect_arguments.tool.name, - ) + for line in black_output.stderr.splitlines(): + if WOULD_REFORMAT_MSG in line: + file = line.lstrip(WOULD_REFORMAT_MSG) + findings += [ + python_tool_common.Finding( + path=pathlib.Path(file), + message="Should be reformatted.", + severity=python_tool_common.Severity.WARN, + tool="black", + rule_id="formatting", + ) + ] + + aspect_arguments.tool_output.write_text(str(findings)) + if findings: + logging.info("Created black output at: %s", aspect_arguments.tool_output) + raise python_tool_common.LinterFindingAsError(findings=findings) def main(): diff --git a/quality/private/python/tools/isort_runner.py b/quality/private/python/tools/isort_runner.py index 8a3d0be..e6ab426 100644 --- a/quality/private/python/tools/isort_runner.py +++ b/quality/private/python/tools/isort_runner.py @@ -45,7 +45,7 @@ def check_with_isort(aspect_arguments: python_tool_common.AspectArguments) -> No logging.info("Created isort output at: %s", aspect_arguments.tool_output) if isort_output.return_code: - raise python_tool_common.LinterFindingAsError( + raise python_tool_common.DeprecatedLinterFindingAsError( path=aspect_arguments.tool_output, tool=aspect_arguments.tool.name, ) diff --git a/quality/private/python/tools/mypy_runner.py b/quality/private/python/tools/mypy_runner.py index b8ab9d9..47fd1ab 100644 --- a/quality/private/python/tools/mypy_runner.py +++ b/quality/private/python/tools/mypy_runner.py @@ -47,7 +47,7 @@ def check_with_mypy(aspect_arguments: python_tool_common.AspectArguments) -> Non logging.info("Created mypy output at: %s", aspect_arguments.tool_output) if mypy_output.return_code: - raise python_tool_common.LinterFindingAsError( + raise python_tool_common.DeprecatedLinterFindingAsError( path=aspect_arguments.tool_output, tool=aspect_arguments.tool.name, ) diff --git a/quality/private/python/tools/python_tool_common.py b/quality/private/python/tools/python_tool_common.py index 415cb39..ed269ec 100644 --- a/quality/private/python/tools/python_tool_common.py +++ b/quality/private/python/tools/python_tool_common.py @@ -2,13 +2,65 @@ import argparse import dataclasses +import enum import itertools +import json import os import pathlib import subprocess import typing as t +@enum.unique +class Severity(str, enum.Enum): + """Enum for severity types.""" + + WARN = "WARN" + ERROR = "ERROR" + INFO = "INFO" + + +class FindingsJSONEncoder(json.JSONEncoder): + """Encodes dataclass objects using asdict and other objects as strings.""" + + def default(self, o): + """Overrides default encoding.""" + if dataclasses.is_dataclass(o): + return dataclasses.asdict(o) + return str(o) + + +@dataclasses.dataclass +class Finding: + """Defines a finding.""" + + path: pathlib.Path + message: str + severity: Severity + tool: str + rule_id: str + line: t.Optional[int] = None + column: t.Optional[int] = None + + def __str__(self): + output = f"{self.path}" + output += f":{self.line}" if self.line else "" + output += f":{self.column}" if self.line and self.column else "" + output += f": {self.message} [{self.tool}:{self.rule_id}]" + return output + + +class Findings(t.List[Finding]): + """Defines a list of findings.""" + + def to_json_file(self, file: pathlib.Path) -> None: + """Dumps a list of findings to a JSON file.""" + file.write_text(json.dumps(self, cls=FindingsJSONEncoder, indent=2), encoding="utf-8") + + def __str__(self) -> str: + return "\n".join([str(finding) for finding in self]) + + class PythonPathNotFoundError(Exception): """Raised when it is not possible to find a target path.""" @@ -18,7 +70,7 @@ def __init__(self, path: str, tool: str): super().__init__(f'The path "{self.path}" was not found. Therefore {self.tool} cannot properly run.') -class LinterFindingAsError(Exception): +class DeprecatedLinterFindingAsError(Exception): """Raised when a linter finds a finding treats it as an error.""" def __init__(self, path: t.Union[str, pathlib.Path], tool: str): @@ -27,12 +79,20 @@ def __init__(self, path: t.Union[str, pathlib.Path], tool: str): super().__init__(f'At least one {self.tool} finding was treated as error. See its output at "{self.path}"') +class LinterFindingAsError(SystemExit): + """Raised when a linter finds a finding treats it as an error.""" + + def __init__(self, findings: Findings): + self.findings = findings + super().__init__(f"\nThe following findings were found:\n{self.findings}\n") + + class LinterSubprocessError(Exception): """Raised when a linter subprocess fails.""" def __init__( self, - commands: str, + commands: t.List[str], return_code: t.Union[str, int], stdout: str, stderr: str, @@ -76,7 +136,7 @@ def execute_subprocess( ) except subprocess.CalledProcessError as exception: raise LinterSubprocessError( - commands=str(commands), + commands=commands, return_code=exception.returncode, stdout=exception.stdout, stderr=exception.stderr, diff --git a/quality/private/python/tools/ruff_check_runner.py b/quality/private/python/tools/ruff_check_runner.py index ada012d..731b96d 100644 --- a/quality/private/python/tools/ruff_check_runner.py +++ b/quality/private/python/tools/ruff_check_runner.py @@ -42,7 +42,7 @@ def check_with_ruff(aspect_arguments: python_tool_common.AspectArguments) -> Non logging.info("Created ruff output at: %s", aspect_arguments.tool_output) if ruff_output.return_code: - raise python_tool_common.LinterFindingAsError( + raise python_tool_common.DeprecatedLinterFindingAsError( path=aspect_arguments.tool_output, tool=aspect_arguments.tool.name, ) diff --git a/quality/private/python/tools/ruff_format_runner.py b/quality/private/python/tools/ruff_format_runner.py index 0b75e66..a86cedf 100644 --- a/quality/private/python/tools/ruff_format_runner.py +++ b/quality/private/python/tools/ruff_format_runner.py @@ -41,7 +41,7 @@ def format_with_ruff(aspect_arguments: python_tool_common.AspectArguments) -> No logging.info("Created ruff output at: %s", aspect_arguments.tool_output) if ruff_output.return_code: - raise python_tool_common.LinterFindingAsError( + raise python_tool_common.DeprecatedLinterFindingAsError( path=aspect_arguments.tool_output, tool=aspect_arguments.tool.name, ) From d8ee0414866c98b73ecf97c550383bfe604f8a16 Mon Sep 17 00:00:00 2001 From: Vitor Sorpile Geraldo Date: Mon, 25 Nov 2024 21:08:26 +0100 Subject: [PATCH 19/74] [python] Standardize isort output This commit standardizes isort_runner output, which is part of a bigger effort to standardize every python tool output. --- quality/private/python/tools/isort_runner.py | 27 ++++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/quality/private/python/tools/isort_runner.py b/quality/private/python/tools/isort_runner.py index e6ab426..4938dda 100644 --- a/quality/private/python/tools/isort_runner.py +++ b/quality/private/python/tools/isort_runner.py @@ -6,6 +6,7 @@ from quality.private.python.tools import python_tool_common ISORT_BAD_CHECK_ERROR_CODE = 1 +ISORT_ERROR_MSG = "ERROR: " def check_with_isort(aspect_arguments: python_tool_common.AspectArguments) -> None: @@ -17,6 +18,7 @@ def check_with_isort(aspect_arguments: python_tool_common.AspectArguments) -> No :raises LinterFindingAsError: If isort finds a file to be formatted. """ + findings = python_tool_common.Findings() try: isort_output = python_tool_common.execute_subprocess( @@ -34,22 +36,31 @@ def check_with_isort(aspect_arguments: python_tool_common.AspectArguments) -> No ) except python_tool_common.LinterSubprocessError as exception: if exception.return_code != ISORT_BAD_CHECK_ERROR_CODE: - raise + raise exception + isort_output = python_tool_common.SubprocessInfo( exception.stdout, exception.stderr, exception.return_code, ) - aspect_arguments.tool_output.write_text(isort_output.stdout) - logging.info("Created isort output at: %s", aspect_arguments.tool_output) - - if isort_output.return_code: - raise python_tool_common.DeprecatedLinterFindingAsError( - path=aspect_arguments.tool_output, - tool=aspect_arguments.tool.name, + for line in isort_output.stderr.splitlines(): + file = line.lstrip(ISORT_ERROR_MSG).split(" ")[0] + findings.append( + python_tool_common.Finding( + path=pathlib.Path(file), + message="Imports are incorrectly sorted and/or formatted.", + severity=python_tool_common.Severity.WARN, + tool="isort", + rule_id="imports_formatting", + ) ) + aspect_arguments.tool_output.write_text(str(findings)) + if findings: + logging.info("Created isort output at: %s", aspect_arguments.tool_output) + raise python_tool_common.LinterFindingAsError(findings=findings) + def main(): """Interfaces python tool aspect and use isort to check a given set of files.""" From 14f1c1bf6bd95260709575dd4d9fa36022c30c23 Mon Sep 17 00:00:00 2001 From: Vitor Sorpile Geraldo Date: Tue, 26 Nov 2024 22:33:22 +0100 Subject: [PATCH 20/74] [python] Add tests for isort runner --- quality/private/python/tools/BUILD | 1 + quality/private/python/tools/isort_runner.py | 2 +- quality/private/python/tools/test/BUILD | 9 ++ .../python/tools/test/test_isort_runner.py | 124 ++++++++++++++++++ 4 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 quality/private/python/tools/test/BUILD create mode 100644 quality/private/python/tools/test/test_isort_runner.py diff --git a/quality/private/python/tools/BUILD b/quality/private/python/tools/BUILD index e93ef3e..4270748 100644 --- a/quality/private/python/tools/BUILD +++ b/quality/private/python/tools/BUILD @@ -4,6 +4,7 @@ load("@swf_bazel_rules_quality_rules_python//python:defs.bzl", "py_library") py_library( name = "python_tool_common", srcs = ["python_tool_common.py"], + visibility = ["//quality/private/python/tools:__subpackages__"], ) py_binary( diff --git a/quality/private/python/tools/isort_runner.py b/quality/private/python/tools/isort_runner.py index 4938dda..cfa582d 100644 --- a/quality/private/python/tools/isort_runner.py +++ b/quality/private/python/tools/isort_runner.py @@ -72,5 +72,5 @@ def main(): check_with_isort(aspect_arguments=args) -if __name__ == "__main__": +if __name__ == "__main__": # pragma: no cover main() diff --git a/quality/private/python/tools/test/BUILD b/quality/private/python/tools/test/BUILD new file mode 100644 index 0000000..60665e0 --- /dev/null +++ b/quality/private/python/tools/test/BUILD @@ -0,0 +1,9 @@ +load("@swf_bazel_rules_quality_python//:defs.bzl", "py_test") + +py_test( + name = "test_isort_runner", + srcs = ["test_isort_runner.py"], + deps = [ + "//quality/private/python/tools:isort", + ], +) diff --git a/quality/private/python/tools/test/test_isort_runner.py b/quality/private/python/tools/test/test_isort_runner.py new file mode 100644 index 0000000..2331623 --- /dev/null +++ b/quality/private/python/tools/test/test_isort_runner.py @@ -0,0 +1,124 @@ +"""Tests for the isort runner.""" + +import pathlib +import sys +import tempfile +import unittest +from unittest.mock import patch + +from quality.private.python.tools import isort_runner, python_tool_common + + +class TestIsortRunner(unittest.TestCase): + """Test class for isort runner.""" + + def setUp(self) -> None: + _, self.tmp_file_path = tempfile.mkstemp() + self.aspect_args = python_tool_common.AspectArguments( + target_imports=set(), + target_dependencies=set(), + tool=pathlib.Path(""), + tool_config=pathlib.Path(""), + tool_root="", + target_files=set(), + tool_output=pathlib.Path(self.tmp_file_path), + ) + + def tearDown(self) -> None: + pathlib.Path(self.tmp_file_path).unlink() + + def test_isort_output_with_no_issues(self) -> None: + """Tests check_with_isort function with the results of a file with no issues.""" + with patch( + "quality.private.python.tools.python_tool_common.execute_subprocess", + side_effect=[ + python_tool_common.SubprocessInfo( + stdout="", + stderr="", + return_code=0, + ) + ], + ): + isort_runner.check_with_isort(self.aspect_args) + self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) + + def test_isort_output_with_issues(self) -> None: + """Tests check_with_isort function with the results of a file with issues.""" + expected_error_message = "file.py: Imports are incorrectly sorted and/or formatted. [isort:imports_formatting]" + expected_output_log_message = f"INFO:root:Created isort output at: {self.tmp_file_path}" + + with patch( + "quality.private.python.tools.python_tool_common.execute_subprocess", + side_effect=[ + python_tool_common.LinterSubprocessError( + commands=[], + stdout="", + stderr="ERROR: file.py Imports are incorrectly sorted and/or formatted.\n", + return_code=1, + ) + ], + ): + with self.assertRaises(python_tool_common.LinterFindingAsError) as exception: + with self.assertLogs() as logs: + isort_runner.check_with_isort(self.aspect_args) + + findings_as_error_message = str(exception.exception.findings) + output_file_content = self.aspect_args.tool_output.read_text(encoding="utf-8") + output_log_message = logs.output[0] + + self.assertEqual(expected_error_message, findings_as_error_message) + self.assertEqual(expected_error_message, output_file_content) + self.assertEqual(expected_output_log_message, output_log_message) + + def test_check_with_isort_unexpected_exception(self) -> None: + """Test check_with_isort function when an unexpected exception occurs.""" + with patch( + "quality.private.python.tools.python_tool_common.execute_subprocess", + side_effect=[ + python_tool_common.LinterSubprocessError( + commands=[], + stdout="", + stderr="ERROR: unexpected error\n", + return_code=2, + ) + ], + ): + with self.assertRaises(python_tool_common.LinterSubprocessError) as exception: + with self.assertLogs() as logs: + isort_runner.check_with_isort(self.aspect_args) + + self.assertEqual(exception.exception.return_code, 2) + self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) + self.assertFalse(logs.output) + + def test_call_main(self) -> None: + """Tests calling main.""" + mocked_args = [ + "--tool", + " ", + "--tool-config", + " ", + "--tool-output", + self.tmp_file_path, + "--tool-root", + " ", + ] + with ( + patch.object(sys, "argv", ["isort"] + mocked_args), + patch( + "quality.private.python.tools.python_tool_common.execute_subprocess", + side_effect=[ + python_tool_common.SubprocessInfo( + stdout="", + stderr="", + return_code=0, + ) + ], + ), + ): + isort_runner.main() + self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) + + +if __name__ == "__main__": # pragma: no cover + unittest.main() From 824d56ac907972252f4787c44c1527be00d20bcc Mon Sep 17 00:00:00 2001 From: "Yuri.Barboza" Date: Thu, 28 Nov 2024 19:21:03 +0100 Subject: [PATCH 21/74] [python] Create test for python black runner. This commit will add test coverage to the current black runner. --- quality/private/python/tools/black_runner.py | 6 +- quality/private/python/tools/test/BUILD | 6 + .../python/tools/test/test_black_runner.py | 132 ++++++++++++++++++ 3 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 quality/private/python/tools/test/test_black_runner.py diff --git a/quality/private/python/tools/black_runner.py b/quality/private/python/tools/black_runner.py index ce90015..2c9ae61 100644 --- a/quality/private/python/tools/black_runner.py +++ b/quality/private/python/tools/black_runner.py @@ -31,8 +31,8 @@ def check_with_black(aspect_arguments: python_tool_common.AspectArguments) -> No ) for line in black_output.stderr.splitlines(): - if WOULD_REFORMAT_MSG in line: - file = line.lstrip(WOULD_REFORMAT_MSG) + if line.startswith(WOULD_REFORMAT_MSG): + file = line.removeprefix(WOULD_REFORMAT_MSG) findings += [ python_tool_common.Finding( path=pathlib.Path(file), @@ -59,5 +59,5 @@ def main(): check_with_black(aspect_arguments=args) -if __name__ == "__main__": +if __name__ == "__main__": # pragma: no cover main() diff --git a/quality/private/python/tools/test/BUILD b/quality/private/python/tools/test/BUILD index 60665e0..7b0bf35 100644 --- a/quality/private/python/tools/test/BUILD +++ b/quality/private/python/tools/test/BUILD @@ -7,3 +7,9 @@ py_test( "//quality/private/python/tools:isort", ], ) + +py_test( + name = "test_black_runner", + srcs = ["test_black_runner.py"], + deps = ["//quality/private/python/tools:black"], +) diff --git a/quality/private/python/tools/test/test_black_runner.py b/quality/private/python/tools/test/test_black_runner.py new file mode 100644 index 0000000..7ed2e48 --- /dev/null +++ b/quality/private/python/tools/test/test_black_runner.py @@ -0,0 +1,132 @@ +"""Tests for the black runner.""" + +import pathlib +import sys +import tempfile +import unittest +from unittest.mock import patch + +from quality.private.python.tools import black_runner, python_tool_common + + +class TestBlackRunner(unittest.TestCase): + """Test class for black runner.""" + + def setUp(self) -> None: + _, self.tmp_file_path = tempfile.mkstemp() + self.aspect_args = python_tool_common.AspectArguments( + target_imports=set(), + target_dependencies=set(), + tool=pathlib.Path(""), + tool_config=pathlib.Path(""), + tool_root="", + target_files=set(), + tool_output=pathlib.Path(self.tmp_file_path), + ) + + def tearDown(self) -> None: + pathlib.Path(self.tmp_file_path).unlink() + + @patch( + "quality.private.python.tools.python_tool_common.execute_subprocess", + side_effect=[ + python_tool_common.SubprocessInfo( + stdout="", + stderr="", + return_code=0, + ) + ], + ) + def test_black_output_with_no_issues(self, _) -> None: + """Tests check_with_black function with the results of a file with no issues.""" + + black_runner.check_with_black(self.aspect_args) + self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) + + @patch( + "quality.private.python.tools.python_tool_common.execute_subprocess", + side_effect=[ + python_tool_common.SubprocessInfo( + stdout="", + stderr="--- file.py\nwould reformat file.py", + return_code=0, + ) + ], + ) + def test_black_output_with_issues(self, _) -> None: + """Tests check_with_black function with results of files with issues.""" + expected_error_message = ( + "\nThe following findings were found:\n file.py: Should be reformatted. [black:formatting]\n" + ) + exepected_output_message = " file.py: Should be reformatted. [black:formatting]" + expected_output_log_message = f"INFO:root:Created black output at: {self.tmp_file_path}" + findings = python_tool_common.Findings() + findings += [ + python_tool_common.Finding( + path=pathlib.Path("file.py"), + message="Should be reformatted.", + severity=python_tool_common.Severity.WARN, + tool="black", + rule_id="formatting", + ) + ] + with self.assertRaises(python_tool_common.LinterFindingAsError) as exception: + with self.assertLogs() as logs: + black_runner.check_with_black(self.aspect_args) + + output_log_message = logs.output[0] + output_file_content = self.aspect_args.tool_output.read_text(encoding="utf-8") + + self.assertEqual(expected_error_message, str(exception.exception)) + self.assertEqual(exepected_output_message, output_file_content) + self.assertEqual(expected_output_log_message, output_log_message) + + @patch( + "quality.private.python.tools.python_tool_common.execute_subprocess", + side_effect=[ + python_tool_common.LinterSubprocessError( + commands=[], + stdout="", + stderr="1 file would be reformatted.", + return_code=1, + ) + ], + ) + def test_check_with_black_unexpected_exception(self, _) -> None: + """Test check_with_black function when an unexpected exception occurs.""" + with self.assertRaises(python_tool_common.LinterSubprocessError) as exception: + with self.assertLogs() as logs: + black_runner.check_with_black(self.aspect_args) + self.assertEqual(exception.exception.return_code, 1) + self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) + self.assertFalse(logs.output) + + @patch( + "quality.private.python.tools.python_tool_common.execute_subprocess", + side_effect=[ + python_tool_common.SubprocessInfo( + stdout="", + stderr="", + return_code=0, + ) + ], + ) + def test_call_main(self, _) -> None: + """Tests calling main.""" + mocked_args = [ + "--tool", + " ", + "--tool-config", + " ", + "--tool-output", + self.tmp_file_path, + "--tool-root", + " ", + ] + with patch.object(sys, "argv", ["black"] + mocked_args): + black_runner.main() + self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) + + +if __name__ == "__main__": # pragma: no cover + unittest.main() From f73906e1989ece35d2429e7105f1fb83bdcda7b6 Mon Sep 17 00:00:00 2001 From: "Yuri.Barboza" Date: Tue, 3 Dec 2024 13:20:35 +0100 Subject: [PATCH 22/74] [python] Standarlize mypy output This commit standardizes mypy_runner output, which is part of a bigger effort to standardize every python tool output. --- quality/private/python/tools/mypy_runner.py | 36 ++++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/quality/private/python/tools/mypy_runner.py b/quality/private/python/tools/mypy_runner.py index 47fd1ab..1f82dff 100644 --- a/quality/private/python/tools/mypy_runner.py +++ b/quality/private/python/tools/mypy_runner.py @@ -2,6 +2,7 @@ import logging import os +import pathlib from quality.private.python.tools import python_tool_common @@ -19,6 +20,7 @@ def check_with_mypy(aspect_arguments: python_tool_common.AspectArguments) -> Non """ pylint_env = os.environ + findings = python_tool_common.Findings() for target_import in aspect_arguments.target_imports | aspect_arguments.target_dependencies: if "PYTHONPATH" not in pylint_env: pylint_env["PYTHONPATH"] = str(target_import) @@ -31,27 +33,45 @@ def check_with_mypy(aspect_arguments: python_tool_common.AspectArguments) -> Non f"{aspect_arguments.tool}", "--config-file", f"{aspect_arguments.tool_config}", + "--show-column-numbers", *map(str, aspect_arguments.target_files), ], ) except python_tool_common.LinterSubprocessError as exception: if exception.return_code != MYPY_BAD_CHECK_ERROR_CODE: - raise + raise exception + mypy_output = python_tool_common.SubprocessInfo( exception.stdout, exception.stderr, exception.return_code, ) - aspect_arguments.tool_output.write_text(mypy_output.stdout) - logging.info("Created mypy output at: %s", aspect_arguments.tool_output) - - if mypy_output.return_code: - raise python_tool_common.DeprecatedLinterFindingAsError( - path=aspect_arguments.tool_output, - tool=aspect_arguments.tool.name, + # The last line of mypy's stdout does not contain any finding. Instead, it + # contains information about how many files are ok and how many are have issues. + issues = mypy_output.stdout.splitlines()[:-1] + + for issue in issues: + path, line, column, _, message_and_rule = issue.split(":", 4) + message, rule_id = message_and_rule.rsplit(" ", 1) + findings.append( + python_tool_common.Finding( + path=pathlib.Path(path), + message=message.strip(), + severity=python_tool_common.Severity.WARN, + tool="mypy", + rule_id=rule_id.strip("[]"), + line=int(line), + column=int(column), + ) ) + aspect_arguments.tool_output.write_text(str(findings)) + + if findings: + logging.info("Created mypy output at: %s", aspect_arguments.tool_output) + raise python_tool_common.LinterFindingAsError(findings=findings) + def main(): """Interfaces python tool aspect and use mypy to check a given set of files.""" From f46227dd164a169709941cee76e7ddea5eaad821 Mon Sep 17 00:00:00 2001 From: "Yuri.Barboza" Date: Mon, 2 Dec 2024 19:04:50 +0100 Subject: [PATCH 23/74] [python] Standarlize ruff output This commit standardizes ruff_check_runner output, and ruff_format_runner output which are part of a bigger effort to standardize every python tool output. --- .../private/python/tools/ruff_check_runner.py | 47 ++++++++++++------- .../python/tools/ruff_format_runner.py | 37 +++++++++++---- 2 files changed, 58 insertions(+), 26 deletions(-) diff --git a/quality/private/python/tools/ruff_check_runner.py b/quality/private/python/tools/ruff_check_runner.py index 731b96d..2364d55 100644 --- a/quality/private/python/tools/ruff_check_runner.py +++ b/quality/private/python/tools/ruff_check_runner.py @@ -1,5 +1,6 @@ """A runner that interfaces python tool aspect and runs ruff on a list of files.""" +import json import logging from quality.private.python.tools import python_tool_common @@ -15,37 +16,49 @@ def check_with_ruff(aspect_arguments: python_tool_common.AspectArguments) -> Non python_tool_common module. :raises LinterFindingAsError: If ruff finds a file to be formatted. + :exit codes: + 0 if no violations were found, or if all present violations were fixed + automatically. + 1 if violations were found. + 2 if Ruff terminates abnormally due to invalid configuration, invalid CLI options, + or an internal error. """ + findings = python_tool_common.Findings() try: - ruff_output = python_tool_common.execute_subprocess( + python_tool_common.execute_subprocess( [ f"{aspect_arguments.tool}", "check", "--config", f"{aspect_arguments.tool_config}", "--unsafe-fixes", - "--diff", + "--output-format", + "json", *map(str, aspect_arguments.target_files), ], ) except python_tool_common.LinterSubprocessError as exception: if exception.return_code != RUFF_BAD_CHECK_ERROR_CODE: - raise - ruff_output = python_tool_common.SubprocessInfo( - exception.stdout, - exception.stderr, - exception.return_code, - ) - - aspect_arguments.tool_output.write_text(ruff_output.stdout) - logging.info("Created ruff output at: %s", aspect_arguments.tool_output) - - if ruff_output.return_code: - raise python_tool_common.DeprecatedLinterFindingAsError( - path=aspect_arguments.tool_output, - tool=aspect_arguments.tool.name, - ) + raise exception + + for finding in json.loads(exception.stdout): + findings.append( + python_tool_common.Finding( + path=finding["filename"], + message=finding["message"], + severity=python_tool_common.Severity.WARN, + tool="ruff_check", + rule_id=finding["code"], + line=finding["location"]["row"], + column=finding["location"]["column"], + ) + ) + + aspect_arguments.tool_output.write_text(str(findings)) + if findings: + logging.info("Created ruff check output at: %s", aspect_arguments.tool_output) + raise python_tool_common.LinterFindingAsError(findings=findings) def main(): diff --git a/quality/private/python/tools/ruff_format_runner.py b/quality/private/python/tools/ruff_format_runner.py index a86cedf..ef45bba 100644 --- a/quality/private/python/tools/ruff_format_runner.py +++ b/quality/private/python/tools/ruff_format_runner.py @@ -1,6 +1,7 @@ """A runner that interfaces python tool aspect and runs ruff on a list of files.""" import logging +import pathlib from quality.private.python.tools import python_tool_common @@ -15,7 +16,15 @@ def format_with_ruff(aspect_arguments: python_tool_common.AspectArguments) -> No python_tool_common module. :raises LinterFindingAsError: If ruff finds a file to be formatted. + :exit codes: + 0 if Ruff terminates successfully, and no files would be formatted if --check + were not specified. + 1 if Ruff terminates successfully, and one or more files would be formatted if + --check were not specified. + 2 if Ruff terminates abnormally due to invalid configuration, invalid CLI options, + or an internal error. """ + findings = python_tool_common.Findings() try: ruff_output = python_tool_common.execute_subprocess( @@ -30,21 +39,31 @@ def format_with_ruff(aspect_arguments: python_tool_common.AspectArguments) -> No ) except python_tool_common.LinterSubprocessError as exception: if exception.return_code != RUFF_BAD_CHECK_ERROR_CODE: - raise + raise exception + ruff_output = python_tool_common.SubprocessInfo( exception.stdout, exception.stderr, exception.return_code, ) - aspect_arguments.tool_output.write_text(ruff_output.stdout) - logging.info("Created ruff output at: %s", aspect_arguments.tool_output) - - if ruff_output.return_code: - raise python_tool_common.DeprecatedLinterFindingAsError( - path=aspect_arguments.tool_output, - tool=aspect_arguments.tool.name, - ) + files = {file.lstrip("-").strip() for file in ruff_output.stdout.splitlines() if file.startswith("---")} + + for file in files: + findings.append( + python_tool_common.Finding( + path=pathlib.Path(file), + message="Should be reformatted.", + severity=python_tool_common.Severity.WARN, + tool="ruff_format", + rule_id="formatting", + ) + ) + + aspect_arguments.tool_output.write_text(str(findings)) + if findings: + logging.info("Created ruff format output at: %s", aspect_arguments.tool_output) + raise python_tool_common.LinterFindingAsError(findings=findings) def main(): From 2d823a5753185e6c6eb228de3cb64f57464b7c87 Mon Sep 17 00:00:00 2001 From: "Yuri.Barboza" Date: Tue, 3 Dec 2024 13:57:59 +0100 Subject: [PATCH 24/74] [python] Remove DeprecatedLinterFindingAsError This commit will be removing the DeprecatedLinterFindingAsError function from the python_tool_common, since it should not be used by the tools anymore. --- quality/private/python/tools/python_tool_common.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/quality/private/python/tools/python_tool_common.py b/quality/private/python/tools/python_tool_common.py index ed269ec..37ce1b0 100644 --- a/quality/private/python/tools/python_tool_common.py +++ b/quality/private/python/tools/python_tool_common.py @@ -70,15 +70,6 @@ def __init__(self, path: str, tool: str): super().__init__(f'The path "{self.path}" was not found. Therefore {self.tool} cannot properly run.') -class DeprecatedLinterFindingAsError(Exception): - """Raised when a linter finds a finding treats it as an error.""" - - def __init__(self, path: t.Union[str, pathlib.Path], tool: str): - self.path = path - self.tool = tool - super().__init__(f'At least one {self.tool} finding was treated as error. See its output at "{self.path}"') - - class LinterFindingAsError(SystemExit): """Raised when a linter finds a finding treats it as an error.""" From 02d76d8e690310805c78625c3bcdf8eb71e499d6 Mon Sep 17 00:00:00 2001 From: Vitor Sorpile Geraldo Date: Tue, 26 Nov 2024 18:05:08 +0100 Subject: [PATCH 25/74] [python] Stardardize pylint output This commit standardizes pylint_runner output, which is part of a bigger effort to standardize every python tool output. --- quality/private/python/tools/pylint_runner.py | 59 +++++++++++++++++-- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/quality/private/python/tools/pylint_runner.py b/quality/private/python/tools/pylint_runner.py index 0aef2a1..e297b5d 100644 --- a/quality/private/python/tools/pylint_runner.py +++ b/quality/private/python/tools/pylint_runner.py @@ -2,9 +2,24 @@ import logging import os +import pathlib from quality.private.python.tools import python_tool_common +PYLINT_MODULE_START_MSG = "************* Module" + + +def is_pylint_critical_error(exception: python_tool_common.LinterSubprocessError) -> bool: + """Checks if the return code represents a pylint critical error. + + Cases considered: + - Pylint returns an odd number when a fatal error occurs; + - Pylint returns 32 when an unrecognized option is used in the CLI call; + - Pylint returns an error message in stderr when an option is configured with a invalid value. + """ + return_code = int(exception.return_code) + return (return_code % 2) == 1 or return_code == 32 or exception.stderr != "" + def check_with_pylint(aspect_arguments: python_tool_common.AspectArguments) -> None: """Run a pylint subprocess, check its output and write its findings to a file.""" @@ -16,30 +31,62 @@ def check_with_pylint(aspect_arguments: python_tool_common.AspectArguments) -> N else: pylint_env["PYTHONPATH"] += ":" + str(target_import) + findings = python_tool_common.Findings() + try: - python_tool_common.execute_subprocess( + pylint_output = python_tool_common.execute_subprocess( [ # Binary executable path. f"{aspect_arguments.tool}", # Configuration flag and path. "--rcfile", f"{aspect_arguments.tool_config}", - # Putput a colorized text to a given path. - f"--output-format=text:{aspect_arguments.tool_output},colorized", # Text content template. "--msg-template", - "{path}:{line}:{column}: {msg} [{msg_id}, {symbol}]", + "{path}:{line}:{column}:{msg}:{symbol}", # Exclude pylint persistent output as this would be both action input and output. "--persistent", "no", + "--score", + "no", # Files to lint "--", *map(str, aspect_arguments.target_files), ], env=pylint_env, ) - finally: + except python_tool_common.LinterSubprocessError as exception: + if is_pylint_critical_error(exception): + raise exception + + pylint_output = python_tool_common.SubprocessInfo( + exception.stdout, + exception.stderr, + exception.return_code, + ) + + for output_line in pylint_output.stdout.splitlines(): + if output_line.startswith(PYLINT_MODULE_START_MSG): + continue + + path, line, column, message, rule_id = output_line.split(":") + + findings.append( + python_tool_common.Finding( + path=pathlib.Path(path), + message=message, + severity=python_tool_common.Severity.WARN, + tool="pylint", + rule_id=rule_id, + line=int(line), + column=int(column), + ) + ) + + aspect_arguments.tool_output.write_text(str(findings)) + if findings: logging.info("Created pylint output at: %s", aspect_arguments.tool_output) + raise python_tool_common.LinterFindingAsError(findings=findings) def main(): @@ -52,5 +99,5 @@ def main(): check_with_pylint(aspect_arguments=args) -if __name__ == "__main__": +if __name__ == "__main__": # pragma: no cover main() From a232ac89a0deef00c186167b4de59616078f8031 Mon Sep 17 00:00:00 2001 From: Vitor Sorpile Geraldo Date: Mon, 2 Dec 2024 20:14:53 +0100 Subject: [PATCH 26/74] [python] Add tests for pylint_runner --- quality/private/python/tools/test/BUILD | 10 +- .../python/tools/test/test_pylint_runner.py | 145 ++++++++++++++++++ 2 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 quality/private/python/tools/test/test_pylint_runner.py diff --git a/quality/private/python/tools/test/BUILD b/quality/private/python/tools/test/BUILD index 7b0bf35..6a710f6 100644 --- a/quality/private/python/tools/test/BUILD +++ b/quality/private/python/tools/test/BUILD @@ -3,9 +3,13 @@ load("@swf_bazel_rules_quality_python//:defs.bzl", "py_test") py_test( name = "test_isort_runner", srcs = ["test_isort_runner.py"], - deps = [ - "//quality/private/python/tools:isort", - ], + deps = ["//quality/private/python/tools:isort"], +) + +py_test( + name = "test_pylint_runner", + srcs = ["test_pylint_runner.py"], + deps = ["//quality/private/python/tools:pylint"], ) py_test( diff --git a/quality/private/python/tools/test/test_pylint_runner.py b/quality/private/python/tools/test/test_pylint_runner.py new file mode 100644 index 0000000..f5827ba --- /dev/null +++ b/quality/private/python/tools/test/test_pylint_runner.py @@ -0,0 +1,145 @@ +"""Tests for the pylint runner.""" + +import os +import pathlib +import sys +import tempfile +import unittest +from unittest.mock import patch + +from quality.private.python.tools import pylint_runner, python_tool_common + + +class TestPylintRunner(unittest.TestCase): + """Test class for pylint runner.""" + + def setUp(self) -> None: + _, self.tmp_file_path = tempfile.mkstemp() + self.aspect_args = python_tool_common.AspectArguments( + target_imports=set(), + target_dependencies=set(), + tool=pathlib.Path(""), + tool_config=pathlib.Path(""), + tool_root="", + target_files=set(), + tool_output=pathlib.Path(self.tmp_file_path), + ) + + def tearDown(self) -> None: + pathlib.Path(self.tmp_file_path).unlink() + + @patch( + "quality.private.python.tools.python_tool_common.execute_subprocess", + side_effect=[ + python_tool_common.SubprocessInfo( + stdout="", + stderr="", + return_code=0, + ) + ], + ) + def test_pylint_output_with_no_issues(self, _) -> None: + """Tests check_with_pylint function with the results of a file with no issues.""" + self.aspect_args.target_imports = set([pathlib.Path("termcolor")]) + pylint_runner.check_with_pylint(self.aspect_args) + self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) + + @patch( + "quality.private.python.tools.python_tool_common.execute_subprocess", + side_effect=[ + python_tool_common.SubprocessInfo( + stdout="", + stderr="", + return_code=0, + ) + ], + ) + def test_pylint_output_with_no_issues_and_python_path_set(self, _) -> None: + """Tests check_with_pylint function with the results of a file with no issues and PYTHONPATH variable set.""" + modified_environ = os.environ.copy() + modified_environ["PYTHONPATH"] = "python" + with patch.dict(os.environ, modified_environ, clear=True): + self.aspect_args.target_imports = set([pathlib.Path("termcolor")]) + pylint_runner.check_with_pylint(self.aspect_args) + self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) + + @patch( + "quality.private.python.tools.python_tool_common.execute_subprocess", + side_effect=[ + python_tool_common.LinterSubprocessError( + commands=[], + stdout="************* Module pylint_runner\nfile.py:11:1:Error message:error-code\n", + stderr="", + return_code=16, + ) + ], + ) + def test_pylint_output_with_issues(self, _) -> None: + """Tests check_with_pylint function with the results of a file with issues.""" + expected_error_message = "file.py:11:1: Error message [pylint:error-code]" + expected_output_log_message = f"INFO:root:Created pylint output at: {self.tmp_file_path}" + + with self.assertRaises(python_tool_common.LinterFindingAsError) as exception: + with self.assertLogs() as logs: + pylint_runner.check_with_pylint(self.aspect_args) + + findings_as_error_message = str(exception.exception.findings) + output_file_content = self.aspect_args.tool_output.read_text(encoding="utf-8") + output_log_message = logs.output[0] + + self.assertEqual(expected_error_message, findings_as_error_message) + self.assertEqual(expected_error_message, output_file_content) + self.assertEqual(expected_output_log_message, output_log_message) + + @patch( + "quality.private.python.tools.python_tool_common.execute_subprocess", + side_effect=[ + python_tool_common.LinterSubprocessError( + commands=[], + stdout="", + stderr="ERROR: unexpected error\n", + return_code=32, + ) + ], + ) + def test_check_with_pylint_unexpected_exception(self, _) -> None: + """Test check_with_pylint function when an unexpected exception occurs.""" + with self.assertRaises(python_tool_common.LinterSubprocessError) as exception: + with self.assertLogs() as logs: + pylint_runner.check_with_pylint(self.aspect_args) + + expected_return_code = 32 + + self.assertEqual(exception.exception.return_code, expected_return_code) + self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) + self.assertFalse(logs.output) + + @patch( + "quality.private.python.tools.python_tool_common.execute_subprocess", + side_effect=[ + python_tool_common.SubprocessInfo( + stdout="", + stderr="", + return_code=0, + ) + ], + ) + def test_call_main(self, _) -> None: + """Tests calling main.""" + mocked_args = [ + "--tool", + " ", + "--tool-config", + " ", + "--tool-output", + self.tmp_file_path, + "--tool-root", + " ", + ] + with patch.object(sys, "argv", ["pylint"] + mocked_args): + pylint_runner.main() + self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) + + +if __name__ == "__main__": # pragma: no cover + unittest.main() From 0c5271eb1fd893ff18ebe9774fde45a9977e8ced Mon Sep 17 00:00:00 2001 From: "Yuri.Barboza" Date: Thu, 5 Dec 2024 19:54:58 +0100 Subject: [PATCH 27/74] [python] Black automated Fix To better integrate our python aspect with our metrics tooling or even with a normal CI usage we should enable our aspect to automatically apply fixes. So in this commit black shall now be able to automatically fix a file depending on the user command. --- quality/private/python/python_providers.bzl | 1 + quality/private/python/python_tool_aspect.bzl | 17 +++++++++++++++- quality/private/python/tools/black_runner.py | 20 ++++++++++--------- .../python/tools/python_tool_common.py | 9 ++++++++- .../python/tools/test/test_black_runner.py | 20 ++++++++++++++++++- .../python/tools/test/test_isort_runner.py | 1 + .../python/tools/test/test_pylint_runner.py | 1 + 7 files changed, 57 insertions(+), 12 deletions(-) diff --git a/quality/private/python/python_providers.bzl b/quality/private/python/python_providers.bzl index dd23f3a..dcee8f0 100644 --- a/quality/private/python/python_providers.bzl +++ b/quality/private/python/python_providers.bzl @@ -11,6 +11,7 @@ PythonCollectInfo = provider( PythonToolInfo = provider( doc = "Configuration structure for the python tool aspect.", fields = { + "additional_features": "List of additional bazel features to be enabled when invoking python aspect.", "config": "Configuration file for the respective python tool.", }, ) diff --git a/quality/private/python/python_tool_aspect.bzl b/quality/private/python/python_tool_aspect.bzl index 20ef9c7..4c7089a 100644 --- a/quality/private/python/python_tool_aspect.bzl +++ b/quality/private/python/python_tool_aspect.bzl @@ -5,11 +5,19 @@ load("@swf_bazel_rules_quality//quality/private/python:python_helper.bzl", "is_v load("@swf_bazel_rules_quality//quality/private/python:python_providers.bzl", "PythonCollectInfo", "PythonToolInfo") def _python_tool_config_impl(ctx): - return [PythonToolInfo(config = ctx.file.config)] + return [PythonToolInfo(config = ctx.file.config, additional_features = ctx.attr.additional_features)] python_tool_config = rule( implementation = _python_tool_config_impl, attrs = { + "additional_features": attr.string_list( + default = [], + doc = """List of additional bazel features to be enabled when invoking python aspect. +The available options are: + refactor: tools that are able to refactor will automatically fix findings. In order to allow + refactoring, this option enforces all actions to run in a non-sandboxed mode. +""", + ), "config": attr.label( allow_single_file = True, ), @@ -25,6 +33,7 @@ def _python_tool_aspect_implementation(target, ctx): return [OutputGroupInfo(python_tool_output = depset([]))] config = ctx.attr._config[PythonToolInfo].config + additional_features = ctx.attr._config[PythonToolInfo].additional_features sources_to_run = [] for source in ctx.rule.attr.srcs: @@ -53,6 +62,11 @@ def _python_tool_aspect_implementation(target, ctx): args.add("--tool-output", output_file.path, format = "%s") args.add("--tool-root", ctx.expand_location(ctx.workspace_name), format = "%s") + file_refactor = "false" + if "refactor" in ctx.features or "refactor" in additional_features: + args.add("--refactor", True, format = "%s") + file_refactor = "true" + ctx.actions.run( inputs = depset([config], transitive = [target[DefaultInfo].default_runfiles.files]), outputs = [output_file], @@ -60,6 +74,7 @@ def _python_tool_aspect_implementation(target, ctx): executable = ctx.executable._runner, arguments = [args], progress_message = "Running {tool} on: {target_name}".format(tool = ctx.executable._runner.basename, target_name = target.label.name), + execution_requirements = {"no-sandbox": file_refactor}, ) return [OutputGroupInfo(python_tool_output = depset(output))] diff --git a/quality/private/python/tools/black_runner.py b/quality/private/python/tools/black_runner.py index 2c9ae61..c663a8e 100644 --- a/quality/private/python/tools/black_runner.py +++ b/quality/private/python/tools/black_runner.py @@ -20,15 +20,17 @@ def check_with_black(aspect_arguments: python_tool_common.AspectArguments) -> No findings = python_tool_common.Findings() - black_output = python_tool_common.execute_subprocess( - [ - f"{aspect_arguments.tool}", - "--diff", - "--config", - f"{aspect_arguments.tool_config}", - *map(str, aspect_arguments.target_files), - ], - ) + subprocess_list = [ + f"{aspect_arguments.tool}", + "--diff", + "--config", + f"{aspect_arguments.tool_config}", + *map(str, aspect_arguments.target_files), + ] + if aspect_arguments.refactor: + subprocess_list.remove("--diff") + + black_output = python_tool_common.execute_subprocess(subprocess_list) for line in black_output.stderr.splitlines(): if line.startswith(WOULD_REFORMAT_MSG): diff --git a/quality/private/python/tools/python_tool_common.py b/quality/private/python/tools/python_tool_common.py index 37ce1b0..4da41a6 100644 --- a/quality/private/python/tools/python_tool_common.py +++ b/quality/private/python/tools/python_tool_common.py @@ -153,7 +153,7 @@ def _is_relative_to(path: pathlib.Path, root: pathlib.Path): @dataclasses.dataclass -class AspectArguments: +class AspectArguments: # pylint: disable=too-many-instance-attributes """Class that provides a clean and verified interface between aspect and runner.""" target_imports: t.Set[pathlib.Path] @@ -163,6 +163,7 @@ class AspectArguments: tool_config: pathlib.Path tool_output: pathlib.Path tool_root: str + refactor: bool def __post_init__(self): def resolve_paths(paths: t.List[str], prepend_path: str = "") -> t.Set[pathlib.Path]: @@ -255,5 +256,11 @@ def parse_args() -> AspectArguments: required=True, help="", ) + parser.add_argument( + "--refactor", + type=bool, + default=False, + help="", + ) return AspectArguments(**vars(parser.parse_args())) diff --git a/quality/private/python/tools/test/test_black_runner.py b/quality/private/python/tools/test/test_black_runner.py index 7ed2e48..af83f66 100644 --- a/quality/private/python/tools/test/test_black_runner.py +++ b/quality/private/python/tools/test/test_black_runner.py @@ -22,6 +22,7 @@ def setUp(self) -> None: tool_root="", target_files=set(), tool_output=pathlib.Path(self.tmp_file_path), + refactor=False, ) def tearDown(self) -> None: @@ -39,10 +40,27 @@ def tearDown(self) -> None: ) def test_black_output_with_no_issues(self, _) -> None: """Tests check_with_black function with the results of a file with no issues.""" - black_runner.check_with_black(self.aspect_args) + self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) + @patch( + "quality.private.python.tools.python_tool_common.execute_subprocess", + side_effect=[ + python_tool_common.SubprocessInfo( + stdout="", + stderr="", + return_code=0, + ) + ], + ) + def test_black_output_with_refactor(self, execute_subprocess) -> None: + """Tests check_with_black function with the refactor being true.""" + self.aspect_args.refactor = True + black_runner.check_with_black(self.aspect_args) + + self.assertFalse("--diff" in execute_subprocess.call_args) + @patch( "quality.private.python.tools.python_tool_common.execute_subprocess", side_effect=[ diff --git a/quality/private/python/tools/test/test_isort_runner.py b/quality/private/python/tools/test/test_isort_runner.py index 2331623..4d05139 100644 --- a/quality/private/python/tools/test/test_isort_runner.py +++ b/quality/private/python/tools/test/test_isort_runner.py @@ -22,6 +22,7 @@ def setUp(self) -> None: tool_root="", target_files=set(), tool_output=pathlib.Path(self.tmp_file_path), + refactor=False, ) def tearDown(self) -> None: diff --git a/quality/private/python/tools/test/test_pylint_runner.py b/quality/private/python/tools/test/test_pylint_runner.py index f5827ba..1a4530a 100644 --- a/quality/private/python/tools/test/test_pylint_runner.py +++ b/quality/private/python/tools/test/test_pylint_runner.py @@ -23,6 +23,7 @@ def setUp(self) -> None: tool_root="", target_files=set(), tool_output=pathlib.Path(self.tmp_file_path), + refactor=False, ) def tearDown(self) -> None: From dccda33d18a6de33e48424b5e7a8ddd6bfbd7ca1 Mon Sep 17 00:00:00 2001 From: "Yuri.Barboza" Date: Thu, 5 Dec 2024 19:54:58 +0100 Subject: [PATCH 28/74] [python] Isort Automated Fix To better integrate our python aspect with our metrics tooling or even with a normal CI usage we should enable our aspect to automatically apply fixes. So in this commit isort shall now be able to automatically fix a file depending on the user command. --- quality/private/python/tools/isort_runner.py | 27 +-- .../python/tools/test/test_isort_runner.py | 181 +++++++++++------- 2 files changed, 123 insertions(+), 85 deletions(-) diff --git a/quality/private/python/tools/isort_runner.py b/quality/private/python/tools/isort_runner.py index cfa582d..ff90f60 100644 --- a/quality/private/python/tools/isort_runner.py +++ b/quality/private/python/tools/isort_runner.py @@ -21,19 +21,20 @@ def check_with_isort(aspect_arguments: python_tool_common.AspectArguments) -> No findings = python_tool_common.Findings() try: - isort_output = python_tool_common.execute_subprocess( - [ - f"{aspect_arguments.tool}", - "--check-only", - "--diff", - "--sp", - f"{aspect_arguments.tool_config}", - "--src", - f"{pathlib.Path.cwd()}", - "--", - *map(str, aspect_arguments.target_files), - ], - ) + subprocess_list = [ + f"{aspect_arguments.tool}", + "--sp", + f"{aspect_arguments.tool_config}", + "--src", + f"{pathlib.Path.cwd()}", + "--", + *map(str, aspect_arguments.target_files), + ] + + if not aspect_arguments.refactor: + subprocess_list[1:1] = ["--check-only", "--diff"] + + isort_output = python_tool_common.execute_subprocess(subprocess_list) except python_tool_common.LinterSubprocessError as exception: if exception.return_code != ISORT_BAD_CHECK_ERROR_CODE: raise exception diff --git a/quality/private/python/tools/test/test_isort_runner.py b/quality/private/python/tools/test/test_isort_runner.py index 4d05139..61ed46e 100644 --- a/quality/private/python/tools/test/test_isort_runner.py +++ b/quality/private/python/tools/test/test_isort_runner.py @@ -28,71 +28,120 @@ def setUp(self) -> None: def tearDown(self) -> None: pathlib.Path(self.tmp_file_path).unlink() - def test_isort_output_with_no_issues(self) -> None: + @patch( + "quality.private.python.tools.python_tool_common.execute_subprocess", + side_effect=[ + python_tool_common.SubprocessInfo( + stdout="", + stderr="", + return_code=0, + ) + ], + ) + def test_isort_output_with_no_issues(self, _) -> None: """Tests check_with_isort function with the results of a file with no issues.""" - with patch( - "quality.private.python.tools.python_tool_common.execute_subprocess", - side_effect=[ - python_tool_common.SubprocessInfo( - stdout="", - stderr="", - return_code=0, - ) - ], - ): - isort_runner.check_with_isort(self.aspect_args) - self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) - - def test_isort_output_with_issues(self) -> None: + isort_runner.check_with_isort(self.aspect_args) + + self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) + + @patch( + "quality.private.python.tools.python_tool_common.execute_subprocess", + side_effect=[ + python_tool_common.SubprocessInfo( + stdout="", + stderr="", + return_code=0, + ) + ], + ) + def test_black_output_with_refactor(self, execute_subprocess) -> None: + """Tests check_with_isort function with the refactor being true.""" + self.aspect_args.refactor = True + isort_runner.check_with_isort(self.aspect_args) + + self.assertFalse("--check-only" in execute_subprocess.call_args.args[0]) + self.assertFalse("--diff" in execute_subprocess.call_args.args[0]) + + @patch( + "quality.private.python.tools.python_tool_common.execute_subprocess", + side_effect=[ + python_tool_common.SubprocessInfo( + stdout="", + stderr="", + return_code=0, + ) + ], + ) + def test_black_output_without_refactor(self, execute_subprocess) -> None: + """Tests check_with_isort function without the refactor being true.""" + self.aspect_args.refactor = False + isort_runner.check_with_isort(self.aspect_args) + + self.assertTrue("--check-only" in execute_subprocess.call_args.args[0]) + self.assertTrue("--diff" in execute_subprocess.call_args.args[0]) + + @patch( + "quality.private.python.tools.python_tool_common.execute_subprocess", + side_effect=[ + python_tool_common.LinterSubprocessError( + commands=[], + stdout="", + stderr="ERROR: file.py Imports are incorrectly sorted and/or formatted.\n", + return_code=1, + ) + ], + ) + def test_isort_output_with_issues(self, _) -> None: """Tests check_with_isort function with the results of a file with issues.""" expected_error_message = "file.py: Imports are incorrectly sorted and/or formatted. [isort:imports_formatting]" expected_output_log_message = f"INFO:root:Created isort output at: {self.tmp_file_path}" - - with patch( - "quality.private.python.tools.python_tool_common.execute_subprocess", - side_effect=[ - python_tool_common.LinterSubprocessError( - commands=[], - stdout="", - stderr="ERROR: file.py Imports are incorrectly sorted and/or formatted.\n", - return_code=1, - ) - ], - ): - with self.assertRaises(python_tool_common.LinterFindingAsError) as exception: - with self.assertLogs() as logs: - isort_runner.check_with_isort(self.aspect_args) - - findings_as_error_message = str(exception.exception.findings) - output_file_content = self.aspect_args.tool_output.read_text(encoding="utf-8") - output_log_message = logs.output[0] - - self.assertEqual(expected_error_message, findings_as_error_message) - self.assertEqual(expected_error_message, output_file_content) - self.assertEqual(expected_output_log_message, output_log_message) - - def test_check_with_isort_unexpected_exception(self) -> None: + self.aspect_args.refactor = True + + with self.assertRaises(python_tool_common.LinterFindingAsError) as exception: + with self.assertLogs() as logs: + isort_runner.check_with_isort(self.aspect_args) + + findings_as_error_message = str(exception.exception.findings) + output_file_content = self.aspect_args.tool_output.read_text(encoding="utf-8") + output_log_message = logs.output[0] + + self.assertEqual(expected_error_message, findings_as_error_message) + self.assertEqual(expected_error_message, output_file_content) + self.assertEqual(expected_output_log_message, output_log_message) + + @patch( + "quality.private.python.tools.python_tool_common.execute_subprocess", + side_effect=[ + python_tool_common.LinterSubprocessError( + commands=[], + stdout="", + stderr="ERROR: unexpected error\n", + return_code=2, + ) + ], + ) + def test_check_with_isort_unexpected_exception(self, _) -> None: """Test check_with_isort function when an unexpected exception occurs.""" - with patch( - "quality.private.python.tools.python_tool_common.execute_subprocess", - side_effect=[ - python_tool_common.LinterSubprocessError( - commands=[], - stdout="", - stderr="ERROR: unexpected error\n", - return_code=2, - ) - ], - ): - with self.assertRaises(python_tool_common.LinterSubprocessError) as exception: - with self.assertLogs() as logs: - isort_runner.check_with_isort(self.aspect_args) - - self.assertEqual(exception.exception.return_code, 2) - self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) - self.assertFalse(logs.output) - def test_call_main(self) -> None: + with self.assertRaises(python_tool_common.LinterSubprocessError) as exception: + with self.assertLogs() as logs: + isort_runner.check_with_isort(self.aspect_args) + + self.assertEqual(exception.exception.return_code, 2) + self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) + self.assertFalse(logs.output) + + @patch( + "quality.private.python.tools.python_tool_common.execute_subprocess", + side_effect=[ + python_tool_common.SubprocessInfo( + stdout="", + stderr="", + return_code=0, + ) + ], + ) + def test_call_main(self, _) -> None: """Tests calling main.""" mocked_args = [ "--tool", @@ -104,19 +153,7 @@ def test_call_main(self) -> None: "--tool-root", " ", ] - with ( - patch.object(sys, "argv", ["isort"] + mocked_args), - patch( - "quality.private.python.tools.python_tool_common.execute_subprocess", - side_effect=[ - python_tool_common.SubprocessInfo( - stdout="", - stderr="", - return_code=0, - ) - ], - ), - ): + with patch.object(sys, "argv", ["isort"] + mocked_args): isort_runner.main() self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) From e1300a328be01ec0ce2dc75cc2da7933f9bcd8d2 Mon Sep 17 00:00:00 2001 From: "Yuri.Barboza" Date: Wed, 11 Dec 2024 13:44:52 +0100 Subject: [PATCH 29/74] [python] Fix black runner test This commit goal is to fix test_black_output_with_refactor test, fixing the assert case to assert against the correct variable. --- quality/private/python/tools/test/test_black_runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quality/private/python/tools/test/test_black_runner.py b/quality/private/python/tools/test/test_black_runner.py index af83f66..8119cd0 100644 --- a/quality/private/python/tools/test/test_black_runner.py +++ b/quality/private/python/tools/test/test_black_runner.py @@ -59,7 +59,7 @@ def test_black_output_with_refactor(self, execute_subprocess) -> None: self.aspect_args.refactor = True black_runner.check_with_black(self.aspect_args) - self.assertFalse("--diff" in execute_subprocess.call_args) + self.assertFalse("--diff" in execute_subprocess.call_args.args[0]) @patch( "quality.private.python.tools.python_tool_common.execute_subprocess", From 59703aa9585d36433d3b2ea35d701e40032f5b83 Mon Sep 17 00:00:00 2001 From: "Yuri.Barboza" Date: Wed, 11 Dec 2024 14:36:01 +0100 Subject: [PATCH 30/74] [python] Ruff Automated Fix To better integrate our python aspect with our metrics tooling or even with a normal CI usage we should enable our aspect to automatically apply fixes. So in this commit ruff check and ruff format shall now be able to automatically fix a file depending on the user command. --- .../private/python/tools/ruff_check_runner.py | 27 ++++++++++--------- .../python/tools/ruff_format_runner.py | 21 ++++++++------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/quality/private/python/tools/ruff_check_runner.py b/quality/private/python/tools/ruff_check_runner.py index 2364d55..e0a9199 100644 --- a/quality/private/python/tools/ruff_check_runner.py +++ b/quality/private/python/tools/ruff_check_runner.py @@ -26,18 +26,21 @@ def check_with_ruff(aspect_arguments: python_tool_common.AspectArguments) -> Non findings = python_tool_common.Findings() try: - python_tool_common.execute_subprocess( - [ - f"{aspect_arguments.tool}", - "check", - "--config", - f"{aspect_arguments.tool_config}", - "--unsafe-fixes", - "--output-format", - "json", - *map(str, aspect_arguments.target_files), - ], - ) + subprocess_list = [ + f"{aspect_arguments.tool}", + "check", + "--config", + f"{aspect_arguments.tool_config}", + "--fix", + "--output-format", + "json", + *map(str, aspect_arguments.target_files), + ] + if not aspect_arguments.refactor: + subprocess_list.remove("--fix") + subprocess_list[4:4] = ["--unsafe-fixes"] + + python_tool_common.execute_subprocess(subprocess_list) except python_tool_common.LinterSubprocessError as exception: if exception.return_code != RUFF_BAD_CHECK_ERROR_CODE: raise exception diff --git a/quality/private/python/tools/ruff_format_runner.py b/quality/private/python/tools/ruff_format_runner.py index ef45bba..b6c77a2 100644 --- a/quality/private/python/tools/ruff_format_runner.py +++ b/quality/private/python/tools/ruff_format_runner.py @@ -27,16 +27,17 @@ def format_with_ruff(aspect_arguments: python_tool_common.AspectArguments) -> No findings = python_tool_common.Findings() try: - ruff_output = python_tool_common.execute_subprocess( - [ - f"{aspect_arguments.tool}", - "format", - "--config", - f"{aspect_arguments.tool_config}", - "--diff", - *map(str, aspect_arguments.target_files), - ], - ) + subprocess_list = [ + f"{aspect_arguments.tool}", + "format", + "--config", + f"{aspect_arguments.tool_config}", + *map(str, aspect_arguments.target_files), + ] + if not aspect_arguments.refactor: + subprocess_list[4:4] = ["--diff"] + + ruff_output = python_tool_common.execute_subprocess(subprocess_list) except python_tool_common.LinterSubprocessError as exception: if exception.return_code != RUFF_BAD_CHECK_ERROR_CODE: raise exception From 99b561fd802224556c8fb6ed4821ba41e3ae9cc4 Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Tue, 3 Dec 2024 10:50:09 +0100 Subject: [PATCH 31/74] [docs] Improve our documentation Simplify our main README.md into `Offered Tools`, `How to use BRQ` and `Contributing`. Create a `CONTRIBUTING.md`. Split, and slighty refactor, each specific tool section into its own README.md file. --- quality/private/python/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 quality/private/python/README.md diff --git a/quality/private/python/README.md b/quality/private/python/README.md new file mode 100644 index 0000000..23fbca2 --- /dev/null +++ b/quality/private/python/README.md @@ -0,0 +1,3 @@ +# Python + +TBD From 03ebeec893e326829d3ad36d6ad2e2b05fd4a0ff Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Tue, 7 Jan 2025 15:45:15 +0100 Subject: [PATCH 32/74] [python] Improve compatibility across multiple versions Minor improvements that improve compatibility for multiple versions of python. --- quality/private/python/black_entry_point.py | 11 +++++++++++ quality/private/python/isort_entry_point.py | 11 +++++++++++ quality/private/python/mypy_entry_point.py | 11 +++++++++++ quality/private/python/pylint_entry_point.py | 11 +++++++++++ quality/private/python/ruff_entry_point.py | 4 ++-- quality/private/python/tools/black_runner.py | 15 +++++++++++++-- .../python/tools/test/test_isort_runner.py | 14 ++++++++++++-- 7 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 quality/private/python/black_entry_point.py create mode 100644 quality/private/python/isort_entry_point.py create mode 100644 quality/private/python/mypy_entry_point.py create mode 100644 quality/private/python/pylint_entry_point.py diff --git a/quality/private/python/black_entry_point.py b/quality/private/python/black_entry_point.py new file mode 100644 index 0000000..c69f927 --- /dev/null +++ b/quality/private/python/black_entry_point.py @@ -0,0 +1,11 @@ +"""Entry point for black python library. + +This executes black by importing, thus executing, its main entry point. +""" + +# ruff: noqa: F401 +# We do not want to use __main__ but only import it. +# That is because when we import it, python already runs the tool entry point. + +if __name__ == "__main__": + from black import __main__ # type: ignore[import-untyped] diff --git a/quality/private/python/isort_entry_point.py b/quality/private/python/isort_entry_point.py new file mode 100644 index 0000000..2b2e0df --- /dev/null +++ b/quality/private/python/isort_entry_point.py @@ -0,0 +1,11 @@ +"""Entry point for isort python library. + +This executes isort by importing, thus executing, its main entry point. +""" + +# ruff: noqa: F401 +# We do not want to use __main__ but only import it. +# That is because when we import it, python already runs the tool entry point. + +if __name__ == "__main__": + from isort import __main__ # type: ignore[import-untyped] diff --git a/quality/private/python/mypy_entry_point.py b/quality/private/python/mypy_entry_point.py new file mode 100644 index 0000000..8248700 --- /dev/null +++ b/quality/private/python/mypy_entry_point.py @@ -0,0 +1,11 @@ +"""Entry point for mypy python library. + +This executes mypy by importing, thus executing, its main entry point. +""" + +# ruff: noqa: F401 +# We do not want to use __main__ but only import it. +# That is because when we import it, python already runs the tool entry point. + +if __name__ == "__main__": + from mypy import __main__ # type: ignore[import-untyped] diff --git a/quality/private/python/pylint_entry_point.py b/quality/private/python/pylint_entry_point.py new file mode 100644 index 0000000..066fa17 --- /dev/null +++ b/quality/private/python/pylint_entry_point.py @@ -0,0 +1,11 @@ +"""Entry point for pylint python library. + +This executes pylint by importing, thus executing, its main entry point. +""" + +# ruff: noqa: F401 +# We do not want to use __main__ but only import it. +# That is because when we import it, python already runs the tool entry point. + +if __name__ == "__main__": + from pylint import __main__ # type: ignore[import-untyped] diff --git a/quality/private/python/ruff_entry_point.py b/quality/private/python/ruff_entry_point.py index 762fc2b..f346f03 100644 --- a/quality/private/python/ruff_entry_point.py +++ b/quality/private/python/ruff_entry_point.py @@ -8,9 +8,9 @@ import sys from pathlib import Path -from ruff import __main__ as ruff_main # type: ignore[import-untyped] - if __name__ == "__main__": + from ruff import __main__ as ruff_main # type: ignore[import-untyped] + ruff_main_path = Path(ruff_main.__file__).resolve(strict=True) ruff_external = ruff_main_path.parent.parent.parent ruff_bin = ruff_external / "bin/ruff" diff --git a/quality/private/python/tools/black_runner.py b/quality/private/python/tools/black_runner.py index c663a8e..494addd 100644 --- a/quality/private/python/tools/black_runner.py +++ b/quality/private/python/tools/black_runner.py @@ -8,12 +8,23 @@ WOULD_REFORMAT_MSG = "would reformat" +def _removeprefix(text: str, prefix: str) -> str: + """Remove a certain prefix from a a given text. + + This function is supposed to add backwards compartibility with python 3.8 as + python versions equal or greater than 3.9 already offer this as a built in. + """ + if text.startswith(prefix): + return text[len(prefix) :].strip() + return text + + def check_with_black(aspect_arguments: python_tool_common.AspectArguments) -> None: """Run a black subprocess, check its output and write its findings to a file. :param aspect_arguments: The arguments received from the python_tool_aspect and already processed by - python_tool_common module. + python_tool_common module. :raises LinterFindingAsError: If black finds at least one file to be formatted. """ @@ -34,7 +45,7 @@ def check_with_black(aspect_arguments: python_tool_common.AspectArguments) -> No for line in black_output.stderr.splitlines(): if line.startswith(WOULD_REFORMAT_MSG): - file = line.removeprefix(WOULD_REFORMAT_MSG) + file = _removeprefix(line, WOULD_REFORMAT_MSG) findings += [ python_tool_common.Finding( path=pathlib.Path(file), diff --git a/quality/private/python/tools/test/test_isort_runner.py b/quality/private/python/tools/test/test_isort_runner.py index 61ed46e..e40a853 100644 --- a/quality/private/python/tools/test/test_isort_runner.py +++ b/quality/private/python/tools/test/test_isort_runner.py @@ -154,8 +154,18 @@ def test_call_main(self, _) -> None: " ", ] with patch.object(sys, "argv", ["isort"] + mocked_args): - isort_runner.main() - self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) + with patch( + "quality.private.python.tools.python_tool_common.execute_subprocess", + side_effect=[ + python_tool_common.SubprocessInfo( + stdout="", + stderr="", + return_code=0, + ) + ], + ): + isort_runner.main() + self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) if __name__ == "__main__": # pragma: no cover From 15f1a316f2fb36ad9b891fc4cdc06405ce60224b Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Wed, 15 Jan 2025 10:52:46 +0100 Subject: [PATCH 33/74] [bazel] Adapt every target to the new bazel infra With this we adapt a lot BUILD files (mainly labels) to the new bazel infrastructure. Also, py_version_printer was upgraded to supply more information. --- quality/private/python/BUILD | 51 ++++++++----------------- quality/private/python/tools/BUILD | 3 +- quality/private/python/tools/test/BUILD | 2 +- 3 files changed, 17 insertions(+), 39 deletions(-) diff --git a/quality/private/python/BUILD b/quality/private/python/BUILD index 58d8ab3..a67b47e 100644 --- a/quality/private/python/BUILD +++ b/quality/private/python/BUILD @@ -1,53 +1,32 @@ -load("@swf_bazel_rules_quality//third_party/pip:requirements.bzl", "requirement") -load("@swf_bazel_rules_quality_python//:defs.bzl", "py_binary", "py_console_script_binary") +load("@rules_python//python:defs.bzl", "py_binary") +load("@swf_bazel_rules_quality_pip_hub//:loaders.bzl", "pkg") -py_console_script_binary( +py_binary( name = "pylint_entry_point", - pkg = requirement("pylint"), - script = "pylint", + srcs = ["pylint_entry_point.py"], visibility = ["//visibility:public"], - deps = [ - requirement("astroid"), - requirement("dill"), - requirement("isort"), - requirement("mccabe"), - requirement("platformdirs"), - requirement("tomlkit"), - ], + deps = [pkg("pylint")], ) -py_console_script_binary( +py_binary( name = "black_entry_point", - pkg = requirement("black"), - script = "black", + srcs = ["black_entry_point.py"], visibility = ["//visibility:public"], + deps = [pkg("black")], ) -py_console_script_binary( +py_binary( name = "isort_entry_point", - pkg = requirement("isort"), - script = "isort", + srcs = ["isort_entry_point.py"], visibility = ["//visibility:public"], + deps = [pkg("isort")], ) -py_console_script_binary( +py_binary( name = "mypy_entry_point", - pkg = requirement("mypy"), - script = "mypy", + srcs = ["mypy_entry_point.py"], visibility = ["//visibility:public"], - deps = [ - requirement("types-jsonschema"), - requirement("types-PyYAML"), - requirement("types-requests"), - requirement("types-typed-ast"), - requirement("types-six"), - requirement("types-setuptools"), - requirement("types-python-dateutil"), - requirement("types-certifi"), - requirement("types-chardet"), - requirement("types-urllib3"), - requirement("types-html5lib"), - ], + deps = [pkg("mypy")], ) py_binary( @@ -55,5 +34,5 @@ py_binary( srcs = ["ruff_entry_point.py"], main = "ruff_entry_point.py", visibility = ["//visibility:public"], - deps = [requirement("ruff")], + deps = [pkg("ruff")], ) diff --git a/quality/private/python/tools/BUILD b/quality/private/python/tools/BUILD index 4270748..7ecf09e 100644 --- a/quality/private/python/tools/BUILD +++ b/quality/private/python/tools/BUILD @@ -1,5 +1,4 @@ -load("@swf_bazel_rules_quality_python//:defs.bzl", "py_binary") -load("@swf_bazel_rules_quality_rules_python//python:defs.bzl", "py_library") +load("@rules_python//python:defs.bzl", "py_binary", "py_library") py_library( name = "python_tool_common", diff --git a/quality/private/python/tools/test/BUILD b/quality/private/python/tools/test/BUILD index 6a710f6..bbe371d 100644 --- a/quality/private/python/tools/test/BUILD +++ b/quality/private/python/tools/test/BUILD @@ -1,4 +1,4 @@ -load("@swf_bazel_rules_quality_python//:defs.bzl", "py_test") +load("@rules_python//python:defs.bzl", "py_test") py_test( name = "test_isort_runner", From 95c423eebde397e0c14ba1de7309ed43aa787e93 Mon Sep 17 00:00:00 2001 From: Vitor Sorpile Geraldo Date: Tue, 14 Jan 2025 13:37:10 +0100 Subject: [PATCH 34/74] [python] Add PYTHON_PATH routine to python_common_tool module --- quality/private/python/tools/mypy_runner.py | 7 ------- quality/private/python/tools/pylint_runner.py | 9 --------- quality/private/python/tools/python_tool_common.py | 9 +++++++-- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/quality/private/python/tools/mypy_runner.py b/quality/private/python/tools/mypy_runner.py index 1f82dff..816a162 100644 --- a/quality/private/python/tools/mypy_runner.py +++ b/quality/private/python/tools/mypy_runner.py @@ -1,7 +1,6 @@ """A runner that interfaces python tool aspect and runs mypy on a list of files.""" import logging -import os import pathlib from quality.private.python.tools import python_tool_common @@ -19,13 +18,7 @@ def check_with_mypy(aspect_arguments: python_tool_common.AspectArguments) -> Non If mypy finds a file to be formatted. """ - pylint_env = os.environ findings = python_tool_common.Findings() - for target_import in aspect_arguments.target_imports | aspect_arguments.target_dependencies: - if "PYTHONPATH" not in pylint_env: - pylint_env["PYTHONPATH"] = str(target_import) - else: - pylint_env["PYTHONPATH"] += ":" + str(target_import) try: mypy_output = python_tool_common.execute_subprocess( diff --git a/quality/private/python/tools/pylint_runner.py b/quality/private/python/tools/pylint_runner.py index e297b5d..de0dc66 100644 --- a/quality/private/python/tools/pylint_runner.py +++ b/quality/private/python/tools/pylint_runner.py @@ -1,7 +1,6 @@ """A runner that interfaces python tool aspect and runs pylint on a list of files.""" import logging -import os import pathlib from quality.private.python.tools import python_tool_common @@ -24,13 +23,6 @@ def is_pylint_critical_error(exception: python_tool_common.LinterSubprocessError def check_with_pylint(aspect_arguments: python_tool_common.AspectArguments) -> None: """Run a pylint subprocess, check its output and write its findings to a file.""" - pylint_env = os.environ - for target_import in aspect_arguments.target_imports | aspect_arguments.target_dependencies: - if "PYTHONPATH" not in pylint_env: - pylint_env["PYTHONPATH"] = str(target_import) - else: - pylint_env["PYTHONPATH"] += ":" + str(target_import) - findings = python_tool_common.Findings() try: @@ -53,7 +45,6 @@ def check_with_pylint(aspect_arguments: python_tool_common.AspectArguments) -> N "--", *map(str, aspect_arguments.target_files), ], - env=pylint_env, ) except python_tool_common.LinterSubprocessError as exception: if is_pylint_critical_error(exception): diff --git a/quality/private/python/tools/python_tool_common.py b/quality/private/python/tools/python_tool_common.py index 4da41a6..d3d81eb 100644 --- a/quality/private/python/tools/python_tool_common.py +++ b/quality/private/python/tools/python_tool_common.py @@ -110,7 +110,6 @@ class SubprocessInfo: def execute_subprocess( commands: t.List[str], cwd: pathlib.Path = pathlib.Path.cwd(), - env: t.Optional[t.Mapping[str, str]] = None, ) -> SubprocessInfo: """Function that calls a subprocess and expects a zero return code.""" try: @@ -121,7 +120,6 @@ def execute_subprocess( check=True, text=True, shell=False, - env=env or os.environ, cwd=cwd, universal_newlines=True, ) @@ -203,6 +201,13 @@ def resolve_paths(paths: t.List[str], prepend_path: str = "") -> t.Set[pathlib.P self.target_dependencies = resolve_paths(self.target_dependencies) self.target_files = resolve_paths(self.target_files) + env = os.environ + for target_import in self.target_imports | self.target_dependencies: + if "PYTHONPATH" not in env: + env["PYTHONPATH"] = str(target_import) + else: + env["PYTHONPATH"] += os.pathsep + str(target_import) + def parse_args() -> AspectArguments: """Parse and return arguments.""" From 1d3b5561162967cbb16830d8c0a3cb68904c4f5b Mon Sep 17 00:00:00 2001 From: Vitor Sorpile Geraldo Date: Wed, 22 Jan 2025 13:01:55 +0100 Subject: [PATCH 35/74] [python] Fix mypy entry point With the changes introduced in previous commits, the mypy runner wasn't working as expected due to it's entry point. --- quality/private/python/BUILD | 15 ++++++++++++++- quality/private/python/mypy_entry_point.py | 2 ++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/quality/private/python/BUILD b/quality/private/python/BUILD index a67b47e..06ac570 100644 --- a/quality/private/python/BUILD +++ b/quality/private/python/BUILD @@ -26,7 +26,20 @@ py_binary( name = "mypy_entry_point", srcs = ["mypy_entry_point.py"], visibility = ["//visibility:public"], - deps = [pkg("mypy")], + deps = [ + pkg("mypy"), + pkg("types-jsonschema"), + pkg("types-PyYAML"), + pkg("types-requests"), + pkg("types-typed-ast"), + pkg("types-six"), + pkg("types-setuptools"), + pkg("types-python-dateutil"), + pkg("types-certifi"), + pkg("types-chardet"), + pkg("types-urllib3"), + pkg("types-html5lib"), + ], ) py_binary( diff --git a/quality/private/python/mypy_entry_point.py b/quality/private/python/mypy_entry_point.py index 8248700..5e0624e 100644 --- a/quality/private/python/mypy_entry_point.py +++ b/quality/private/python/mypy_entry_point.py @@ -9,3 +9,5 @@ if __name__ == "__main__": from mypy import __main__ # type: ignore[import-untyped] + + __main__.console_entry() From 961a35d57738128368feed956888c3bc3356924f Mon Sep 17 00:00:00 2001 From: Vitor Sorpile Geraldo Date: Wed, 15 Jan 2025 17:43:18 +0100 Subject: [PATCH 36/74] [python] Move tool runners common code to python_tool_common Currently, there is some duplicated code between the tool runners, so this commit splits the tool runners behavior in three functions (get_command, output_parser and exception_handler) and adds a function that handles the interaction between them. --- quality/private/python/tools/black_runner.py | 42 +++--- quality/private/python/tools/isort_runner.py | 82 +++++------ quality/private/python/tools/mypy_runner.py | 76 ++++------ quality/private/python/tools/pylint_runner.py | 88 +++++------ .../python/tools/python_tool_common.py | 35 +++++ .../private/python/tools/ruff_check_runner.py | 111 +++++++------- .../python/tools/ruff_format_runner.py | 108 +++++++------- .../python/tools/test/test_black_runner.py | 101 ++++--------- .../python/tools/test/test_isort_runner.py | 138 ++++++++---------- .../python/tools/test/test_pylint_runner.py | 121 +++++++-------- 10 files changed, 410 insertions(+), 492 deletions(-) diff --git a/quality/private/python/tools/black_runner.py b/quality/private/python/tools/black_runner.py index 494addd..ca409c4 100644 --- a/quality/private/python/tools/black_runner.py +++ b/quality/private/python/tools/black_runner.py @@ -1,7 +1,7 @@ """A runner that interfaces python tool aspect and runs black on a list of files.""" -import logging import pathlib +import typing as t from quality.private.python.tools import python_tool_common @@ -19,17 +19,8 @@ def _removeprefix(text: str, prefix: str) -> str: return text -def check_with_black(aspect_arguments: python_tool_common.AspectArguments) -> None: - """Run a black subprocess, check its output and write its findings to a file. - - :param aspect_arguments: - The arguments received from the python_tool_aspect and already processed by - python_tool_common module. - :raises LinterFindingAsError: - If black finds at least one file to be formatted. - """ - - findings = python_tool_common.Findings() +def get_black_command(aspect_arguments: python_tool_common.AspectArguments) -> t.List[str]: + """Returns the command to run a black subprocess.""" subprocess_list = [ f"{aspect_arguments.tool}", @@ -41,9 +32,15 @@ def check_with_black(aspect_arguments: python_tool_common.AspectArguments) -> No if aspect_arguments.refactor: subprocess_list.remove("--diff") - black_output = python_tool_common.execute_subprocess(subprocess_list) + return subprocess_list + + +def black_output_parser(tool_output: python_tool_common.SubprocessInfo) -> python_tool_common.Findings: + """Parses `tool_output` to get the findings returned from the tool execution.""" - for line in black_output.stderr.splitlines(): + findings = python_tool_common.Findings() + + for line in tool_output.stderr.splitlines(): if line.startswith(WOULD_REFORMAT_MSG): file = _removeprefix(line, WOULD_REFORMAT_MSG) findings += [ @@ -56,20 +53,15 @@ def check_with_black(aspect_arguments: python_tool_common.AspectArguments) -> No ) ] - aspect_arguments.tool_output.write_text(str(findings)) - if findings: - logging.info("Created black output at: %s", aspect_arguments.tool_output) - raise python_tool_common.LinterFindingAsError(findings=findings) + return findings def main(): - """Interfaces python tool aspect and use black to check a given set of files.""" - - args = python_tool_common.parse_args() - - logging.basicConfig(level=logging.DEBUG) - - check_with_black(aspect_arguments=args) + """Main entry point.""" + python_tool_common.execute_runner( + get_command=get_black_command, + output_parser=black_output_parser, + ) if __name__ == "__main__": # pragma: no cover diff --git a/quality/private/python/tools/isort_runner.py b/quality/private/python/tools/isort_runner.py index ff90f60..41edf3e 100644 --- a/quality/private/python/tools/isort_runner.py +++ b/quality/private/python/tools/isort_runner.py @@ -1,7 +1,7 @@ """A runner that interfaces python tool aspect and runs isort on a list of files.""" -import logging import pathlib +import typing as t from quality.private.python.tools import python_tool_common @@ -9,43 +9,29 @@ ISORT_ERROR_MSG = "ERROR: " -def check_with_isort(aspect_arguments: python_tool_common.AspectArguments) -> None: - """Run a isort subprocess, check its output and write its findings to a file. +def get_isort_command(aspect_arguments: python_tool_common.AspectArguments) -> t.List[str]: + """Returns the command to run an isort subprocess.""" + subprocess_list = [ + f"{aspect_arguments.tool}", + "--sp", + f"{aspect_arguments.tool_config}", + "--src", + f"{pathlib.Path.cwd()}", + "--", + *map(str, aspect_arguments.target_files), + ] - :param aspect_arguments: - The arguments received from the python_tool_aspect and already processed by - python_tool_common module. - :raises LinterFindingAsError: - If isort finds a file to be formatted. - """ - findings = python_tool_common.Findings() + if not aspect_arguments.refactor: + subprocess_list[1:1] = ["--check-only", "--diff"] - try: - subprocess_list = [ - f"{aspect_arguments.tool}", - "--sp", - f"{aspect_arguments.tool_config}", - "--src", - f"{pathlib.Path.cwd()}", - "--", - *map(str, aspect_arguments.target_files), - ] - - if not aspect_arguments.refactor: - subprocess_list[1:1] = ["--check-only", "--diff"] - - isort_output = python_tool_common.execute_subprocess(subprocess_list) - except python_tool_common.LinterSubprocessError as exception: - if exception.return_code != ISORT_BAD_CHECK_ERROR_CODE: - raise exception - - isort_output = python_tool_common.SubprocessInfo( - exception.stdout, - exception.stderr, - exception.return_code, - ) + return subprocess_list - for line in isort_output.stderr.splitlines(): + +def isort_output_parser(tool_output: python_tool_common.SubprocessInfo) -> python_tool_common.Findings: + """Parses `tool_output` to get the findings returned from the tool execution.""" + findings = python_tool_common.Findings() + + for line in tool_output.stderr.splitlines(): file = line.lstrip(ISORT_ERROR_MSG).split(" ")[0] findings.append( python_tool_common.Finding( @@ -57,20 +43,28 @@ def check_with_isort(aspect_arguments: python_tool_common.AspectArguments) -> No ) ) - aspect_arguments.tool_output.write_text(str(findings)) - if findings: - logging.info("Created isort output at: %s", aspect_arguments.tool_output) - raise python_tool_common.LinterFindingAsError(findings=findings) + return findings -def main(): - """Interfaces python tool aspect and use isort to check a given set of files.""" +def isort_exception_handler(exception: python_tool_common.LinterSubprocessError) -> python_tool_common.SubprocessInfo: + """Handles the cases that isort has a non-zero return code.""" + if exception.return_code != ISORT_BAD_CHECK_ERROR_CODE: + raise exception - args = python_tool_common.parse_args() + return python_tool_common.SubprocessInfo( + exception.stdout, + exception.stderr, + exception.return_code, + ) - logging.basicConfig(level=logging.DEBUG) - check_with_isort(aspect_arguments=args) +def main(): + """Main entry point.""" + python_tool_common.execute_runner( + get_command=get_isort_command, + output_parser=isort_output_parser, + exception_handler=isort_exception_handler, + ) if __name__ == "__main__": # pragma: no cover diff --git a/quality/private/python/tools/mypy_runner.py b/quality/private/python/tools/mypy_runner.py index 816a162..108e9fd 100644 --- a/quality/private/python/tools/mypy_runner.py +++ b/quality/private/python/tools/mypy_runner.py @@ -1,48 +1,29 @@ """A runner that interfaces python tool aspect and runs mypy on a list of files.""" -import logging import pathlib +import typing as t from quality.private.python.tools import python_tool_common MYPY_BAD_CHECK_ERROR_CODE = 1 -def check_with_mypy(aspect_arguments: python_tool_common.AspectArguments) -> None: - """Run a mypy subprocess, check its output and write its findings to a file. +def get_mypy_command(aspect_arguments: python_tool_common.AspectArguments) -> t.List[str]: + """Returns the command to run mypy on a subprocess.""" + return [ + f"{aspect_arguments.tool}", + "--config-file", + f"{aspect_arguments.tool_config}", + "--show-column-numbers", + *map(str, aspect_arguments.target_files), + ] - :param aspect_arguments: - The arguments received from the python_tool_aspect and already processed by - python_tool_common module. - :raises LinterFindingAsError: - If mypy finds a file to be formatted. - """ - findings = python_tool_common.Findings() - - try: - mypy_output = python_tool_common.execute_subprocess( - [ - f"{aspect_arguments.tool}", - "--config-file", - f"{aspect_arguments.tool_config}", - "--show-column-numbers", - *map(str, aspect_arguments.target_files), - ], - ) - except python_tool_common.LinterSubprocessError as exception: - if exception.return_code != MYPY_BAD_CHECK_ERROR_CODE: - raise exception - - mypy_output = python_tool_common.SubprocessInfo( - exception.stdout, - exception.stderr, - exception.return_code, - ) +def mypy_output_parser(tool_output: python_tool_common.SubprocessInfo) -> python_tool_common.Findings: + """Parses `tool_output` to get the findings returned from the tool execution.""" - # The last line of mypy's stdout does not contain any finding. Instead, it - # contains information about how many files are ok and how many are have issues. - issues = mypy_output.stdout.splitlines()[:-1] + findings = python_tool_common.Findings() + issues = tool_output.stdout.splitlines()[:-1] for issue in issues: path, line, column, _, message_and_rule = issue.split(":", 4) @@ -59,22 +40,29 @@ def check_with_mypy(aspect_arguments: python_tool_common.AspectArguments) -> Non ) ) - aspect_arguments.tool_output.write_text(str(findings)) - - if findings: - logging.info("Created mypy output at: %s", aspect_arguments.tool_output) - raise python_tool_common.LinterFindingAsError(findings=findings) + return findings -def main(): - """Interfaces python tool aspect and use mypy to check a given set of files.""" +def mypy_exception_handler(exception: python_tool_common.LinterSubprocessError) -> python_tool_common.SubprocessInfo: + """Handles the cases that mypy has a non-zero return code.""" + if exception.return_code != MYPY_BAD_CHECK_ERROR_CODE: + raise exception - args = python_tool_common.parse_args() + return python_tool_common.SubprocessInfo( + exception.stdout, + exception.stderr, + exception.return_code, + ) - logging.basicConfig(level=logging.DEBUG) - check_with_mypy(aspect_arguments=args) +def main(): + """Main entry point.""" + python_tool_common.execute_runner( + get_command=get_mypy_command, + output_parser=mypy_output_parser, + exception_handler=mypy_exception_handler, + ) -if __name__ == "__main__": +if __name__ == "__main__": # pragma: no cover main() diff --git a/quality/private/python/tools/pylint_runner.py b/quality/private/python/tools/pylint_runner.py index de0dc66..32ab531 100644 --- a/quality/private/python/tools/pylint_runner.py +++ b/quality/private/python/tools/pylint_runner.py @@ -1,7 +1,7 @@ """A runner that interfaces python tool aspect and runs pylint on a list of files.""" -import logging import pathlib +import typing as t from quality.private.python.tools import python_tool_common @@ -20,43 +20,35 @@ def is_pylint_critical_error(exception: python_tool_common.LinterSubprocessError return (return_code % 2) == 1 or return_code == 32 or exception.stderr != "" -def check_with_pylint(aspect_arguments: python_tool_common.AspectArguments) -> None: - """Run a pylint subprocess, check its output and write its findings to a file.""" +def get_pylint_command(aspect_arguments: python_tool_common.AspectArguments) -> t.List[str]: + """Returns the command to run a pylint subprocess.""" + + return [ + # Binary executable path. + f"{aspect_arguments.tool}", + # Configuration flag and path. + "--rcfile", + f"{aspect_arguments.tool_config}", + # Text content template. + "--msg-template", + "{path}:{line}:{column}:{msg}:{symbol}", + # Exclude pylint persistent output as this would be both action input and output. + "--persistent", + "no", + "--score", + "no", + # Files to lint + "--", + *map(str, aspect_arguments.target_files), + ] + + +def pylint_output_parser(tool_output: python_tool_common.SubprocessInfo) -> python_tool_common.Findings: + """Parses `tool_output` to get the findings returned from the tool execution.""" findings = python_tool_common.Findings() - try: - pylint_output = python_tool_common.execute_subprocess( - [ - # Binary executable path. - f"{aspect_arguments.tool}", - # Configuration flag and path. - "--rcfile", - f"{aspect_arguments.tool_config}", - # Text content template. - "--msg-template", - "{path}:{line}:{column}:{msg}:{symbol}", - # Exclude pylint persistent output as this would be both action input and output. - "--persistent", - "no", - "--score", - "no", - # Files to lint - "--", - *map(str, aspect_arguments.target_files), - ], - ) - except python_tool_common.LinterSubprocessError as exception: - if is_pylint_critical_error(exception): - raise exception - - pylint_output = python_tool_common.SubprocessInfo( - exception.stdout, - exception.stderr, - exception.return_code, - ) - - for output_line in pylint_output.stdout.splitlines(): + for output_line in tool_output.stdout.splitlines(): if output_line.startswith(PYLINT_MODULE_START_MSG): continue @@ -74,20 +66,28 @@ def check_with_pylint(aspect_arguments: python_tool_common.AspectArguments) -> N ) ) - aspect_arguments.tool_output.write_text(str(findings)) - if findings: - logging.info("Created pylint output at: %s", aspect_arguments.tool_output) - raise python_tool_common.LinterFindingAsError(findings=findings) + return findings -def main(): - """Interfaces python tool aspect and use pylint to check a given set of files.""" +def pylint_exception_handler(exception: python_tool_common.LinterSubprocessError) -> python_tool_common.SubprocessInfo: + """Handles the cases that pylint has a non-zero return code.""" + if is_pylint_critical_error(exception): + raise exception - args = python_tool_common.parse_args() + return python_tool_common.SubprocessInfo( + exception.stdout, + exception.stderr, + exception.return_code, + ) - logging.basicConfig(level=logging.DEBUG) - check_with_pylint(aspect_arguments=args) +def main(): + """Main entry point.""" + python_tool_common.execute_runner( + get_command=get_pylint_command, + output_parser=pylint_output_parser, + exception_handler=pylint_exception_handler, + ) if __name__ == "__main__": # pragma: no cover diff --git a/quality/private/python/tools/python_tool_common.py b/quality/private/python/tools/python_tool_common.py index d3d81eb..c959c73 100644 --- a/quality/private/python/tools/python_tool_common.py +++ b/quality/private/python/tools/python_tool_common.py @@ -5,6 +5,7 @@ import enum import itertools import json +import logging import os import pathlib import subprocess @@ -269,3 +270,37 @@ def parse_args() -> AspectArguments: ) return AspectArguments(**vars(parser.parse_args())) + + +def execute_runner( + get_command: t.Callable, output_parser: t.Callable, exception_handler: t.Optional[t.Callable] = None +) -> None: + """Handles running the tool subprocess, checking its return and outputing the findings. + + :param get_command: + Function that returns the command to run the tool. + :param output_parser: + Function that receives SubprocessInfo, process it and returns Findings. + :param exception_handler: + Function that handles the cases that the tool has a non-zero return code. + """ + args = parse_args() + + tool_name = args.tool_output.name[: args.tool_output.name.find("_output")] + subprocess_list = get_command(args) + + try: + tool_output = execute_subprocess(subprocess_list) + + except LinterSubprocessError as exception: + if exception_handler: + tool_output = exception_handler(exception) + else: + raise exception + + findings = output_parser(tool_output) + + args.tool_output.write_text(str(findings), encoding="utf-8") + if findings: + logging.info("Created %s output at: %s", tool_name, args.tool_output) + raise LinterFindingAsError(findings=findings) diff --git a/quality/private/python/tools/ruff_check_runner.py b/quality/private/python/tools/ruff_check_runner.py index e0a9199..cbaaa7c 100644 --- a/quality/private/python/tools/ruff_check_runner.py +++ b/quality/private/python/tools/ruff_check_runner.py @@ -1,78 +1,75 @@ """A runner that interfaces python tool aspect and runs ruff on a list of files.""" import json -import logging +import typing as t from quality.private.python.tools import python_tool_common RUFF_BAD_CHECK_ERROR_CODE = 1 -def check_with_ruff(aspect_arguments: python_tool_common.AspectArguments) -> None: - """Run a ruff check subprocess, check its output and write its findings to a file. - - :param aspect_arguments: - The arguments received from the python_tool_aspect and already processed by - python_tool_common module. - :raises LinterFindingAsError: - If ruff finds a file to be formatted. - :exit codes: - 0 if no violations were found, or if all present violations were fixed - automatically. - 1 if violations were found. - 2 if Ruff terminates abnormally due to invalid configuration, invalid CLI options, - or an internal error. - """ +def get_ruff_check_command(aspect_arguments: python_tool_common.AspectArguments) -> t.List[str]: + """Returns the command to run a ruff check subprocess.""" + subprocess_list = [ + f"{aspect_arguments.tool}", + "check", + "--config", + f"{aspect_arguments.tool_config}", + "--fix", + "--output-format", + "json", + *map(str, aspect_arguments.target_files), + ] + + if not aspect_arguments.refactor: + subprocess_list.remove("--fix") + subprocess_list[4:4] = ["--unsafe-fixes"] + + return subprocess_list + + +def ruff_check_output_parser(tool_output: python_tool_common.SubprocessInfo) -> python_tool_common.Findings: + """Parses `tool_output` to get the findings returned from the tool execution.""" findings = python_tool_common.Findings() - try: - subprocess_list = [ - f"{aspect_arguments.tool}", - "check", - "--config", - f"{aspect_arguments.tool_config}", - "--fix", - "--output-format", - "json", - *map(str, aspect_arguments.target_files), - ] - if not aspect_arguments.refactor: - subprocess_list.remove("--fix") - subprocess_list[4:4] = ["--unsafe-fixes"] - - python_tool_common.execute_subprocess(subprocess_list) - except python_tool_common.LinterSubprocessError as exception: - if exception.return_code != RUFF_BAD_CHECK_ERROR_CODE: - raise exception - - for finding in json.loads(exception.stdout): - findings.append( - python_tool_common.Finding( - path=finding["filename"], - message=finding["message"], - severity=python_tool_common.Severity.WARN, - tool="ruff_check", - rule_id=finding["code"], - line=finding["location"]["row"], - column=finding["location"]["column"], - ) + for finding in json.loads(tool_output.stdout): + findings.append( + python_tool_common.Finding( + path=finding["filename"], + message=finding["message"], + severity=python_tool_common.Severity.WARN, + tool="ruff_check", + rule_id=finding["code"], + line=finding["location"]["row"], + column=finding["location"]["column"], ) + ) - aspect_arguments.tool_output.write_text(str(findings)) - if findings: - logging.info("Created ruff check output at: %s", aspect_arguments.tool_output) - raise python_tool_common.LinterFindingAsError(findings=findings) + return findings -def main(): - """Interfaces python tool aspect and use ruff to check a given set of files.""" +def ruff_check_exception_handler( + exception: python_tool_common.LinterSubprocessError, +) -> python_tool_common.SubprocessInfo: + """Handles the cases that ruff_check has a non-zero return code.""" + if exception.return_code != RUFF_BAD_CHECK_ERROR_CODE: + raise exception - args = python_tool_common.parse_args() + return python_tool_common.SubprocessInfo( + exception.stdout, + exception.stderr, + exception.return_code, + ) - logging.basicConfig(level=logging.DEBUG) - check_with_ruff(aspect_arguments=args) +def main(): + """Main entry point.""" + python_tool_common.execute_runner( + get_command=get_ruff_check_command, + output_parser=ruff_check_output_parser, + exception_handler=ruff_check_exception_handler, + ) -if __name__ == "__main__": +if __name__ == "__main__": # pragma: no cover main() diff --git a/quality/private/python/tools/ruff_format_runner.py b/quality/private/python/tools/ruff_format_runner.py index b6c77a2..daf9d32 100644 --- a/quality/private/python/tools/ruff_format_runner.py +++ b/quality/private/python/tools/ruff_format_runner.py @@ -1,81 +1,71 @@ """A runner that interfaces python tool aspect and runs ruff on a list of files.""" -import logging import pathlib +import typing as t from quality.private.python.tools import python_tool_common RUFF_BAD_CHECK_ERROR_CODE = 1 -def format_with_ruff(aspect_arguments: python_tool_common.AspectArguments) -> None: - """Run a ruff format subprocess, check its output and write its findings to a file. - - :param aspect_arguments: - The arguments received from the python_tool_aspect and already processed by - python_tool_common module. - :raises LinterFindingAsError: - If ruff finds a file to be formatted. - :exit codes: - 0 if Ruff terminates successfully, and no files would be formatted if --check - were not specified. - 1 if Ruff terminates successfully, and one or more files would be formatted if - --check were not specified. - 2 if Ruff terminates abnormally due to invalid configuration, invalid CLI options, - or an internal error. - """ +def get_ruff_format_command(aspect_arguments: python_tool_common.AspectArguments) -> t.List[str]: + """Returns the command to run a ruff format subprocess.""" + + subprocess_list = [ + f"{aspect_arguments.tool}", + "format", + "--config", + f"{aspect_arguments.tool_config}", + *map(str, aspect_arguments.target_files), + ] + if not aspect_arguments.refactor: + subprocess_list[4:4] = ["--diff"] + + return subprocess_list + + +def ruff_format_output_parser(tool_output: python_tool_common.SubprocessInfo) -> python_tool_common.Findings: + """Parses `tool_output` to get the findings returned from the tool execution.""" findings = python_tool_common.Findings() - try: - subprocess_list = [ - f"{aspect_arguments.tool}", - "format", - "--config", - f"{aspect_arguments.tool_config}", - *map(str, aspect_arguments.target_files), - ] - if not aspect_arguments.refactor: - subprocess_list[4:4] = ["--diff"] - - ruff_output = python_tool_common.execute_subprocess(subprocess_list) - except python_tool_common.LinterSubprocessError as exception: - if exception.return_code != RUFF_BAD_CHECK_ERROR_CODE: - raise exception - - ruff_output = python_tool_common.SubprocessInfo( - exception.stdout, - exception.stderr, - exception.return_code, - ) + files = {file.lstrip("-").strip() for file in tool_output.stdout.splitlines() if file.startswith("---")} - files = {file.lstrip("-").strip() for file in ruff_output.stdout.splitlines() if file.startswith("---")} - - for file in files: - findings.append( - python_tool_common.Finding( - path=pathlib.Path(file), - message="Should be reformatted.", - severity=python_tool_common.Severity.WARN, - tool="ruff_format", - rule_id="formatting", - ) + for file in files: + findings.append( + python_tool_common.Finding( + path=pathlib.Path(file), + message="Should be reformatted.", + severity=python_tool_common.Severity.WARN, + tool="ruff_format", + rule_id="formatting", ) + ) - aspect_arguments.tool_output.write_text(str(findings)) - if findings: - logging.info("Created ruff format output at: %s", aspect_arguments.tool_output) - raise python_tool_common.LinterFindingAsError(findings=findings) + return findings -def main(): - """Interfaces python tool aspect and use ruff to check a given set of files.""" +def ruff_format_exception_handler( + exception: python_tool_common.LinterSubprocessError, +) -> python_tool_common.SubprocessInfo: + """Handles the cases that ruff_format has a non-zero return code.""" + if exception.return_code != RUFF_BAD_CHECK_ERROR_CODE: + raise exception - args = python_tool_common.parse_args() + return python_tool_common.SubprocessInfo( + exception.stdout, + exception.stderr, + exception.return_code, + ) - logging.basicConfig(level=logging.DEBUG) - format_with_ruff(aspect_arguments=args) +def main(): + """Main entry point.""" + python_tool_common.execute_runner( + get_command=get_ruff_format_command, + output_parser=ruff_format_output_parser, + exception_handler=ruff_format_exception_handler, + ) -if __name__ == "__main__": +if __name__ == "__main__": # pragma: no cover main() diff --git a/quality/private/python/tools/test/test_black_runner.py b/quality/private/python/tools/test/test_black_runner.py index 8119cd0..10bf2e6 100644 --- a/quality/private/python/tools/test/test_black_runner.py +++ b/quality/private/python/tools/test/test_black_runner.py @@ -28,96 +28,49 @@ def setUp(self) -> None: def tearDown(self) -> None: pathlib.Path(self.tmp_file_path).unlink() - @patch( - "quality.private.python.tools.python_tool_common.execute_subprocess", - side_effect=[ + def test_black_output_parser_with_no_issues(self) -> None: + """Tests black_output_parser function with the results of a file with no issues.""" + expected_findings = python_tool_common.Findings() + findings = black_runner.black_output_parser( python_tool_common.SubprocessInfo( stdout="", stderr="", return_code=0, ) - ], - ) - def test_black_output_with_no_issues(self, _) -> None: - """Tests check_with_black function with the results of a file with no issues.""" - black_runner.check_with_black(self.aspect_args) + ) - self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) + self.assertEqual(expected_findings, findings) - @patch( - "quality.private.python.tools.python_tool_common.execute_subprocess", - side_effect=[ - python_tool_common.SubprocessInfo( - stdout="", - stderr="", - return_code=0, - ) - ], - ) - def test_black_output_with_refactor(self, execute_subprocess) -> None: - """Tests check_with_black function with the refactor being true.""" + def test_get_black_command_with_refactor(self) -> None: + """Tests get_black_command function with the refactor being true.""" self.aspect_args.refactor = True - black_runner.check_with_black(self.aspect_args) - - self.assertFalse("--diff" in execute_subprocess.call_args.args[0]) + command = black_runner.get_black_command(self.aspect_args) + + self.assertFalse("--diff" in command) + + def test_black_output_parser_with_issues(self) -> None: + """Tests black_output_parser function with results of files with issues.""" + expected_findings = python_tool_common.Findings( + [ + python_tool_common.Finding( + path=pathlib.Path("file.py"), + message="Should be reformatted.", + severity=python_tool_common.Severity.WARN, + tool="black", + rule_id="formatting", + ) + ] + ) - @patch( - "quality.private.python.tools.python_tool_common.execute_subprocess", - side_effect=[ + findings = black_runner.black_output_parser( python_tool_common.SubprocessInfo( stdout="", stderr="--- file.py\nwould reformat file.py", return_code=0, ) - ], - ) - def test_black_output_with_issues(self, _) -> None: - """Tests check_with_black function with results of files with issues.""" - expected_error_message = ( - "\nThe following findings were found:\n file.py: Should be reformatted. [black:formatting]\n" ) - exepected_output_message = " file.py: Should be reformatted. [black:formatting]" - expected_output_log_message = f"INFO:root:Created black output at: {self.tmp_file_path}" - findings = python_tool_common.Findings() - findings += [ - python_tool_common.Finding( - path=pathlib.Path("file.py"), - message="Should be reformatted.", - severity=python_tool_common.Severity.WARN, - tool="black", - rule_id="formatting", - ) - ] - with self.assertRaises(python_tool_common.LinterFindingAsError) as exception: - with self.assertLogs() as logs: - black_runner.check_with_black(self.aspect_args) - - output_log_message = logs.output[0] - output_file_content = self.aspect_args.tool_output.read_text(encoding="utf-8") - self.assertEqual(expected_error_message, str(exception.exception)) - self.assertEqual(exepected_output_message, output_file_content) - self.assertEqual(expected_output_log_message, output_log_message) - - @patch( - "quality.private.python.tools.python_tool_common.execute_subprocess", - side_effect=[ - python_tool_common.LinterSubprocessError( - commands=[], - stdout="", - stderr="1 file would be reformatted.", - return_code=1, - ) - ], - ) - def test_check_with_black_unexpected_exception(self, _) -> None: - """Test check_with_black function when an unexpected exception occurs.""" - with self.assertRaises(python_tool_common.LinterSubprocessError) as exception: - with self.assertLogs() as logs: - black_runner.check_with_black(self.aspect_args) - self.assertEqual(exception.exception.return_code, 1) - self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) - self.assertFalse(logs.output) + self.assertEqual(expected_findings, findings) @patch( "quality.private.python.tools.python_tool_common.execute_subprocess", diff --git a/quality/private/python/tools/test/test_isort_runner.py b/quality/private/python/tools/test/test_isort_runner.py index e40a853..cb078c9 100644 --- a/quality/private/python/tools/test/test_isort_runner.py +++ b/quality/private/python/tools/test/test_isort_runner.py @@ -28,109 +28,89 @@ def setUp(self) -> None: def tearDown(self) -> None: pathlib.Path(self.tmp_file_path).unlink() - @patch( - "quality.private.python.tools.python_tool_common.execute_subprocess", - side_effect=[ + def test_isort_output_with_no_issues(self) -> None: + """Tests isort_output_parser function with the results of a file with no issues.""" + expected_findings = python_tool_common.Findings() + + findings = isort_runner.isort_output_parser( python_tool_common.SubprocessInfo( stdout="", stderr="", return_code=0, ) - ], - ) - def test_isort_output_with_no_issues(self, _) -> None: - """Tests check_with_isort function with the results of a file with no issues.""" - isort_runner.check_with_isort(self.aspect_args) + ) - self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) + self.assertEqual(expected_findings, findings) - @patch( - "quality.private.python.tools.python_tool_common.execute_subprocess", - side_effect=[ - python_tool_common.SubprocessInfo( - stdout="", - stderr="", - return_code=0, - ) - ], - ) - def test_black_output_with_refactor(self, execute_subprocess) -> None: - """Tests check_with_isort function with the refactor being true.""" + def test_get_isort_command_with_refactor(self) -> None: + """Tests get_isort_command function with the refactor being true.""" self.aspect_args.refactor = True - isort_runner.check_with_isort(self.aspect_args) - - self.assertFalse("--check-only" in execute_subprocess.call_args.args[0]) - self.assertFalse("--diff" in execute_subprocess.call_args.args[0]) + command = isort_runner.get_isort_command(self.aspect_args) + + self.assertFalse("--check-only" in command) + self.assertFalse("--diff" in command) + + def test_isort_output_with_issues(self) -> None: + """Tests isort_output_parser function with the results of a file with issues.""" + expected_findings = python_tool_common.Findings( + [ + python_tool_common.Finding( + path=pathlib.Path("file.py"), + message="Imports are incorrectly sorted and/or formatted.", + severity=python_tool_common.Severity.WARN, + tool="isort", + rule_id="imports_formatting", + ) + ] + ) - @patch( - "quality.private.python.tools.python_tool_common.execute_subprocess", - side_effect=[ + findings = isort_runner.isort_output_parser( python_tool_common.SubprocessInfo( - stdout="", - stderr="", - return_code=0, - ) - ], - ) - def test_black_output_without_refactor(self, execute_subprocess) -> None: - """Tests check_with_isort function without the refactor being true.""" - self.aspect_args.refactor = False - isort_runner.check_with_isort(self.aspect_args) - - self.assertTrue("--check-only" in execute_subprocess.call_args.args[0]) - self.assertTrue("--diff" in execute_subprocess.call_args.args[0]) - - @patch( - "quality.private.python.tools.python_tool_common.execute_subprocess", - side_effect=[ - python_tool_common.LinterSubprocessError( - commands=[], stdout="", stderr="ERROR: file.py Imports are incorrectly sorted and/or formatted.\n", return_code=1, ) - ], - ) - def test_isort_output_with_issues(self, _) -> None: - """Tests check_with_isort function with the results of a file with issues.""" - expected_error_message = "file.py: Imports are incorrectly sorted and/or formatted. [isort:imports_formatting]" - expected_output_log_message = f"INFO:root:Created isort output at: {self.tmp_file_path}" - self.aspect_args.refactor = True - - with self.assertRaises(python_tool_common.LinterFindingAsError) as exception: - with self.assertLogs() as logs: - isort_runner.check_with_isort(self.aspect_args) + ) - findings_as_error_message = str(exception.exception.findings) - output_file_content = self.aspect_args.tool_output.read_text(encoding="utf-8") - output_log_message = logs.output[0] + self.assertEqual(expected_findings, findings) - self.assertEqual(expected_error_message, findings_as_error_message) - self.assertEqual(expected_error_message, output_file_content) - self.assertEqual(expected_output_log_message, output_log_message) - - @patch( - "quality.private.python.tools.python_tool_common.execute_subprocess", - side_effect=[ - python_tool_common.LinterSubprocessError( - commands=[], - stdout="", - stderr="ERROR: unexpected error\n", - return_code=2, - ) - ], - ) - def test_check_with_isort_unexpected_exception(self, _) -> None: - """Test check_with_isort function when an unexpected exception occurs.""" + def test_isort_unexpected_exception(self) -> None: + """Test isort_exception_handler function when an unexpected exception occurs.""" with self.assertRaises(python_tool_common.LinterSubprocessError) as exception: with self.assertLogs() as logs: - isort_runner.check_with_isort(self.aspect_args) + isort_runner.isort_exception_handler( + python_tool_common.LinterSubprocessError( + commands=[], + stdout="", + stderr="ERROR: unexpected error\n", + return_code=2, + ) + ) self.assertEqual(exception.exception.return_code, 2) self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) self.assertFalse(logs.output) + def test_isort_exception_handler(self) -> None: + """Test isort_exception_handler function when an expected exception occurs.""" + expected_info = python_tool_common.SubprocessInfo( + stdout="stdout message", + stderr="stderr message", + return_code=isort_runner.ISORT_BAD_CHECK_ERROR_CODE, + ) + + info = isort_runner.isort_exception_handler( + python_tool_common.LinterSubprocessError( + commands=[], + stdout="stdout message", + stderr="stderr message", + return_code=isort_runner.ISORT_BAD_CHECK_ERROR_CODE, + ) + ) + + self.assertEqual(expected_info, info) + @patch( "quality.private.python.tools.python_tool_common.execute_subprocess", side_effect=[ diff --git a/quality/private/python/tools/test/test_pylint_runner.py b/quality/private/python/tools/test/test_pylint_runner.py index 1a4530a..5b145a3 100644 --- a/quality/private/python/tools/test/test_pylint_runner.py +++ b/quality/private/python/tools/test/test_pylint_runner.py @@ -1,6 +1,5 @@ """Tests for the pylint runner.""" -import os import pathlib import sys import tempfile @@ -29,85 +28,56 @@ def setUp(self) -> None: def tearDown(self) -> None: pathlib.Path(self.tmp_file_path).unlink() - @patch( - "quality.private.python.tools.python_tool_common.execute_subprocess", - side_effect=[ - python_tool_common.SubprocessInfo( - stdout="", - stderr="", - return_code=0, - ) - ], - ) - def test_pylint_output_with_no_issues(self, _) -> None: - """Tests check_with_pylint function with the results of a file with no issues.""" - self.aspect_args.target_imports = set([pathlib.Path("termcolor")]) - pylint_runner.check_with_pylint(self.aspect_args) - self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) + def test_pylint_output_with_no_issues(self) -> None: + """Tests pylint_output_parser function with the results of a file with no issues.""" + expected_findings = python_tool_common.Findings() - @patch( - "quality.private.python.tools.python_tool_common.execute_subprocess", - side_effect=[ + findings = pylint_runner.pylint_output_parser( python_tool_common.SubprocessInfo( stdout="", stderr="", return_code=0, ) - ], - ) - def test_pylint_output_with_no_issues_and_python_path_set(self, _) -> None: - """Tests check_with_pylint function with the results of a file with no issues and PYTHONPATH variable set.""" - modified_environ = os.environ.copy() - modified_environ["PYTHONPATH"] = "python" - with patch.dict(os.environ, modified_environ, clear=True): - self.aspect_args.target_imports = set([pathlib.Path("termcolor")]) - pylint_runner.check_with_pylint(self.aspect_args) - self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) - - @patch( - "quality.private.python.tools.python_tool_common.execute_subprocess", - side_effect=[ - python_tool_common.LinterSubprocessError( - commands=[], + ) + self.assertEqual(expected_findings, findings) + + def test_pylint_output_with_issues(self) -> None: + """Tests pylint_output_parser function with the results of a file with issues.""" + expected_findings = python_tool_common.Findings( + [ + python_tool_common.Finding( + path=pathlib.Path("file.py"), + message="Error message", + severity=python_tool_common.Severity.WARN, + tool="pylint", + rule_id="error-code", + line=11, + column=1, + ) + ] + ) + findings = pylint_runner.pylint_output_parser( + python_tool_common.SubprocessInfo( stdout="************* Module pylint_runner\nfile.py:11:1:Error message:error-code\n", stderr="", return_code=16, ) - ], - ) - def test_pylint_output_with_issues(self, _) -> None: - """Tests check_with_pylint function with the results of a file with issues.""" - expected_error_message = "file.py:11:1: Error message [pylint:error-code]" - expected_output_log_message = f"INFO:root:Created pylint output at: {self.tmp_file_path}" - - with self.assertRaises(python_tool_common.LinterFindingAsError) as exception: - with self.assertLogs() as logs: - pylint_runner.check_with_pylint(self.aspect_args) - - findings_as_error_message = str(exception.exception.findings) - output_file_content = self.aspect_args.tool_output.read_text(encoding="utf-8") - output_log_message = logs.output[0] + ) - self.assertEqual(expected_error_message, findings_as_error_message) - self.assertEqual(expected_error_message, output_file_content) - self.assertEqual(expected_output_log_message, output_log_message) + self.assertEqual(expected_findings, findings) - @patch( - "quality.private.python.tools.python_tool_common.execute_subprocess", - side_effect=[ - python_tool_common.LinterSubprocessError( - commands=[], - stdout="", - stderr="ERROR: unexpected error\n", - return_code=32, - ) - ], - ) - def test_check_with_pylint_unexpected_exception(self, _) -> None: - """Test check_with_pylint function when an unexpected exception occurs.""" + def test_pylint_unexpected_exception(self) -> None: + """Test pylint_exception_handler function when an unexpected exception occurs.""" with self.assertRaises(python_tool_common.LinterSubprocessError) as exception: with self.assertLogs() as logs: - pylint_runner.check_with_pylint(self.aspect_args) + pylint_runner.pylint_exception_handler( + python_tool_common.LinterSubprocessError( + commands=[], + stdout="", + stderr="ERROR: unexpected error\n", + return_code=32, + ) + ) expected_return_code = 32 @@ -115,6 +85,25 @@ def test_check_with_pylint_unexpected_exception(self, _) -> None: self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) self.assertFalse(logs.output) + def test_pylint_exception_handler(self) -> None: + """Test pylint_exception_handler function when an expected exception occurs.""" + expected_info = python_tool_common.SubprocessInfo( + stdout="stdout message", + stderr="", + return_code=2, + ) + + info = pylint_runner.pylint_exception_handler( + python_tool_common.LinterSubprocessError( + commands=[], + stdout="stdout message", + stderr="", + return_code=2, + ) + ) + + self.assertEqual(expected_info, info) + @patch( "quality.private.python.tools.python_tool_common.execute_subprocess", side_effect=[ From ace3ece007883ed6ac8e33ef9d48d74818cc16af Mon Sep 17 00:00:00 2001 From: Vitor Sorpile Geraldo Date: Fri, 24 Jan 2025 12:33:23 +0100 Subject: [PATCH 37/74] [quality] Fix mypy issues on qac/clang_tidy modules In order to fix all issues pointed by mypy, this commit adds missing type hints and refactors code to match the expected types. It also re-enables the qac and clang_tidy modules at the playbook that runs the mypy check on CI. --- quality/private/python/support/pyproject.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/quality/private/python/support/pyproject.toml b/quality/private/python/support/pyproject.toml index af42ad8..c1519f6 100644 --- a/quality/private/python/support/pyproject.toml +++ b/quality/private/python/support/pyproject.toml @@ -5,7 +5,10 @@ line-length = 120 profile = "black" [tool.pylint] -max-line-length=120 +max-line-length = 120 +# Important to ignore no-value-for-parameter warnings for tests +# with decorators that change the function signature +signature-mutators = ["unittest.mock.patch"] [tool.pylint.messages_control] disable = [ From 5c1bf5186c7e07d73c4c00b6ee065fc21f08a839 Mon Sep 17 00:00:00 2001 From: "Yuri.Barboza" Date: Fri, 14 Feb 2025 13:36:56 +0100 Subject: [PATCH 38/74] [python] Add py_pytest rule --- quality/private/python/BUILD | 27 +++++++++- quality/private/python/py_pytest.bzl | 54 +++++++++++++++++++ quality/private/python/python_tool_aspect.bzl | 5 +- quality/private/python/support/pyproject.toml | 51 ++++++++++++++++++ quality/private/python/tools/BUILD | 8 +++ quality/private/python/tools/pytest_runner.py | 13 +++++ 6 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 quality/private/python/py_pytest.bzl create mode 100644 quality/private/python/tools/pytest_runner.py diff --git a/quality/private/python/BUILD b/quality/private/python/BUILD index 06ac570..dd3e529 100644 --- a/quality/private/python/BUILD +++ b/quality/private/python/BUILD @@ -1,4 +1,4 @@ -load("@rules_python//python:defs.bzl", "py_binary") +load("@rules_python//python:defs.bzl", "py_binary", "py_library") load("@swf_bazel_rules_quality_pip_hub//:loaders.bzl", "pkg") py_binary( @@ -8,6 +8,31 @@ py_binary( deps = [pkg("pylint")], ) +py_library( + name = "default_py_test_deps", + visibility = ["//visibility:public"], + deps = [ + pkg("hypothesis"), + pkg("pytest"), + pkg("pytest-asyncio"), + pkg("pytest-benchmark"), + pkg("pytest-bdd"), + pkg("pytest-cov"), + pkg("pytest-instafail"), + pkg("pytest-mock"), + pkg("pytest-rerunfailures"), + pkg("pytest-rich"), + pkg("pytest-timeout"), + pkg("pytest-vcr"), + ], +) + +label_flag( + name = "py_test_deps", + build_setting_default = "@swf_bazel_rules_quality//quality/private/python:default_py_test_deps", + visibility = ["//visibility:public"], +) + py_binary( name = "black_entry_point", srcs = ["black_entry_point.py"], diff --git a/quality/private/python/py_pytest.bzl b/quality/private/python/py_pytest.bzl new file mode 100644 index 0000000..e26dc7b --- /dev/null +++ b/quality/private/python/py_pytest.bzl @@ -0,0 +1,54 @@ +"""Custom py_test rule that is compatible with pytest.""" + +load("@rules_python//python:defs.bzl", "py_test") + +_CONFIG = "@swf_bazel_rules_quality//quality:quality_pytest_config" + +_RUNNER_LABEL = "@swf_bazel_rules_quality//quality/private/python/tools:pytest_runner" +_RUNNER_FILE = "pytest_runner.py" + +_BASE_SRCS = [] +_BASE_DATA = [] +_BASE_DEPS = "@swf_bazel_rules_quality//quality/private/python:py_test_deps" + +def py_pytest( + name, + srcs, + deps = [], + data = [], + env = {}, + *args, + **kwargs): + """Produces a custom py_test target that is compatible with pytest. + + Args: + name: A unique name for this target. + srcs: The list of source (.py) files that are processed to create the target. + deps: The list of other libraries to be linked in to the binary target. + data: Files needed by this rule at runtime. + env: Specifies additional environment variables to set when the target is executed by bazel run. + *args: Arguments inherited from py_test rule. + **kwargs: Keyword arguments inherited from py_test rule. + """ + + if "main" in kwargs: + fail("Error, this rule automatically creates main file in order to enable pytest usage.") + + srcs = depset(srcs + [common_src for common_src in _BASE_SRCS]).to_list() + data = depset(data + [_CONFIG] + [common_data for common_data in _BASE_DATA]).to_list() + deps = depset(deps + [_BASE_DEPS]).to_list() + + env = {key: value for key, value in env.items()} + env.update({"_PYTEST_CONFIG_FILE": "$(location %s)" % Label(_CONFIG)}) + env.update({"_PYTEST_RUNNER_TARGETS": ",".join(["$(location %s)" % src for src in srcs])}) + + py_test( + name = name, + srcs = srcs + [_RUNNER_LABEL], + main = _RUNNER_FILE, + deps = deps, + data = data, + env = env, + *args, + **kwargs + ) diff --git a/quality/private/python/python_tool_aspect.bzl b/quality/private/python/python_tool_aspect.bzl index 4c7089a..e4684fd 100644 --- a/quality/private/python/python_tool_aspect.bzl +++ b/quality/private/python/python_tool_aspect.bzl @@ -5,7 +5,10 @@ load("@swf_bazel_rules_quality//quality/private/python:python_helper.bzl", "is_v load("@swf_bazel_rules_quality//quality/private/python:python_providers.bzl", "PythonCollectInfo", "PythonToolInfo") def _python_tool_config_impl(ctx): - return [PythonToolInfo(config = ctx.file.config, additional_features = ctx.attr.additional_features)] + return [ + DefaultInfo(files = depset([ctx.file.config])), + PythonToolInfo(config = ctx.file.config, additional_features = ctx.attr.additional_features), + ] python_tool_config = rule( implementation = _python_tool_config_impl, diff --git a/quality/private/python/support/pyproject.toml b/quality/private/python/support/pyproject.toml index c1519f6..99bd181 100644 --- a/quality/private/python/support/pyproject.toml +++ b/quality/private/python/support/pyproject.toml @@ -36,3 +36,54 @@ indent-style = "space" skip-magic-trailing-comma = false # Like Black, automatically detect the appropriate line ending. line-ending = "auto" + +[tool.pytest.ini_options] +log_level = "NOTSET" + +addopts = [ + "-vv", + "--strict-markers", + "--strict-config", + "--instafail", + "--color=yes", + "--benchmark-columns=Min,Max,Median" +] + +testpaths = ["tests"] + +required_plugins = [ + "hypothesis", + "pytest-asyncio", + "pytest-benchmark", + "pytest-bdd", + "pytest-instafail", + "pytest-mock", + "pytest-rerunfailures", + "pytest-rich", + "pytest-timeout", + "pytest-vcr" +] + +markers = [ + "windows: Tests which only can run on Windows.", + "linux: Tests which only can run on Linux.", + "ci: Tests which should only run on CI.", + "no_ci: Tests which should not run on CI." +] + +# Makes any test maked with @pytest.mark.xfail fail the test suite if it +# unexpectedly passes. +xfail_strict = true + +# Treat all warnings as errors. +filterwarnings = "error" + +[tool.coverage.run] +# Activate branch coverage analysis. +branch = true + +[tool.coverage.report] +# Fail if coverage is less than 100%. +fail_under = 98 +# List the lines that were not covered by tests. +show_missing = true diff --git a/quality/private/python/tools/BUILD b/quality/private/python/tools/BUILD index 7ecf09e..db1d6e4 100644 --- a/quality/private/python/tools/BUILD +++ b/quality/private/python/tools/BUILD @@ -1,4 +1,12 @@ load("@rules_python//python:defs.bzl", "py_binary", "py_library") +load("@swf_bazel_rules_quality_pip_hub//:loaders.bzl", "pkg") + +py_binary( + name = "pytest_runner", + srcs = ["pytest_runner.py"], + visibility = ["//visibility:public"], + deps = [pkg("pytest")], +) py_library( name = "python_tool_common", diff --git a/quality/private/python/tools/pytest_runner.py b/quality/private/python/tools/pytest_runner.py new file mode 100644 index 0000000..f10d05c --- /dev/null +++ b/quality/private/python/tools/pytest_runner.py @@ -0,0 +1,13 @@ +"""Pytest wrapper.""" + +import os +import sys + +import pytest + +if __name__ == "__main__": + pytest_args = [] + pytest_args.extend(["--config-file", os.environ["_PYTEST_CONFIG_FILE"]]) + pytest_args.extend(os.environ["_PYTEST_RUNNER_TARGETS"].split(",")) + pytest_args.extend(sys.argv[1:]) + sys.exit(pytest.main(pytest_args)) From e01b5064ff9f01e0bcc30c3211d16f94c9c5d2f7 Mon Sep 17 00:00:00 2001 From: "Yuri.Barboza" Date: Fri, 14 Feb 2025 13:37:35 +0100 Subject: [PATCH 39/74] [python] Migrate test targets from py_test to py_pytest --- quality/private/python/tools/test/BUILD | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/quality/private/python/tools/test/BUILD b/quality/private/python/tools/test/BUILD index bbe371d..665a4dc 100644 --- a/quality/private/python/tools/test/BUILD +++ b/quality/private/python/tools/test/BUILD @@ -1,18 +1,18 @@ -load("@rules_python//python:defs.bzl", "py_test") +load("@swf_bazel_rules_quality//quality:defs.bzl", "py_pytest") -py_test( +py_pytest( name = "test_isort_runner", srcs = ["test_isort_runner.py"], deps = ["//quality/private/python/tools:isort"], ) -py_test( +py_pytest( name = "test_pylint_runner", srcs = ["test_pylint_runner.py"], deps = ["//quality/private/python/tools:pylint"], ) -py_test( +py_pytest( name = "test_black_runner", srcs = ["test_black_runner.py"], deps = ["//quality/private/python/tools:black"], From ca8ab373fdde3bac445243c8e872b29b34fafce8 Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Fri, 21 Feb 2025 13:05:15 +0100 Subject: [PATCH 40/74] [pytest] Remove non stable pytest packages While adding tests for python 3.8 and 3.12 a lot of non stable pytest packages failed to comply with both versions. This aims to remove all non stable packages. If the end user wants, it is allowed to add more packages to our custom py_pytest rule as necessary. Link to pytest plugins: https://docs.pytest.org/en/stable/reference/plugin_list.html --- quality/private/python/BUILD | 5 ----- quality/private/python/support/pyproject.toml | 5 ----- 2 files changed, 10 deletions(-) diff --git a/quality/private/python/BUILD b/quality/private/python/BUILD index dd3e529..e2bcc8f 100644 --- a/quality/private/python/BUILD +++ b/quality/private/python/BUILD @@ -12,16 +12,11 @@ py_library( name = "default_py_test_deps", visibility = ["//visibility:public"], deps = [ - pkg("hypothesis"), pkg("pytest"), - pkg("pytest-asyncio"), pkg("pytest-benchmark"), pkg("pytest-bdd"), - pkg("pytest-cov"), - pkg("pytest-instafail"), pkg("pytest-mock"), pkg("pytest-rerunfailures"), - pkg("pytest-rich"), pkg("pytest-timeout"), pkg("pytest-vcr"), ], diff --git a/quality/private/python/support/pyproject.toml b/quality/private/python/support/pyproject.toml index 99bd181..33af4df 100644 --- a/quality/private/python/support/pyproject.toml +++ b/quality/private/python/support/pyproject.toml @@ -44,7 +44,6 @@ addopts = [ "-vv", "--strict-markers", "--strict-config", - "--instafail", "--color=yes", "--benchmark-columns=Min,Max,Median" ] @@ -52,14 +51,10 @@ addopts = [ testpaths = ["tests"] required_plugins = [ - "hypothesis", - "pytest-asyncio", "pytest-benchmark", "pytest-bdd", - "pytest-instafail", "pytest-mock", "pytest-rerunfailures", - "pytest-rich", "pytest-timeout", "pytest-vcr" ] From b99012199aeabc6fc88ce1fc7d0e2356b9255b8d Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Mon, 3 Mar 2025 16:34:26 +0100 Subject: [PATCH 41/74] [python] Add proper typehints to python tools `execute_runner` --- quality/private/python/tools/python_tool_common.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/quality/private/python/tools/python_tool_common.py b/quality/private/python/tools/python_tool_common.py index c959c73..309d00e 100644 --- a/quality/private/python/tools/python_tool_common.py +++ b/quality/private/python/tools/python_tool_common.py @@ -273,7 +273,9 @@ def parse_args() -> AspectArguments: def execute_runner( - get_command: t.Callable, output_parser: t.Callable, exception_handler: t.Optional[t.Callable] = None + get_command: t.Callable[[AspectArguments], t.List[str]], + output_parser: t.Callable[[SubprocessInfo], Findings], + exception_handler: t.Optional[t.Callable[[LinterSubprocessError], SubprocessInfo]] = None, ) -> None: """Handles running the tool subprocess, checking its return and outputing the findings. From 082193ad22e48a9e7855921a655d0f2fb6bcadf4 Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Mon, 3 Mar 2025 16:38:25 +0100 Subject: [PATCH 42/74] [python] Add JSON output to every tool This is needed to ease the a quality-tools metrics template creation for BRQ python tools. The new JSON file will ease file conversion while the the new output file names are going to ease file collection. --- quality/private/python/python_tool_aspect.bzl | 78 ++++++++++--------- .../python/tools/python_tool_common.py | 25 ++++-- 2 files changed, 60 insertions(+), 43 deletions(-) diff --git a/quality/private/python/python_tool_aspect.bzl b/quality/private/python/python_tool_aspect.bzl index e4684fd..431a7f3 100644 --- a/quality/private/python/python_tool_aspect.bzl +++ b/quality/private/python/python_tool_aspect.bzl @@ -30,7 +30,7 @@ The available options are: def _python_tool_aspect_implementation(target, ctx): """Python tool aspect implementation.""" - output = [] + outputs = [] if not PyInfo in target or not is_valid_label(target.label): return [OutputGroupInfo(python_tool_output = depset([]))] @@ -46,41 +46,47 @@ def _python_tool_aspect_implementation(target, ctx): else: sources_to_run.append(source.label.name) - if sources_to_run: - output_file = ctx.actions.declare_file(ctx.executable._runner.basename + "_output_" + target.label.name + ".txt") - output.append(output_file) - - deps = getattr(target[PythonCollectInfo], "deps") - imports = getattr(target[PythonCollectInfo], "imports") - - args = ctx.actions.args() - args.use_param_file("@%s", use_always = True) - args.set_param_file_format("multiline") - - args.add_all("--target-imports", imports, format_each = "%s") - args.add_all("--target-dependencies", deps, format_each = "%s") - args.add_all("--target-files", sources_to_run, format_each = "%s") - args.add("--tool", ctx.executable._tool.path, format = "%s") - args.add("--tool-config", config.path, format = "%s") - args.add("--tool-output", output_file.path, format = "%s") - args.add("--tool-root", ctx.expand_location(ctx.workspace_name), format = "%s") - - file_refactor = "false" - if "refactor" in ctx.features or "refactor" in additional_features: - args.add("--refactor", True, format = "%s") - file_refactor = "true" - - ctx.actions.run( - inputs = depset([config], transitive = [target[DefaultInfo].default_runfiles.files]), - outputs = [output_file], - tools = [ctx.executable._runner, ctx.executable._tool, target[DefaultInfo].files_to_run], - executable = ctx.executable._runner, - arguments = [args], - progress_message = "Running {tool} on: {target_name}".format(tool = ctx.executable._runner.basename, target_name = target.label.name), - execution_requirements = {"no-sandbox": file_refactor}, - ) - - return [OutputGroupInfo(python_tool_output = depset(output))] + if not sources_to_run: + return [OutputGroupInfo(python_tool_output = depset([]))] + + basename = target.label.name + "_" + ctx.executable._runner.basename + ".py_findings" + findings_text_file = ctx.actions.declare_file(basename + ".txt") + outputs.append(findings_text_file) + findings_json_file = ctx.actions.declare_file(basename + ".json") + outputs.append(findings_json_file) + + deps = getattr(target[PythonCollectInfo], "deps") + imports = getattr(target[PythonCollectInfo], "imports") + + args = ctx.actions.args() + args.use_param_file("@%s", use_always = True) + args.set_param_file_format("multiline") + + args.add_all("--target-imports", imports, format_each = "%s") + args.add_all("--target-dependencies", deps, format_each = "%s") + args.add_all("--target-files", sources_to_run, format_each = "%s") + args.add("--tool", ctx.executable._tool.path, format = "%s") + args.add("--tool-config", config.path, format = "%s") + args.add("--tool-output-text", findings_text_file.path, format = "%s") + args.add("--tool-output-json", findings_json_file.path, format = "%s") + args.add("--tool-root", ctx.expand_location(ctx.workspace_name), format = "%s") + + file_refactor = "false" + if "refactor" in ctx.features or "refactor" in additional_features: + args.add("--refactor", True, format = "%s") + file_refactor = "true" + + ctx.actions.run( + inputs = depset([config], transitive = [target[DefaultInfo].default_runfiles.files]), + outputs = outputs, + tools = [ctx.executable._runner, ctx.executable._tool, target[DefaultInfo].files_to_run], + executable = ctx.executable._runner, + arguments = [args], + progress_message = "Running {tool} on: {target_name}".format(tool = ctx.executable._runner.basename, target_name = target.label.name), + execution_requirements = {"no-sandbox": file_refactor}, + ) + + return [OutputGroupInfo(python_tool_output = depset(outputs))] def _python_tool_aspect(tool, runner, config): """Python tool aspect. diff --git a/quality/private/python/tools/python_tool_common.py b/quality/private/python/tools/python_tool_common.py index 309d00e..1af4459 100644 --- a/quality/private/python/tools/python_tool_common.py +++ b/quality/private/python/tools/python_tool_common.py @@ -54,8 +54,12 @@ def __str__(self): class Findings(t.List[Finding]): """Defines a list of findings.""" + def to_text_file(self, file: pathlib.Path) -> None: + """Dumps a list of findings to a .txt file.""" + file.write_text(str(self), encoding="utf-8") + def to_json_file(self, file: pathlib.Path) -> None: - """Dumps a list of findings to a JSON file.""" + """Dumps a list of findings to a .json file.""" file.write_text(json.dumps(self, cls=FindingsJSONEncoder, indent=2), encoding="utf-8") def __str__(self) -> str: @@ -160,7 +164,8 @@ class AspectArguments: # pylint: disable=too-many-instance-attributes target_files: t.Set[pathlib.Path] tool: pathlib.Path tool_config: pathlib.Path - tool_output: pathlib.Path + tool_output_text: pathlib.Path + tool_output_json: pathlib.Path tool_root: str refactor: bool @@ -251,7 +256,13 @@ def parse_args() -> AspectArguments: help="", ) parser.add_argument( - "--tool-output", + "--tool-output-text", + type=pathlib.Path, + required=True, + help="", + ) + parser.add_argument( + "--tool-output-json", type=pathlib.Path, required=True, help="", @@ -288,7 +299,7 @@ def execute_runner( """ args = parse_args() - tool_name = args.tool_output.name[: args.tool_output.name.find("_output")] + tool_name = args.tool.name.split("_entry_point")[0] subprocess_list = get_command(args) try: @@ -302,7 +313,7 @@ def execute_runner( findings = output_parser(tool_output) - args.tool_output.write_text(str(findings), encoding="utf-8") + findings.to_text_file(args.tool_output_text) + findings.to_json_file(args.tool_output_json) if findings: - logging.info("Created %s output at: %s", tool_name, args.tool_output) - raise LinterFindingAsError(findings=findings) + logging.info("Created %s output at %s and %s", tool_name, args.tool_output_text, args.tool_output_json) From 35c242b60b0c6ad8232206542778b2c3117d2088 Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Mon, 3 Mar 2025 16:39:17 +0100 Subject: [PATCH 43/74] [python] Improve `LinterFindingAsError` message --- .../private/python/tools/python_tool_common.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/quality/private/python/tools/python_tool_common.py b/quality/private/python/tools/python_tool_common.py index 1af4459..4d620bb 100644 --- a/quality/private/python/tools/python_tool_common.py +++ b/quality/private/python/tools/python_tool_common.py @@ -5,7 +5,6 @@ import enum import itertools import json -import logging import os import pathlib import subprocess @@ -78,9 +77,12 @@ def __init__(self, path: str, tool: str): class LinterFindingAsError(SystemExit): """Raised when a linter finds a finding treats it as an error.""" - def __init__(self, findings: Findings): + def __init__(self, tool_name: str, findings: Findings, outputs: t.List[pathlib.Path]): self.findings = findings - super().__init__(f"\nThe following findings were found:\n{self.findings}\n") + message = f'\nTool "{tool_name}" found findings and stored them at:\n- ' + message += "\n- ".join([str(output) for output in outputs]) + message += f"\nThe following findings were found:\n{self.findings}\n" + super().__init__(message) class LinterSubprocessError(Exception): @@ -316,4 +318,11 @@ def execute_runner( findings.to_text_file(args.tool_output_text) findings.to_json_file(args.tool_output_json) if findings: - logging.info("Created %s output at %s and %s", tool_name, args.tool_output_text, args.tool_output_json) + raise LinterFindingAsError( + tool_name=tool_name, + findings=findings, + outputs=[ + args.tool_output_text, + args.tool_output_json, + ], + ) From d2729bcc7b1d60b7cb65d20b66aabe6d78c70ff0 Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Mon, 3 Mar 2025 16:39:41 +0100 Subject: [PATCH 44/74] [python] Adequate tests to new JSON output --- .../python/tools/test/test_black_runner.py | 19 +++++++++++----- .../python/tools/test/test_isort_runner.py | 22 +++++++++++++------ .../python/tools/test/test_pylint_runner.py | 22 +++++++++++++------ 3 files changed, 43 insertions(+), 20 deletions(-) diff --git a/quality/private/python/tools/test/test_black_runner.py b/quality/private/python/tools/test/test_black_runner.py index 10bf2e6..2daf480 100644 --- a/quality/private/python/tools/test/test_black_runner.py +++ b/quality/private/python/tools/test/test_black_runner.py @@ -1,5 +1,6 @@ """Tests for the black runner.""" +import json import pathlib import sys import tempfile @@ -13,7 +14,8 @@ class TestBlackRunner(unittest.TestCase): """Test class for black runner.""" def setUp(self) -> None: - _, self.tmp_file_path = tempfile.mkstemp() + self.tmp_text_file_path = pathlib.Path(tempfile.mkstemp()[1]) + self.tmp_json_file_path = pathlib.Path(tempfile.mkstemp()[1]) self.aspect_args = python_tool_common.AspectArguments( target_imports=set(), target_dependencies=set(), @@ -21,12 +23,14 @@ def setUp(self) -> None: tool_config=pathlib.Path(""), tool_root="", target_files=set(), - tool_output=pathlib.Path(self.tmp_file_path), + tool_output_text=self.tmp_text_file_path, + tool_output_json=self.tmp_json_file_path, refactor=False, ) def tearDown(self) -> None: - pathlib.Path(self.tmp_file_path).unlink() + self.tmp_text_file_path.unlink() + self.tmp_json_file_path.unlink() def test_black_output_parser_with_no_issues(self) -> None: """Tests black_output_parser function with the results of a file with no issues.""" @@ -89,14 +93,17 @@ def test_call_main(self, _) -> None: " ", "--tool-config", " ", - "--tool-output", - self.tmp_file_path, + "--tool-output-text", + str(self.tmp_text_file_path), + "--tool-output-json", + str(self.tmp_json_file_path), "--tool-root", " ", ] with patch.object(sys, "argv", ["black"] + mocked_args): black_runner.main() - self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) + self.assertFalse(self.aspect_args.tool_output_text.read_text(encoding="utf-8")) + self.assertFalse(json.loads(self.aspect_args.tool_output_json.read_text(encoding="utf-8"))) if __name__ == "__main__": # pragma: no cover diff --git a/quality/private/python/tools/test/test_isort_runner.py b/quality/private/python/tools/test/test_isort_runner.py index cb078c9..e0d0e97 100644 --- a/quality/private/python/tools/test/test_isort_runner.py +++ b/quality/private/python/tools/test/test_isort_runner.py @@ -1,5 +1,6 @@ """Tests for the isort runner.""" +import json import pathlib import sys import tempfile @@ -13,7 +14,8 @@ class TestIsortRunner(unittest.TestCase): """Test class for isort runner.""" def setUp(self) -> None: - _, self.tmp_file_path = tempfile.mkstemp() + self.tmp_text_file_path = pathlib.Path(tempfile.mkstemp()[1]) + self.tmp_json_file_path = pathlib.Path(tempfile.mkstemp()[1]) self.aspect_args = python_tool_common.AspectArguments( target_imports=set(), target_dependencies=set(), @@ -21,12 +23,14 @@ def setUp(self) -> None: tool_config=pathlib.Path(""), tool_root="", target_files=set(), - tool_output=pathlib.Path(self.tmp_file_path), + tool_output_text=self.tmp_text_file_path, + tool_output_json=self.tmp_json_file_path, refactor=False, ) def tearDown(self) -> None: - pathlib.Path(self.tmp_file_path).unlink() + self.tmp_text_file_path.unlink() + self.tmp_json_file_path.unlink() def test_isort_output_with_no_issues(self) -> None: """Tests isort_output_parser function with the results of a file with no issues.""" @@ -89,7 +93,8 @@ def test_isort_unexpected_exception(self) -> None: ) self.assertEqual(exception.exception.return_code, 2) - self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) + self.assertFalse(self.aspect_args.tool_output_text.read_text(encoding="utf-8")) + self.assertFalse(self.aspect_args.tool_output_json.read_text(encoding="utf-8")) self.assertFalse(logs.output) def test_isort_exception_handler(self) -> None: @@ -128,8 +133,10 @@ def test_call_main(self, _) -> None: " ", "--tool-config", " ", - "--tool-output", - self.tmp_file_path, + "--tool-output-text", + str(self.tmp_text_file_path), + "--tool-output-json", + str(self.tmp_json_file_path), "--tool-root", " ", ] @@ -145,7 +152,8 @@ def test_call_main(self, _) -> None: ], ): isort_runner.main() - self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) + self.assertFalse(self.aspect_args.tool_output_text.read_text(encoding="utf-8")) + self.assertFalse(json.loads(self.aspect_args.tool_output_json.read_text(encoding="utf-8"))) if __name__ == "__main__": # pragma: no cover diff --git a/quality/private/python/tools/test/test_pylint_runner.py b/quality/private/python/tools/test/test_pylint_runner.py index 5b145a3..de338ba 100644 --- a/quality/private/python/tools/test/test_pylint_runner.py +++ b/quality/private/python/tools/test/test_pylint_runner.py @@ -1,5 +1,6 @@ """Tests for the pylint runner.""" +import json import pathlib import sys import tempfile @@ -13,7 +14,8 @@ class TestPylintRunner(unittest.TestCase): """Test class for pylint runner.""" def setUp(self) -> None: - _, self.tmp_file_path = tempfile.mkstemp() + self.tmp_text_file_path = pathlib.Path(tempfile.mkstemp()[1]) + self.tmp_json_file_path = pathlib.Path(tempfile.mkstemp()[1]) self.aspect_args = python_tool_common.AspectArguments( target_imports=set(), target_dependencies=set(), @@ -21,12 +23,14 @@ def setUp(self) -> None: tool_config=pathlib.Path(""), tool_root="", target_files=set(), - tool_output=pathlib.Path(self.tmp_file_path), + tool_output_text=self.tmp_text_file_path, + tool_output_json=self.tmp_json_file_path, refactor=False, ) def tearDown(self) -> None: - pathlib.Path(self.tmp_file_path).unlink() + self.tmp_text_file_path.unlink() + self.tmp_json_file_path.unlink() def test_pylint_output_with_no_issues(self) -> None: """Tests pylint_output_parser function with the results of a file with no issues.""" @@ -82,7 +86,8 @@ def test_pylint_unexpected_exception(self) -> None: expected_return_code = 32 self.assertEqual(exception.exception.return_code, expected_return_code) - self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) + self.assertFalse(self.aspect_args.tool_output_text.read_text(encoding="utf-8")) + self.assertFalse(self.aspect_args.tool_output_json.read_text(encoding="utf-8")) self.assertFalse(logs.output) def test_pylint_exception_handler(self) -> None: @@ -121,14 +126,17 @@ def test_call_main(self, _) -> None: " ", "--tool-config", " ", - "--tool-output", - self.tmp_file_path, + "--tool-output-text", + str(self.tmp_text_file_path), + "--tool-output-json", + str(self.tmp_json_file_path), "--tool-root", " ", ] with patch.object(sys, "argv", ["pylint"] + mocked_args): pylint_runner.main() - self.assertFalse(self.aspect_args.tool_output.read_text(encoding="utf-8")) + self.assertFalse(self.aspect_args.tool_output_text.read_text(encoding="utf-8")) + self.assertFalse(json.loads(self.aspect_args.tool_output_json.read_text(encoding="utf-8"))) if __name__ == "__main__": # pragma: no cover From 2c2b192afb6073f91c88b22f8f0aa7926433cd5b Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Wed, 5 Mar 2025 10:53:19 +0100 Subject: [PATCH 45/74] [python] Set default Finding line and column to 1 This is expected from findings-converter perspectve. --- quality/private/python/tools/python_tool_common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quality/private/python/tools/python_tool_common.py b/quality/private/python/tools/python_tool_common.py index 4d620bb..58fa6cd 100644 --- a/quality/private/python/tools/python_tool_common.py +++ b/quality/private/python/tools/python_tool_common.py @@ -39,8 +39,8 @@ class Finding: severity: Severity tool: str rule_id: str - line: t.Optional[int] = None - column: t.Optional[int] = None + line: int = 1 + column: int = 1 def __str__(self): output = f"{self.path}" From 30f172e5f4ee4245c2284a49e9af07509bfa07e9 Mon Sep 17 00:00:00 2001 From: "Yuri.Barboza" Date: Mon, 17 Feb 2025 19:16:45 +0100 Subject: [PATCH 46/74] [readme] Python module documentation This commit will add a documentation to the python README --- quality/private/python/README.md | 190 ++++++++++++++++++++++++++++++- 1 file changed, 189 insertions(+), 1 deletion(-) diff --git a/quality/private/python/README.md b/quality/private/python/README.md index 23fbca2..b685374 100644 --- a/quality/private/python/README.md +++ b/quality/private/python/README.md @@ -1,3 +1,191 @@ # Python -TBD +This module offers a set of Bazel integrated, fully cacheable and configurable Python quality tools and test frameworks. + +- [Python](#python) + - [Python Tools](#python-tools) + - [How to use](#how-to-use) + - [Basic Usage](#basic-usage) + - [Available Tools](#available-tools) + - [Black](#black) + - [Isort](#isort) + - [Mypy](#mypy) + - [Pylint](#pylint) + - [Ruff-check](#ruff-check) + - [Ruff-format](#ruff-format) + - [Extra Features](#extra-features) + - [Custom Pytest](#custom-pytest) + - [Configuration](#configuration) + - [How to use](#how-to-use-1) + +## Python Tools + +Bazel Rules Quality has multiple Bazel integrated [available tools](#available-tools) to check and refactor our Bazel users Python code. + +### How to use + +Each tool uses the same [default python configuration](quality/BUILD#L58). One can configure each tool separately or, preferably, chose to create a single custom configuration for every tool. +To achieve that, a custom configuration rule must be created using the [`python_tool_config`](quality/defs.bzl#L24) rule. The file referenced by `python_tool_config` can be, for example, a `pyproject.toml` file containing one section for each selected tool. Our [default configuration file](/quality/private/python/support/pyproject.toml) can be used as template. + +```py +# path/to/BUILD +load("@swf_bazel_rules_quality//quality:defs.bzl", "python_tool_config") +filegroup( + name = "pyproject_toml", + srcs = ["pyproject.toml"], + visibility = ["//visibility:public"], +) +python_tool_config( + name = "custom_config", + config = ":pyproject_toml", + visibility = ["//visibility:public"], +) +Finally, each selected tool configuration can be overloaded with the newly created custom configuration target. target overload shortcut can be added to the `.bazelrc` file: + +# .bazelrc +build:pylint --@swf_bazel_rules_quality//quality:quality_pylint_config=//path/to:pylint_config +build:mypy --@swf_bazel_rules_quality//quality:quality_mypy_config=//path/to:python_config +build:black --@swf_bazel_rules_quality//quality:quality_black_config=//path/to:python_config +build:isort --@swf_bazel_rules_quality//quality:quality_isort_config=//path/to:python_config +build:ruff_check --@swf_bazel_rules_quality//quality:quality_ruff_config=//path/to:python_config +build:ruff_format --@swf_bazel_rules_quality//quality:quality_ruff_config=//path/to:python_config +``` + +### Basic Usage + +To run one or more tools, considering that they are correctly configured, you can use the following commands: + +```bash +bazel build --config=pylint --keep_going -- //... +bazel build --config=black --keep_going -- //... +bazel build --config=isort --keep_going -- //... +bazel build --config=ruff_check --keep_going -- //... +bazel build --config=ruff_format --keep_going -- //... +bazel build --config=mypy --keep_going -- //... +``` + +Finally in the `.bazelrc` file we can configure the build for each tool. + +```py +# .bazelrc + +build:pylint --output_groups=python_tool_output +build:pylint --aspects=@swf_bazel_rules_quality//quality:defs.bzl%pylint_aspect + +build:black --output_groups=python_tool_output +build:black --aspects=@swf_bazel_rules_quality//quality:defs.bzl%black_aspect + +build:isort --output_groups=python_tool_output +build:isort --aspects=@swf_bazel_rules_quality//quality:defs.bzl%isort_aspect + +build:mypy --output_groups=python_tool_output +build:mypy --aspects=@swf_bazel_rules_quality//quality:defs.bzl%mypy_aspect + +build:ruff_check --output_groups=python_tool_output +build:ruff_check --aspects=@swf_bazel_rules_quality//quality:defs.bzl%ruff_check_aspect +build:ruff_format --output_groups=python_tool_output +build:ruff_format --aspects=@swf_bazel_rules_quality//quality:defs.bzl%ruff_format_aspect +``` + +### Available Tools + +#### Black + +Black is the uncompromising Python code formatter. By using it, you agree to cede control over minutiae of hand-formatting. + +For more information read [black documentation](https://black.readthedocs.io/en/stable/). + +#### Isort + +Isort is a Python utility / library to sort imports alphabetically and automatically separate into sections and by type. + +For more information read [Isort documentation](https://pycqa.github.io/isort/). + +#### Mypy + +Mypy is a static type checker for Python. Type checkers help ensure that you're using variables and functions in your code correctly. With mypy, add type hints ([PEP 484](https://www.python.org/dev/peps/pep-0484/)) to your Python programs, and mypy will warn you when you use those types incorrectly. + +For more information read [Mypy documentation](https://mypy.readthedocs.io/en/stable/). + +#### Pylint + +Pylint is a static code analyser for Python 2 or 3. Pylint analysis your code without actually running it. It checks for errors, enforces a coding standard, look for code smells, and can make suggestions about how the code could be refactored. + +For more information read [Pylint documentation](https://pylint.readthedocs.io/en/stable/). + +#### Ruff-check + +Ruff is an extremely fast Python linter designed as a drop-in replacement for Flake8 (plus dozens of plugins), isort, pydocstyle, pyupgrade, autoflake, and more. Ruff check is the primary entrypoint to the Ruff linter. + +For more information read [Ruff check documentation](https://docs.astral.sh/ruff/linter/). + +#### Ruff-format + +The Ruff formatter is an extremely fast Python code formatter designed as a drop-in replacement for Black, available as part of the ruff CLI via ruff format. Ruff format is the primary entrypoint to the formatter. + +For more information read [Ruff formatter documentation](https://docs.astral.sh/ruff/formatter/). + +### Extra Features + +Some tools have an extra feature called `refactor`. When used with the CLI command, the tool will fix most findings that have been found in the target file. The tools that have this feature are: `black`, `isort`, `ruff-check` and `ruff-format`. Note that `ruff-check` may not automatically fix all findings. + +To use the `refactor` feature, one must add `--features=refactor` to the command line. The following command shows how to refactor every target using `black`: + +```py +bazel build --config=black --keep_going --features=refactor -- //... +``` + +## Custom Pytest + +Bazel's default [pytest rule](https://bazel.build/reference/be/python#py_test) rule was not designed specifically for [Python's pytest](https://github.com/pytest-dev/pytest). Therefore, one has to do some additional scripting in order to use pytest framework with Python adn Bazel. + +To improve Python coding experience with Bazel, a custom `py_pytest` rule is offered by Bazel Rules Quality. + +### Configuration + +Following the same configuration flow as the [Python tools](#python-tools), it is possible to configure the custom `py_pytest` rule by overriding [quality_pytest_config](/quality/BUILD#L65) label. In a similar fashion, the `py_pytest` default dependencies can also be modified by overriding [py_test_deps](/quality/private/python/BUILD#L30) label. + +To use a custom configuration file, one can add the following command line in the `.bazelrc`: + +```bash +# .bazelrc +build --@swf_bazel_rules_quality//quality:quality_pytest_config=//quality/private/python/support:awesome_pyproject +``` + +To use a custom default dependency list, one can add the following command line in the `.bazelrc`: + +```bash +# .bazelrc +build --@swf_bazel_rules_quality//quality/private/python:py_test_deps=//:py_custom_test_deps +``` + +### How to use + +The custom `py_pytest` rule is basically a drop-in replacement to Bazel's default `pytest` rule. To utilize it, one has to load `py_pytest` and then create a test target with it. For example: + +```bash +# BUILD +load("@swf_bazel_rules_quality//quality:defs.bzl", "py_pytest") + +py_pytest( + name = "test_dummy", + srcs = ["dummy.py"], + deps = ["dummy_lib"], +) +``` + +It's only necessary to import the `pytest` when used in the test file. + +Finally run the bazel command to run the test. + +```bash +bazel test //:test_dummy +``` + +If any of the tests are failing, an error message will be reported in the terminal and/or in an report file. + +There is also a possibility to use test arguments with the bazel command, for example: + +```bash +bazel test //... --test_arg=[argument] +``` From 49cbb3005fa86ef44a12554fbe0fef4ec0ce5085 Mon Sep 17 00:00:00 2001 From: "Yuri.Barboza" Date: Tue, 18 Mar 2025 13:44:58 +0100 Subject: [PATCH 47/74] [python] Fix ruff check runner finding path This commit will fix a bug found in the ruff check runner where the Finding path was supposed of type Path but instead it was of type string. --- quality/private/python/tools/ruff_check_runner.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/quality/private/python/tools/ruff_check_runner.py b/quality/private/python/tools/ruff_check_runner.py index cbaaa7c..9a73286 100644 --- a/quality/private/python/tools/ruff_check_runner.py +++ b/quality/private/python/tools/ruff_check_runner.py @@ -1,6 +1,7 @@ """A runner that interfaces python tool aspect and runs ruff on a list of files.""" import json +import pathlib import typing as t from quality.private.python.tools import python_tool_common @@ -35,7 +36,7 @@ def ruff_check_output_parser(tool_output: python_tool_common.SubprocessInfo) -> for finding in json.loads(tool_output.stdout): findings.append( python_tool_common.Finding( - path=finding["filename"], + path=pathlib.Path(finding["filename"]), message=finding["message"], severity=python_tool_common.Severity.WARN, tool="ruff_check", From efa175b5e0042bbe6779c1df35110baac6b390ef Mon Sep 17 00:00:00 2001 From: "Yuri.Barboza" Date: Tue, 18 Mar 2025 13:47:06 +0100 Subject: [PATCH 48/74] [python] Improve coverage for python tool This commit will add tests for mypy, ruff_check and ruff_format runner, improving the coverage. --- quality/private/python/tools/test/BUILD | 39 +++++ quality/private/python/tools/test/conftest.py | 69 ++++++++ .../python/tools/test/test_black_runner.py | 18 +- .../python/tools/test/test_mypy_runner.py | 114 ++++++++++++ .../tools/test/test_ruff_check_runner.py | 164 ++++++++++++++++++ .../tools/test/test_ruff_format_runner.py | 147 ++++++++++++++++ 6 files changed, 550 insertions(+), 1 deletion(-) create mode 100644 quality/private/python/tools/test/conftest.py create mode 100644 quality/private/python/tools/test/test_mypy_runner.py create mode 100644 quality/private/python/tools/test/test_ruff_check_runner.py create mode 100644 quality/private/python/tools/test/test_ruff_format_runner.py diff --git a/quality/private/python/tools/test/BUILD b/quality/private/python/tools/test/BUILD index 665a4dc..4ad2cee 100644 --- a/quality/private/python/tools/test/BUILD +++ b/quality/private/python/tools/test/BUILD @@ -1,4 +1,6 @@ +load("@rules_python//python:defs.bzl", "py_library") load("@swf_bazel_rules_quality//quality:defs.bzl", "py_pytest") +load("@swf_bazel_rules_quality_pip_hub//:loaders.bzl", "pkg") py_pytest( name = "test_isort_runner", @@ -17,3 +19,40 @@ py_pytest( srcs = ["test_black_runner.py"], deps = ["//quality/private/python/tools:black"], ) + +py_pytest( + name = "test_mypy_runner", + srcs = ["test_mypy_runner.py"], + deps = [ + ":conftest", + "//quality/private/python/tools:mypy", + ], +) + +py_pytest( + name = "test_ruff_check_runner", + srcs = ["test_ruff_check_runner.py"], + deps = [ + ":conftest", + "//quality/private/python/tools:ruff_check", + ], +) + +py_pytest( + name = "test_ruff_format_runner", + srcs = ["test_ruff_format_runner.py"], + deps = [ + ":conftest", + "//quality/private/python/tools:ruff_format", + ], +) + +py_library( + name = "conftest", + srcs = ["conftest.py"], + visibility = ["//visibility:public"], + deps = [ + "@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common", + pkg("pytest"), + ], +) diff --git a/quality/private/python/tools/test/conftest.py b/quality/private/python/tools/test/conftest.py new file mode 100644 index 0000000..296d378 --- /dev/null +++ b/quality/private/python/tools/test/conftest.py @@ -0,0 +1,69 @@ +"""pytest fixtures for the config-generator test""" + +import pathlib +import tempfile + +import pytest + +from quality.private.python.tools import python_tool_common + + +@pytest.fixture(name="subprocess_output", scope="module") +def fixture_subprocessinfo(request) -> python_tool_common.SubprocessInfo: + """Fixture to return a python tool common SubprocessInfo class without errors.""" + stdout, stderr, return_code = request.param + + return python_tool_common.SubprocessInfo( + stdout=stdout, + stderr=stderr, + return_code=return_code, + ) + + +@pytest.fixture(name="linter_subprocess_error", scope="module") +def fixture_linter_subprocesserror(request) -> python_tool_common.LinterSubprocessError: + """Fixture to return a generic python tool common LinterSubprocessError.""" + commands, stdout, stderr, return_code = request.param + + return python_tool_common.LinterSubprocessError( + commands=commands, + stdout=stdout, + stderr=stderr, + return_code=return_code, + ) + + +@pytest.fixture(name="aspect_args") +def fixture_aspect_argument() -> python_tool_common.AspectArguments: + """Fixture to return a python tool common AspectArguments class.""" + tmp_text_file_path = pathlib.Path(tempfile.mkstemp()[1]) + tmp_json_file_path = pathlib.Path(tempfile.mkstemp()[1]) + target_file = pathlib.Path(tempfile.mkstemp()[1]) + + return python_tool_common.AspectArguments( + target_imports=set(), + target_dependencies=set(), + target_files=set([target_file]), + tool=pathlib.Path("tool.exec"), + tool_config=pathlib.Path("tool.config"), + tool_output_text=tmp_text_file_path, + tool_output_json=tmp_json_file_path, + tool_root="", + refactor=False, + ) + + +@pytest.fixture(name="finding", scope="module") +def fixture_finding(request) -> python_tool_common.Finding: + """Fixture to return a generic python tool common Finding class.""" + path, message, severity, tool, rule_id, line, column = request.param + + return python_tool_common.Finding( + path=path, + message=message, + severity=severity, + tool=tool, + rule_id=rule_id, + line=line, + column=column, + ) diff --git a/quality/private/python/tools/test/test_black_runner.py b/quality/private/python/tools/test/test_black_runner.py index 2daf480..8de759d 100644 --- a/quality/private/python/tools/test/test_black_runner.py +++ b/quality/private/python/tools/test/test_black_runner.py @@ -32,6 +32,22 @@ def tearDown(self) -> None: self.tmp_text_file_path.unlink() self.tmp_json_file_path.unlink() + def test_black_removeprefix(self) -> None: + """Test black _removeprefix for both text string cases.""" + would_reformat_message = "would reformat file.py." + expected_text = "reformat" + expected_strip_text = "file.py." + + # pylint: disable-next=protected-access + return_text = black_runner._removeprefix(text=expected_text, prefix=black_runner.WOULD_REFORMAT_MSG) + # pylint: disable-next=protected-access + return_strip_text = black_runner._removeprefix( + text=would_reformat_message, prefix=black_runner.WOULD_REFORMAT_MSG + ) + + assert return_text == expected_text + assert return_strip_text == expected_strip_text + def test_black_output_parser_with_no_issues(self) -> None: """Tests black_output_parser function with the results of a file with no issues.""" expected_findings = python_tool_common.Findings() @@ -69,7 +85,7 @@ def test_black_output_parser_with_issues(self) -> None: findings = black_runner.black_output_parser( python_tool_common.SubprocessInfo( stdout="", - stderr="--- file.py\nwould reformat file.py", + stderr="--- file.py\nwould reformat file.py\n", return_code=0, ) ) diff --git a/quality/private/python/tools/test/test_mypy_runner.py b/quality/private/python/tools/test/test_mypy_runner.py new file mode 100644 index 0000000..da84682 --- /dev/null +++ b/quality/private/python/tools/test/test_mypy_runner.py @@ -0,0 +1,114 @@ +"""Tests for the mypy runner.""" + +import pathlib + +import pytest +from pytest_mock import MockerFixture + +from quality.private.python.tools import mypy_runner, python_tool_common + + +@pytest.mark.parametrize( + "subprocess_output, linter_subprocess_error", + [ + (("", "", 0), ([], "stdout message", "", 1)), + (("stdout message", "", 1), ([], "stdout message", "", 1)), + ], + indirect=["subprocess_output", "linter_subprocess_error"], +) +def test_mypy_output_parser_with_no_issues( + subprocess_output: python_tool_common.SubprocessInfo, + linter_subprocess_error: python_tool_common.LinterSubprocessError, +) -> None: + """Tests mypy_output_parser function and mypy_exception_handler.""" + expected_findings = python_tool_common.Findings() + + if subprocess_output.return_code == 0: + findings = mypy_runner.mypy_output_parser(subprocess_output) + assert expected_findings == findings + if subprocess_output.return_code == mypy_runner.MYPY_BAD_CHECK_ERROR_CODE: + expection_info = mypy_runner.mypy_exception_handler(linter_subprocess_error) + assert subprocess_output == expection_info + + +def test_get_mypy_command(aspect_args: python_tool_common.AspectArguments) -> None: + """Tests get_mypy_command function return commands.""" + target_file = "".join(str(file) for file in aspect_args.target_files) + expected_command = [ + "tool.exec", + "--config-file", + "tool.config", + "--show-column-numbers", + target_file, + ] + + mypy_command = mypy_runner.get_mypy_command(aspect_args) + + assert mypy_command == expected_command + + +@pytest.mark.parametrize( + "linter_subprocess_error", + [([], "", "ERROR: unexpected error\n", 32)], + indirect=["linter_subprocess_error"], +) +def test_mypy_unexpected_exception( + linter_subprocess_error: python_tool_common.LinterSubprocessError, +) -> None: + """Test mypy_exception_handler function when an unexpected exception occurs.""" + with pytest.raises(python_tool_common.LinterSubprocessError) as exception: + mypy_runner.mypy_exception_handler(linter_subprocess_error) + + expected_exception = ( + 'The command "[]" returned code "32" and the following error message:\nERROR: unexpected error\n' + ) + + assert expected_exception == str(exception.value) + + +@pytest.mark.parametrize( + "subprocess_output, finding", + [ + ( + ("stdout", "", 1), + ( + pathlib.Path("file.py"), + 'Unsupported operand types for + ("int" and "str")', + python_tool_common.Severity.WARN, + "mypy", + "mypy:operator", + 11, + 12, + ), + ) + ], + indirect=["subprocess_output", "finding"], +) +def test_mypy_output_parser_with_issues( + subprocess_output: python_tool_common.SubprocessInfo, finding: python_tool_common.Finding +) -> None: + """Tests mypy_output_parser function with results of files with issues.""" + + expected_findings = python_tool_common.Findings([finding]) + + tool_output = subprocess_output + tool_output.stdout = ( + 'file.py:11:12: error: Unsupported operand types for + ("int" and "str") [mypy:operator]\n' + + "Found 1 error in 1 file (checked 1 source file)\n" + ) + findings = mypy_runner.mypy_output_parser(tool_output) + + assert expected_findings == findings + + +def test_call_main(mocker: MockerFixture) -> None: + """Tests calling main with mypy methods.""" + execute_runner_mock = mocker.patch("quality.private.python.tools.python_tool_common.execute_runner") + + mypy_runner.main() + + execute_runner_mock.assert_called_once_with( + get_command=mypy_runner.get_mypy_command, + output_parser=mypy_runner.mypy_output_parser, + exception_handler=mypy_runner.mypy_exception_handler, + ) diff --git a/quality/private/python/tools/test/test_ruff_check_runner.py b/quality/private/python/tools/test/test_ruff_check_runner.py new file mode 100644 index 0000000..3567738 --- /dev/null +++ b/quality/private/python/tools/test/test_ruff_check_runner.py @@ -0,0 +1,164 @@ +"""Tests for the ruff_check runner.""" + +import pathlib + +import pytest +from pytest_mock import MockerFixture + +from quality.private.python.tools import python_tool_common, ruff_check_runner + + +@pytest.mark.parametrize( + "subprocess_output, linter_subprocess_error", + [ + (("[]", "", 0), ([], "stdout message", "", 1)), + (("stdout message", "", 1), ([], "stdout message", "", 1)), + ], + indirect=["subprocess_output", "linter_subprocess_error"], +) +def test_ruff_check_output_parser_with_no_issues( + subprocess_output: python_tool_common.SubprocessInfo, + linter_subprocess_error: python_tool_common.LinterSubprocessError, +) -> None: + """Tests ruff_check_output_parser function and ruff_check_exception_handler.""" + expected_findings = python_tool_common.Findings() + + if subprocess_output.return_code == 0: + findings = ruff_check_runner.ruff_check_output_parser(subprocess_output) + assert expected_findings == findings + if subprocess_output.return_code == ruff_check_runner.RUFF_BAD_CHECK_ERROR_CODE: + expection_info = ruff_check_runner.ruff_check_exception_handler(linter_subprocess_error) + assert subprocess_output == expection_info + + +def test_get_ruff_check_command(aspect_args: python_tool_common.AspectArguments) -> None: + """Tests get_ruff_check_command function return commands.""" + target_file = "".join(str(file) for file in aspect_args.target_files) + expected_command = [ + "tool.exec", + "check", + "--config", + "tool.config", + "--unsafe-fixes", + "--output-format", + "json", + target_file, + ] + + ruff_check_command = ruff_check_runner.get_ruff_check_command(aspect_args) + + assert ruff_check_command == expected_command + + +@pytest.mark.parametrize( + "linter_subprocess_error", + [([], "stdout message", "", 1)], + indirect=["linter_subprocess_error"], +) +def test_ruff_check_exception_handler( + linter_subprocess_error: python_tool_common.LinterSubprocessError, +) -> None: + """Test ruff_check_exception_handler function when an expected exception occurs.""" + expected_info = python_tool_common.SubprocessInfo( + stdout="stdout message", + stderr="", + return_code=1, + ) + + info = ruff_check_runner.ruff_check_exception_handler(linter_subprocess_error) + + assert expected_info == info + + +@pytest.mark.parametrize( + "linter_subprocess_error", + [([], "", "ERROR: unexpected error\n", 32)], + indirect=["linter_subprocess_error"], +) +def test_ruff_check_unexpected_exception( + linter_subprocess_error: python_tool_common.LinterSubprocessError, +) -> None: + """Test ruff_check_exception_handler function when an unexpected exception occurs.""" + with pytest.raises(python_tool_common.LinterSubprocessError) as exception: + ruff_check_runner.ruff_check_exception_handler(linter_subprocess_error) + + expected_exception = ( + 'The command "[]" returned code "32" and the following error message:\nERROR: unexpected error\n' + ) + + assert expected_exception == str(exception.value) + + +@pytest.mark.parametrize( + "subprocess_output, finding", + [ + ( + ("stdout", "", 1), + ( + pathlib.Path("path/file.py"), + "`os` imported but unused", + python_tool_common.Severity.WARN, + "ruff_check", + "F401", + 10, + 8, + ), + ) + ], + indirect=["subprocess_output", "finding"], +) +def test_ruff_check_output_parser_with_issues( + subprocess_output: python_tool_common.SubprocessInfo, finding: python_tool_common.Finding +) -> None: + """Tests ruff_check_output_parser function with results of files with issues.""" + expected_findings = python_tool_common.Findings([finding]) + + tool_output = subprocess_output + tool_output.stdout = """[\n{\n + "cell": null,\n + "code": "F401",\n + "end_location": {\n"column": 10,\n"row": 10\n},\n + "filename": "path/file.py",\n + "fix": {\n + "applicability": "safe",\n + "edits": [\n{\n"content": "",\n + "end_location": {\n + "column": 1,\n + "row": 6\n + },\n + "location": {\n + "column": 1,\n + "row": 5\n + }\n}\n],\n + "message": "Remove unused import: `os`"\n + },\n + "location": {\n"column":8,\n"row": 10\n},\n + "message": "`os` imported but unused",\n + "noqa_row": 5,\n + "url": "https://docs.astral.sh/ruff/rules/unused-import"\n}\n]""" + findings = ruff_check_runner.ruff_check_output_parser(tool_output) + + assert expected_findings == findings + + +def test_get_ruff_check_command_with_refactor(aspect_args: python_tool_common.AspectArguments) -> None: + """Tests get_ruff_check_command function with the refactor being true.""" + aspect_args.refactor = True + + ruff_check_command = ruff_check_runner.get_ruff_check_command(aspect_args) + + assert "--fix" in ruff_check_command + assert "--unsafe-fixes" not in ruff_check_command + + +def test_call_main(mocker: MockerFixture) -> None: + """Tests calling main.""" + execute_runner_mock = mocker.patch("quality.private.python.tools.python_tool_common.execute_runner") + + ruff_check_runner.main() + + execute_runner_mock.assert_called_once_with( + get_command=ruff_check_runner.get_ruff_check_command, + output_parser=ruff_check_runner.ruff_check_output_parser, + exception_handler=ruff_check_runner.ruff_check_exception_handler, + ) diff --git a/quality/private/python/tools/test/test_ruff_format_runner.py b/quality/private/python/tools/test/test_ruff_format_runner.py new file mode 100644 index 0000000..d5a0cca --- /dev/null +++ b/quality/private/python/tools/test/test_ruff_format_runner.py @@ -0,0 +1,147 @@ +"""Tests for the ruff_format runner.""" + +import pathlib + +import pytest +from pytest_mock import MockerFixture + +from quality.private.python.tools import python_tool_common, ruff_format_runner + + +@pytest.mark.parametrize( + "subprocess_output, linter_subprocess_error", + [ + (("", "", 0), ([], "stdout message", "", 1)), + (("stdout message", "", 1), ([], "stdout message", "", 1)), + ], + indirect=["subprocess_output", "linter_subprocess_error"], +) +def test_ruff_format_output_parser_with_no_issues( + subprocess_output: python_tool_common.SubprocessInfo, + linter_subprocess_error: python_tool_common.LinterSubprocessError, +) -> None: + """Tests ruff_format_output_parser function and ruff_format_exception_handler.""" + expected_findings = python_tool_common.Findings() + + if subprocess_output.return_code == 0: + findings = ruff_format_runner.ruff_format_output_parser(subprocess_output) + assert expected_findings == findings + if subprocess_output.return_code == ruff_format_runner.RUFF_BAD_CHECK_ERROR_CODE: + expection_info = ruff_format_runner.ruff_format_exception_handler(linter_subprocess_error) + assert subprocess_output == expection_info + + +def test_get_ruff_format_command(aspect_args: python_tool_common.AspectArguments) -> None: + """Tests get_ruff_format_command function return commands.""" + target_file = "".join(str(file) for file in aspect_args.target_files) + expected_command = [ + "tool.exec", + "format", + "--config", + "tool.config", + "--diff", + target_file, + ] + + ruff_format_command = ruff_format_runner.get_ruff_format_command(aspect_args) + + assert ruff_format_command == expected_command + + +@pytest.mark.parametrize( + "linter_subprocess_error", + [([], "stdout message", "", 1)], + indirect=["linter_subprocess_error"], +) +def test_ruff_format_exception_handler( + linter_subprocess_error: python_tool_common.LinterSubprocessError, +) -> None: + """Test ruff_format_exception_handler function when an expected exception occurs.""" + expected_info = python_tool_common.SubprocessInfo( + stdout="stdout message", + stderr="", + return_code=1, + ) + + info = ruff_format_runner.ruff_format_exception_handler(linter_subprocess_error) + + assert expected_info == info + + +@pytest.mark.parametrize( + "linter_subprocess_error", + [([], "", "ERROR: unexpected error\n", 32)], + indirect=["linter_subprocess_error"], +) +def test_ruff_format_unexpected_exception( + linter_subprocess_error: python_tool_common.LinterSubprocessError, +) -> None: + """Test ruff_format_exception_handler function when an unexpected exception occurs.""" + with pytest.raises(python_tool_common.LinterSubprocessError) as exception: + ruff_format_runner.ruff_format_exception_handler(linter_subprocess_error) + + expected_exception = ( + 'The command "[]" returned code "32" and the following error message:\nERROR: unexpected error\n' + ) + + assert expected_exception == str(exception.value) + + +@pytest.mark.parametrize( + "subprocess_output, finding", + [ + ( + ("stdout", "", 1), + ( + pathlib.Path("file.py"), + "Should be reformatted.", + python_tool_common.Severity.WARN, + "ruff_format", + "formatting", + 1, + 1, + ), + ) + ], + indirect=["subprocess_output", "finding"], +) +def test_ruff_format_output_parser_with_issues( + subprocess_output: python_tool_common.SubprocessInfo, finding: python_tool_common.Finding +) -> None: + """Tests ruff_format_output_parser function with results of files with issues.""" + expected_findings = python_tool_common.Findings([finding]) + + tool_output = subprocess_output + tool_output.stdout = ( + "--- file.py\n+++ file.py\n" + + "@@ -9,6 +9,7 @@\n" + + "from quality.private.python.tools import pylint_runner, python_tool_common\n" + + "import os\n \n+\n class TestPylintRunner(unittest.TestCase):\n" + + '"""Test class for pylint runner."""\n \n\n' + ) + tool_output.stderr = "1 file would be reformatted\n" + findings = ruff_format_runner.ruff_format_output_parser(tool_output) + + assert expected_findings == findings + + +def test_get_ruff_format_command_with_refactor(aspect_args: python_tool_common.AspectArguments) -> None: + """Tests get_ruff_format_command function with the refactor being true.""" + aspect_args.refactor = True + + ruff_format_command = ruff_format_runner.get_ruff_format_command(aspect_args) + + assert "--diff" not in ruff_format_command + + +def test_call_main(mocker: MockerFixture) -> None: + """Tests calling main.""" + execute_runner_mock = mocker.patch("quality.private.python.tools.python_tool_common.execute_runner") + + ruff_format_runner.main() + + execute_runner_mock.assert_called_once_with( + get_command=ruff_format_runner.get_ruff_format_command, + output_parser=ruff_format_runner.ruff_format_output_parser, + exception_handler=ruff_format_runner.ruff_format_exception_handler, + ) From 7fc8d78bc30e68e1c9e1515588c97fb544d5d6cb Mon Sep 17 00:00:00 2001 From: Daniel Merget Date: Wed, 19 Mar 2025 17:19:23 +0100 Subject: [PATCH 49/74] [documentation] Hint at --features=refactor --- quality/private/python/README.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/quality/private/python/README.md b/quality/private/python/README.md index b685374..b66f60d 100644 --- a/quality/private/python/README.md +++ b/quality/private/python/README.md @@ -56,14 +56,17 @@ build:ruff_format --@swf_bazel_rules_quality//quality:quality_ruff_config=//path To run one or more tools, considering that they are correctly configured, you can use the following commands: ```bash -bazel build --config=pylint --keep_going -- //... -bazel build --config=black --keep_going -- //... -bazel build --config=isort --keep_going -- //... -bazel build --config=ruff_check --keep_going -- //... -bazel build --config=ruff_format --keep_going -- //... +bazel build --config=black --keep_going [--features=refactor] -- //... +bazel build --config=isort --keep_going [--features=refactor] -- //... bazel build --config=mypy --keep_going -- //... +bazel build --config=pylint --keep_going -- //... +bazel build --config=ruff_check --keep_going [--features=refactor] -- //... +bazel build --config=ruff_format --keep_going [--features=refactor] -- //... ``` +Where `--features=refactor` is optional if you want the targets to be refactored automatically by the tool. +See section [Refactoring](#refactoring) below for more details. + Finally in the `.bazelrc` file we can configure the build for each tool. ```py @@ -125,14 +128,17 @@ The Ruff formatter is an extremely fast Python code formatter designed as a drop For more information read [Ruff formatter documentation](https://docs.astral.sh/ruff/formatter/). -### Extra Features +### Refactoring Some tools have an extra feature called `refactor`. When used with the CLI command, the tool will fix most findings that have been found in the target file. The tools that have this feature are: `black`, `isort`, `ruff-check` and `ruff-format`. Note that `ruff-check` may not automatically fix all findings. -To use the `refactor` feature, one must add `--features=refactor` to the command line. The following command shows how to refactor every target using `black`: +To use the `refactor` feature, one must add `--features=refactor` to the command line. For example: ```py bazel build --config=black --keep_going --features=refactor -- //... +bazel build --config=isort --keep_going --features=refactor -- //... +bazel build --config=ruff_check --keep_going --features=refactor -- //... +bazel build --config=ruff_format --keep_going --features=refactor -- //... ``` ## Custom Pytest From cedd62698e9636c5890ba0526040fdf55a398969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Taylor=20Ienczak=20Zanette?= Date: Wed, 12 Mar 2025 10:40:26 -0300 Subject: [PATCH 50/74] [pylint] Take Windows Paths into account when parsing output --- quality/private/python/tools/pylint_runner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quality/private/python/tools/pylint_runner.py b/quality/private/python/tools/pylint_runner.py index 32ab531..6dd166b 100644 --- a/quality/private/python/tools/pylint_runner.py +++ b/quality/private/python/tools/pylint_runner.py @@ -52,11 +52,11 @@ def pylint_output_parser(tool_output: python_tool_common.SubprocessInfo) -> pyth if output_line.startswith(PYLINT_MODULE_START_MSG): continue - path, line, column, message, rule_id = output_line.split(":") + *path_parts, line, column, message, rule_id = output_line.split(":") findings.append( python_tool_common.Finding( - path=pathlib.Path(path), + path=pathlib.Path(":".join(path_parts)), message=message, severity=python_tool_common.Severity.WARN, tool="pylint", From 8222f4e019a9f7426ca161263e2407ee4e3a35ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Taylor=20Ienczak=20Zanette?= Date: Thu, 13 Mar 2025 12:23:16 -0300 Subject: [PATCH 51/74] [pylint] Simplify Windows path handling --- quality/private/python/tools/pylint_runner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quality/private/python/tools/pylint_runner.py b/quality/private/python/tools/pylint_runner.py index 6dd166b..805b678 100644 --- a/quality/private/python/tools/pylint_runner.py +++ b/quality/private/python/tools/pylint_runner.py @@ -52,11 +52,11 @@ def pylint_output_parser(tool_output: python_tool_common.SubprocessInfo) -> pyth if output_line.startswith(PYLINT_MODULE_START_MSG): continue - *path_parts, line, column, message, rule_id = output_line.split(":") + path, line, column, message, rule_id = output_line.rsplit(":", 4) findings.append( python_tool_common.Finding( - path=pathlib.Path(":".join(path_parts)), + path=pathlib.Path(path), message=message, severity=python_tool_common.Severity.WARN, tool="pylint", From 36c067a828d7ea9d1a6536647a313988422958b2 Mon Sep 17 00:00:00 2001 From: "Yuri.Barboza" Date: Tue, 11 Mar 2025 01:29:07 +0100 Subject: [PATCH 52/74] [python] Add test to python_tool_common This commit will add tests to the Python tool common. --- quality/private/python/tools/test/BUILD | 8 +- quality/private/python/tools/test/conftest.py | 18 +- .../tools/test/test_python_tool_common.py | 325 ++++++++++++++++++ 3 files changed, 340 insertions(+), 11 deletions(-) create mode 100644 quality/private/python/tools/test/test_python_tool_common.py diff --git a/quality/private/python/tools/test/BUILD b/quality/private/python/tools/test/BUILD index 4ad2cee..ceb5c7b 100644 --- a/quality/private/python/tools/test/BUILD +++ b/quality/private/python/tools/test/BUILD @@ -52,7 +52,13 @@ py_library( srcs = ["conftest.py"], visibility = ["//visibility:public"], deps = [ - "@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common", + "//quality/private/python/tools:python_tool_common", pkg("pytest"), ], ) + +py_pytest( + name = "test_python_tool_common", + srcs = ["test_python_tool_common.py"], + deps = [":conftest"], +) diff --git a/quality/private/python/tools/test/conftest.py b/quality/private/python/tools/test/conftest.py index 296d378..3c71c6a 100644 --- a/quality/private/python/tools/test/conftest.py +++ b/quality/private/python/tools/test/conftest.py @@ -1,7 +1,6 @@ """pytest fixtures for the config-generator test""" import pathlib -import tempfile import pytest @@ -9,7 +8,7 @@ @pytest.fixture(name="subprocess_output", scope="module") -def fixture_subprocessinfo(request) -> python_tool_common.SubprocessInfo: +def fixture_subprocess_info(request) -> python_tool_common.SubprocessInfo: """Fixture to return a python tool common SubprocessInfo class without errors.""" stdout, stderr, return_code = request.param @@ -21,7 +20,7 @@ def fixture_subprocessinfo(request) -> python_tool_common.SubprocessInfo: @pytest.fixture(name="linter_subprocess_error", scope="module") -def fixture_linter_subprocesserror(request) -> python_tool_common.LinterSubprocessError: +def fixture_linter_subprocess_error(request) -> python_tool_common.LinterSubprocessError: """Fixture to return a generic python tool common LinterSubprocessError.""" commands, stdout, stderr, return_code = request.param @@ -34,20 +33,19 @@ def fixture_linter_subprocesserror(request) -> python_tool_common.LinterSubproce @pytest.fixture(name="aspect_args") -def fixture_aspect_argument() -> python_tool_common.AspectArguments: +def fixture_aspect_argument(tmp_path: pathlib.Path) -> python_tool_common.AspectArguments: """Fixture to return a python tool common AspectArguments class.""" - tmp_text_file_path = pathlib.Path(tempfile.mkstemp()[1]) - tmp_json_file_path = pathlib.Path(tempfile.mkstemp()[1]) - target_file = pathlib.Path(tempfile.mkstemp()[1]) + test = tmp_path / "test.py" + test.write_text("") return python_tool_common.AspectArguments( target_imports=set(), target_dependencies=set(), - target_files=set([target_file]), + target_files=set([test]), tool=pathlib.Path("tool.exec"), tool_config=pathlib.Path("tool.config"), - tool_output_text=tmp_text_file_path, - tool_output_json=tmp_json_file_path, + tool_output_text=pathlib.Path("output.text"), + tool_output_json=pathlib.Path("output.json"), tool_root="", refactor=False, ) diff --git a/quality/private/python/tools/test/test_python_tool_common.py b/quality/private/python/tools/test/test_python_tool_common.py new file mode 100644 index 0000000..0184d19 --- /dev/null +++ b/quality/private/python/tools/test/test_python_tool_common.py @@ -0,0 +1,325 @@ +"""Tests for the python tool common.""" + +import filecmp +import json +import os +import subprocess +from pathlib import Path + +import pytest +from pytest_mock import MockerFixture + +from quality.private.python.tools import python_tool_common + + +def test_findings_to_json_file_and_to_text_file(tmp_path: Path) -> None: + """Test Findings to_json_file and to_text_file methods""" + + dummy_finding = python_tool_common.Finding( + path=Path("file.py"), + message="Generic error message", + severity=python_tool_common.Severity.WARN, + tool="tool", + rule_id="generic", + line=1, + column=1, + ) + findings = python_tool_common.Findings([dummy_finding]) + expected_message = "file.py:1:1: Generic error message [tool:generic]" + expected_findings = [ + { + "path": "file.py", + "message": "Generic error message", + "severity": "WARN", + "tool": "tool", + "rule_id": "generic", + "line": 1, + "column": 1, + } + ] + output_text_file_path = tmp_path / "findings.text" + output_json_file_path = tmp_path / "findings.json" + findings.to_text_file(file=output_text_file_path) + findings.to_json_file(file=output_json_file_path) + + text_findings = output_text_file_path.read_text() + json_findings = json.loads(output_json_file_path.read_text()) + + assert expected_message == str(dummy_finding) + assert text_findings == expected_message + assert json_findings == expected_findings + + +@pytest.mark.parametrize( + "stdout, expected_findings", + [ + ( + "", + python_tool_common.Findings([]), + ), + ( + "file.py,Imports are incorrectly sorted and/or formatted.,WARN,generic_tool,[imports_formatting],2,3", + python_tool_common.Findings( + [ + python_tool_common.Finding( + path=Path("file.py"), + message="Imports are incorrectly sorted and/or formatted.", + severity=python_tool_common.Severity.WARN, + tool="generic_tool", + rule_id="[imports_formatting]", + line=2, + column=3, + ) + ] + ), + ), + ], +) +def test_successful_execute_runner( + mocker: MockerFixture, + stdout: str, + expected_findings: python_tool_common.Findings, + aspect_args: python_tool_common.AspectArguments, + tmp_path: Path, +) -> None: + """Test execute runner function with and without findings. + + Since this test will use all available tools, it's natural that the function + call will have too many arguments and local arguments. + """ + aspect_args.tool_root = "_main" + subprocess_info = python_tool_common.SubprocessInfo( + stdout=stdout, + stderr="", + return_code=0, + ) + mocker.patch( + "quality.private.python.tools.python_tool_common.execute_subprocess", + return_value=subprocess_info, + ) + mocker.patch( + "quality.private.python.tools.python_tool_common.parse_args", + return_value=aspect_args, + ) + text_file = tmp_path / "finding.text" + expected_findings.to_text_file(text_file) + json_file = tmp_path / "finding.json" + expected_findings.to_json_file(json_file) + output_parser = mocker.MagicMock(return_value=expected_findings) + + if subprocess_info.stdout == "": + python_tool_common.execute_runner( + get_command=lambda _: [""], + output_parser=output_parser, + ) + else: + with pytest.raises(python_tool_common.LinterFindingAsError): + python_tool_common.execute_runner( + get_command=lambda _: [""], + output_parser=output_parser, + ) + + output_parser.assert_called_with(subprocess_info) + assert filecmp.cmp(text_file, aspect_args.tool_output_text) + assert filecmp.cmp(json_file, aspect_args.tool_output_json) + + +def test_execute_runner_fault_without_exception_handler( + mocker: MockerFixture, + aspect_args: python_tool_common.AspectArguments, +): + """Test execute runner function with exceptions but without exception handler.""" + mocker.patch( + "quality.private.python.tools.python_tool_common.execute_subprocess", + side_effect=[ + python_tool_common.LinterSubprocessError( + commands=["tool"], + return_code=1, + stdout="exception.stdout", + stderr="exception.stderr", + ) + ], + ) + mocker.patch( + "quality.private.python.tools.python_tool_common.parse_args", + return_value=aspect_args, + ) + expected_error = python_tool_common.LinterSubprocessError + + with pytest.raises(expected_error): + python_tool_common.execute_runner( + get_command=lambda _: [""], + output_parser=lambda _: python_tool_common.Findings([]), + exception_handler=None, + ) + + +def test_execute_runner_fault_with_exception_handler( + mocker: MockerFixture, + aspect_args: python_tool_common.AspectArguments, +): + """Test execute runner function with exceptions and with excpetion handler.""" + mocker.patch( + "quality.private.python.tools.python_tool_common.execute_subprocess", + side_effect=[ + python_tool_common.LinterSubprocessError( + commands=["tool"], + return_code=1, + stdout="exception.stdout", + stderr="exception.stderr", + ) + ], + ) + mocker.patch( + "quality.private.python.tools.python_tool_common.parse_args", + return_value=aspect_args, + ) + expected_error = python_tool_common.LinterFindingAsError + expected_finding = python_tool_common.Finding( + path=Path("file.py"), + message="exception.stdout", + severity=python_tool_common.Severity.WARN, + tool="tool", + rule_id="exception", + ) + exception_handler = mocker.MagicMock( + return_value=python_tool_common.SubprocessInfo( + stdout="--- file.py", + stderr="Tool found an error.\n", + return_code=1, + ) + ) + + with pytest.raises(expected_error): + python_tool_common.execute_runner( + get_command=lambda _: [""], + output_parser=lambda _: python_tool_common.Findings([expected_finding]), + exception_handler=exception_handler, + ) + + +def test_stop_iteration_except_case() -> None: + """Test if AspectArguments StopIteration excpetion is properly raised. + + The exception will be raised when the path for target_file can't be found.""" + expected_message = "Therefore generic_tool cannot properly run." + + with pytest.raises(python_tool_common.PythonPathNotFoundError) as excpt: + python_tool_common.AspectArguments( + target_imports={Path("")}, + target_dependencies={Path("")}, + target_files={Path("wrong_folder/test_file.py")}, + tool=Path("/generic_tool"), + tool_config=Path(""), + tool_output_text=Path(""), + tool_output_json=Path(""), + tool_root="main", + refactor=False, + ) + + assert expected_message in str(excpt) + + +def test_bazel_check_case( + mocker: MockerFixture, aspect_args: python_tool_common.AspectArguments, tmp_path: Path +) -> None: + """Test if AspectArguments checks for bazel generated files before raising an except.""" + mocker.patch("builtins.open", side_effect=FileNotFoundError) + mocker.patch.object(Path, "glob", return_value={Path("path/to/test_python_tool_common.py")}) + tmp_text_file_path = tmp_path / "output.text" + tmp_json_file_path = tmp_path / "output.json" + expected_target_file = {Path("path/to/test_python_tool_common.py")} + + aspect_args = python_tool_common.AspectArguments( + target_imports={Path("")}, + target_dependencies={Path("")}, + target_files={Path("path/to/file.py")}, + tool=Path("path/to/tool_entry_point"), + tool_config=Path("path/to/pyproject.toml"), + tool_output_text=tmp_text_file_path, + tool_output_json=tmp_json_file_path, + tool_root="_main", + refactor=False, + ) + + assert aspect_args.target_files == expected_target_file + + +def test_resolve_relative_paths_valid(mocker: MockerFixture, tmp_path: Path) -> None: + """Test AspectArguments path resolution when no exception is raised. + + Also tests the enviroment for target_imports. + """ + mocker.patch.object(Path, "glob", return_value={Path("quality/private/python/ruff_entry_point")}) + mocker.patch.dict(os.environ, {"PYTHONPATH": "some/path"}, clear=False) + target_imports = {Path("path/to/imports")} + target_files = {Path("quality/private/python/ruff_entry_point")} + tool = Path("path/to/tool_entry_point") + tool_config = Path("path/to/pyproject.toml") + tmp_text_file_path = tmp_path / "output.text" + tmp_json_file_path = tmp_path / "output.json" + expected_tool_path = Path("path/to/tool_entry_point") + expected_tool_config_path = Path("path/to/pyproject.toml") + + aspect_args = python_tool_common.AspectArguments( + target_imports=target_imports, + target_dependencies={Path("")}, + target_files=target_files, + tool=tool, + tool_config=tool_config, + tool_output_text=tmp_text_file_path, + tool_output_json=tmp_json_file_path, + tool_root="quality", + refactor=False, + ) + + assert aspect_args.tool == expected_tool_path + assert aspect_args.tool_config == expected_tool_config_path + assert aspect_args.refactor is False + + +def test_execute_subprocess(mocker: MockerFixture) -> None: + """Test the execute_subprocess function with a zero return code.""" + mocker.patch( + "subprocess.run", + return_value=subprocess.CompletedProcess( + args=[], + returncode=0, + stdout="", + stderr="exception stdeer", + ), + ) + commands = [ + "path/to/tool_entry_point", + "--diff", + "--config", + "path/to/pyproject.toml", + "path/to/test.py", + ] + expected_stderr = "exception stdeer" + expected_return_code = 0 + + info = python_tool_common.execute_subprocess(commands=commands) + + assert info.stdout == "" + assert info.stderr == expected_stderr + assert info.return_code == expected_return_code + + +def test_execute_subprocess_error(mocker: MockerFixture) -> None: + """Test the execute_subprocess function raising LinterSubprocessError.""" + commands = ["false"] + mocker.patch( + "subprocess.run", + side_effect=subprocess.CalledProcessError( + cmd=commands, + returncode=1, + output="An error ocurred.", + ), + ) + expected_output = f'The command "{commands}" returned code "1" and the following error message:\nAn error ocurred.' + + with pytest.raises(python_tool_common.LinterSubprocessError) as excpt: + python_tool_common.execute_subprocess(commands=commands) + + assert str(excpt.value) == expected_output From f5ea1a7395671bc3ad3ddc4f0e512028744a89fb Mon Sep 17 00:00:00 2001 From: Vitor Sorpile Date: Mon, 3 Feb 2025 20:06:33 +0100 Subject: [PATCH 53/74] [quality] Ignore mypy var-annotate check --- quality/private/python/support/pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/quality/private/python/support/pyproject.toml b/quality/private/python/support/pyproject.toml index 33af4df..b8b3c34 100644 --- a/quality/private/python/support/pyproject.toml +++ b/quality/private/python/support/pyproject.toml @@ -20,6 +20,9 @@ disable = [ disallow_untyped_defs = false explicit_package_bases = true incremental = false +# Avoid having to add type hints to internal variables that are initialized, +# but mypy can't be certain about the type of the variable. +disable_error_code = ["var-annotated"] [tool.ruff] # Same as Black. From cf5e3643c2779800ffe06b8282c8c5c9121f4cf7 Mon Sep 17 00:00:00 2001 From: Yuri Barboza Date: Thu, 17 Apr 2025 15:29:18 +0200 Subject: [PATCH 54/74] [python] Parse user enabled features and raise error This commit add a parse in the python aspect which will raise an error or print a message when non compliant features are used with python tools. --- quality/private/python/python_tool_aspect.bzl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/quality/private/python/python_tool_aspect.bzl b/quality/private/python/python_tool_aspect.bzl index 431a7f3..948719c 100644 --- a/quality/private/python/python_tool_aspect.bzl +++ b/quality/private/python/python_tool_aspect.bzl @@ -4,7 +4,21 @@ load("@swf_bazel_rules_quality//quality/private/python:python_collect_aspect.bzl load("@swf_bazel_rules_quality//quality/private/python:python_helper.bzl", "is_valid_label") load("@swf_bazel_rules_quality//quality/private/python:python_providers.bzl", "PythonCollectInfo", "PythonToolInfo") +_AVAILABLE_FEATURES = [ + # Tools that are able to refactor will automatically fix findings using this feature. + "refactor", +] + def _python_tool_config_impl(ctx): + for feature_name in ctx.attr.features: + if feature_name not in _AVAILABLE_FEATURES: + fail('The feature "' + feature_name + '" does not exist. Available features are: "' + ", ".join(_AVAILABLE_FEATURES)) + + for feature_name in ctx.features: + if feature_name not in _AVAILABLE_FEATURES: + # buildifier: disable=print + print('Python tools integration does not offer the feature "' + feature_name + '", but it will still be considered in the build.') + return [ DefaultInfo(files = depset([ctx.file.config])), PythonToolInfo(config = ctx.file.config, additional_features = ctx.attr.additional_features), From 25916ccaf60c9628dac7d512045eb6585bbac7ff Mon Sep 17 00:00:00 2001 From: Yuri Barboza Date: Wed, 23 Apr 2025 14:32:25 +0200 Subject: [PATCH 55/74] [coverage] Add coverage to missing lines This commit adds a new test case for both test_python_tool_common and test_clang_tidy_runner, with the objective of reaching 100% coverage with bazel coverage. --- quality/private/python/tools/test/test_python_tool_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quality/private/python/tools/test/test_python_tool_common.py b/quality/private/python/tools/test/test_python_tool_common.py index 0184d19..1958bb3 100644 --- a/quality/private/python/tools/test/test_python_tool_common.py +++ b/quality/private/python/tools/test/test_python_tool_common.py @@ -251,7 +251,7 @@ def test_resolve_relative_paths_valid(mocker: MockerFixture, tmp_path: Path) -> Also tests the enviroment for target_imports. """ mocker.patch.object(Path, "glob", return_value={Path("quality/private/python/ruff_entry_point")}) - mocker.patch.dict(os.environ, {"PYTHONPATH": "some/path"}, clear=False) + mocker.patch.dict(os.environ, {}, clear=True) target_imports = {Path("path/to/imports")} target_files = {Path("quality/private/python/ruff_entry_point")} tool = Path("path/to/tool_entry_point") From 262e68cc7641700ef7b8d3245df8600ecfc23d6e Mon Sep 17 00:00:00 2001 From: Sascha Moecker Date: Thu, 8 May 2025 13:00:21 +0200 Subject: [PATCH 56/74] [aspects] Add mnemonic to all actions. See https://bazel.build/rules/lib/builtins/actions#run and https://bazel.build/reference/glossary#mnemonic. This helps to identify and analyze actions e.g. when running on a remote cluster. --- quality/private/python/python_tool_aspect.bzl | 1 + 1 file changed, 1 insertion(+) diff --git a/quality/private/python/python_tool_aspect.bzl b/quality/private/python/python_tool_aspect.bzl index 948719c..b6d8b39 100644 --- a/quality/private/python/python_tool_aspect.bzl +++ b/quality/private/python/python_tool_aspect.bzl @@ -98,6 +98,7 @@ def _python_tool_aspect_implementation(target, ctx): arguments = [args], progress_message = "Running {tool} on: {target_name}".format(tool = ctx.executable._runner.basename, target_name = target.label.name), execution_requirements = {"no-sandbox": file_refactor}, + mnemonic = "PyAnalysis", ) return [OutputGroupInfo(python_tool_output = depset(outputs))] From 41f45facfc72bd7d15df43ce19ef3d3941b3f1a3 Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Wed, 18 Jun 2025 14:20:17 +0200 Subject: [PATCH 57/74] [pytest] Remove `pytest-vcr` from default plugins While pytest-vcr is a nice library it is causing too much noise when using our custom py_pytest rule from an external repo due to incompatible libraries resolution. As it is not used and also not considered an essential lib, it makes sense to remove it from BRQ. Users can still add VCR back using their own dependencies. --- quality/private/python/BUILD | 1 - quality/private/python/support/pyproject.toml | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/quality/private/python/BUILD b/quality/private/python/BUILD index e2bcc8f..d95a67a 100644 --- a/quality/private/python/BUILD +++ b/quality/private/python/BUILD @@ -18,7 +18,6 @@ py_library( pkg("pytest-mock"), pkg("pytest-rerunfailures"), pkg("pytest-timeout"), - pkg("pytest-vcr"), ], ) diff --git a/quality/private/python/support/pyproject.toml b/quality/private/python/support/pyproject.toml index b8b3c34..8613db8 100644 --- a/quality/private/python/support/pyproject.toml +++ b/quality/private/python/support/pyproject.toml @@ -58,8 +58,7 @@ required_plugins = [ "pytest-bdd", "pytest-mock", "pytest-rerunfailures", - "pytest-timeout", - "pytest-vcr" + "pytest-timeout" ] markers = [ From 7770f465d34e28c5e59d4d3a7df2a31af8a9facc Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Tue, 22 Jul 2025 18:44:00 +0200 Subject: [PATCH 58/74] [python] Create `pip-audit` `pybinary` entry point --- quality/private/python/BUILD | 8 ++++++++ quality/private/python/pip_audit_entry_point.py | 13 +++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 quality/private/python/pip_audit_entry_point.py diff --git a/quality/private/python/BUILD b/quality/private/python/BUILD index d95a67a..9d5936b 100644 --- a/quality/private/python/BUILD +++ b/quality/private/python/BUILD @@ -68,3 +68,11 @@ py_binary( visibility = ["//visibility:public"], deps = [pkg("ruff")], ) + +py_binary( + name = "pip_audit_entry_point", + srcs = ["pip_audit_entry_point.py"], + main = "pip_audit_entry_point.py", + visibility = ["//visibility:public"], + deps = [pkg("pip-audit")], +) diff --git a/quality/private/python/pip_audit_entry_point.py b/quality/private/python/pip_audit_entry_point.py new file mode 100644 index 0000000..5262de4 --- /dev/null +++ b/quality/private/python/pip_audit_entry_point.py @@ -0,0 +1,13 @@ +"""Entry point for pip-audit python library. + +This executes pip-audit by importing, thus executing, its main entry point. +""" + +# ruff: noqa: F401 +# We do not want to use __main__ but only import it. +# That is because when we import it, python already runs the tool entry point. + +if __name__ == "__main__": + from pip_audit._cli import audit # type: ignore[import-untyped] + + audit() From ed3f6409e241d7849d3a423ebe3abdb71f7a87c8 Mon Sep 17 00:00:00 2001 From: Lucas Munaretto Date: Tue, 22 Jul 2025 19:01:02 +0200 Subject: [PATCH 59/74] [python] Create pip-audit rule This rule can be used to check python requirements files. As it is an executable rule, it can be used with `bazel run //target` command. From the start, it already supports: - Locked and not locked requirement files; - `index_url` option to change pip index; - `no_deps` option to allow not locked files to be checked. --- quality/private/python/README.md | 40 ++++++++- .../private/python/python_pip_audit_rule.bzl | 81 +++++++++++++++++++ quality/private/python/tools/BUILD | 6 ++ .../python/tools/pip_audit_runner.sh.template | 2 + 4 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 quality/private/python/python_pip_audit_rule.bzl create mode 100644 quality/private/python/tools/pip_audit_runner.sh.template diff --git a/quality/private/python/README.md b/quality/private/python/README.md index b66f60d..d295f46 100644 --- a/quality/private/python/README.md +++ b/quality/private/python/README.md @@ -118,7 +118,7 @@ For more information read [Pylint documentation](https://pylint.readthedocs.io/e #### Ruff-check -Ruff is an extremely fast Python linter designed as a drop-in replacement for Flake8 (plus dozens of plugins), isort, pydocstyle, pyupgrade, autoflake, and more. Ruff check is the primary entrypoint to the Ruff linter. +Ruff is an extremely fast Python linter designed as a drop-in replacement for Flake8 (plus dozens of plugins), isort, pydocstyle, pyupgrade, autoflake, and more. Ruff check is the primary entrypoint to the Ruff linter. For more information read [Ruff check documentation](https://docs.astral.sh/ruff/linter/). @@ -141,6 +141,44 @@ bazel build --config=ruff_check --keep_going --features=refactor -- //... bazel build --config=ruff_format --keep_going --features=refactor -- //... ``` +## Pip-audit rule + +A custom Bazel rule which generates a script that runs `pip-audit` on the specified requirements file. + +This helps validate vulnerabilities that a certain set of Python requirements might have, which is a good practice and also required for qualification purposes. + +For more information, read the [pip-audit documentation](https://pypi.org/project/pip-audit/). + +### Configuration + +The rule supports the following attributes: + +- `requirement`: Mandatory. The requirement file to check for vulnerabilities, e.g., a `requirements.txt` file. Locked files with hashes are expected. Non-locked files without hashes will work only with the `no_deps` option set. +- `no_deps`: Optional. Defaults to `False`. If set, pip-audit will not check for dependencies of the packages in the requirements file. Required for non-locked requirement files. +- `index_url`: Optional. If set, overrides the index URL used by the pip-audit command. If not provided, gets the index from the requirement file or uses the default PyPI index. + +### How to use + +To use the pip-audit rule, load it from the quality module and create a target with your requirements file: + +```starlark +# BUILD +load("@swf_bazel_rules_quality//quality:defs.bzl", "pip_audit_rule") + +pip_audit_rule( + name = "pip_audit", + requirement = "requirements.txt", +) +``` + +To run the respective target: + +```bash +bazel run //:pip_audit +``` + +In case of multiple requirements files, it is necessary to create multiple rules and execute each one of them. + ## Custom Pytest Bazel's default [pytest rule](https://bazel.build/reference/be/python#py_test) rule was not designed specifically for [Python's pytest](https://github.com/pytest-dev/pytest). Therefore, one has to do some additional scripting in order to use pytest framework with Python adn Bazel. diff --git a/quality/private/python/python_pip_audit_rule.bzl b/quality/private/python/python_pip_audit_rule.bzl new file mode 100644 index 0000000..0fa5200 --- /dev/null +++ b/quality/private/python/python_pip_audit_rule.bzl @@ -0,0 +1,81 @@ +"""Executable rule that runs pip-audit on a requirements file.""" + +def _pip_audit_rule_impl(ctx): + """Implementation of the pip_audit_rule. + + This rule generates a script that runs pip-audit on the specified requirements file. + It allows for an optional index URL to be specified for the pip-audit command. + As this rule does not resolve dependencies, files without hashes, non-locked, will only work + with the `no-deps` option set to true. + """ + + pip_audit_tool = ctx.executable._pip_audit_tool + + script = ctx.actions.declare_file(ctx.label.name) + + requirement_file = ctx.file.requirement + + args_list = ["--disable-pip"] + if ctx.attr.no_deps: + args_list.append("--no-deps") + if ctx.attr.index_url: + args_list.extend(["--index-url", ctx.attr.index_url]) + args_list.extend(["--requirement", requirement_file.short_path]) + + ctx.actions.expand_template( + template = ctx.file._script_template, + output = script, + substitutions = { + "{ARGUMENTS}": " ".join(args_list), + "{PIP_AUDIT_TOOL}": pip_audit_tool.short_path, + }, + is_executable = True, + ) + + runfiles = ctx.runfiles(files = [requirement_file]) + runfiles = runfiles.merge(ctx.attr._pip_audit_tool[DefaultInfo].default_runfiles) + + return [DefaultInfo( + executable = script, + runfiles = runfiles, + )] + +pip_audit_rule = rule( + implementation = _pip_audit_rule_impl, + attrs = { + "index_url": attr.string( + default = "", + doc = ( + "Optional. If set, overrides the index URL used by the pip-audit command." + + "If not provided, gets the index from the requirement file or uses the default PyPI index." + ), + ), + "no_deps": attr.bool( + default = False, + doc = ( + "Optional. If set, pip-audit will not check for dependencies of the packages in the requirements file." + + "Required for non-locked requirement files." + ), + ), + "requirement": attr.label( + allow_single_file = True, + mandatory = True, + doc = ( + "The requirement file to check for vulnerabilities, e.g., a `requirements.txt` file." + + "Locked files, with hashes, are expected. Non locked files, without hashes, will work only with the `no-deps` option set." + ), + ), + "_pip_audit_tool": attr.label( + default = "@swf_bazel_rules_quality//quality/private/python:pip_audit_entry_point", + executable = True, + cfg = "exec", + doc = "Bazel py_binary target with python's pip-audit package entry point.", + ), + "_script_template": attr.label( + default = "//quality/private/python/tools:pip_audit_runner_template", + allow_single_file = True, + doc = "Pip-audit runner template script.", + ), + }, + executable = True, +) diff --git a/quality/private/python/tools/BUILD b/quality/private/python/tools/BUILD index db1d6e4..1a33f35 100644 --- a/quality/private/python/tools/BUILD +++ b/quality/private/python/tools/BUILD @@ -71,3 +71,9 @@ py_binary( "@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common", ], ) + +filegroup( + name = "pip_audit_runner_template", + srcs = ["pip_audit_runner.sh.template"], + visibility = ["//visibility:public"], +) diff --git a/quality/private/python/tools/pip_audit_runner.sh.template b/quality/private/python/tools/pip_audit_runner.sh.template new file mode 100644 index 0000000..5ebad37 --- /dev/null +++ b/quality/private/python/tools/pip_audit_runner.sh.template @@ -0,0 +1,2 @@ +#!/bin/bash +exec {PIP_AUDIT_TOOL} {ARGUMENTS} From 54527e625e87c0d829696173f9a77a0ace1114fb Mon Sep 17 00:00:00 2001 From: "jan.nowotsch" Date: Thu, 24 Jul 2025 11:50:15 +0200 Subject: [PATCH 60/74] [pycoverage] Move pycoverage from quality-tools Move the pycoverage implementation from the quality-tools here. Since pycoverage is a bazel integration it should rather be used by quality-tools but provided by bazel-rules-quality. --- quality/private/python/pycoverage/BUILD | 23 ++++++++ .../python/pycoverage/output_generator.py | 58 +++++++++++++++++++ .../python/pycoverage/report_generator.py | 58 +++++++++++++++++++ quality/private/python/pycoverage/test/BUILD | 24 ++++++++ .../pycoverage/test/data/lcov_files.tmp | 3 + .../test/data/lcov_files_no_py_targets.tmp | 2 + .../test/test_pycoverage_output_generator.py | 48 +++++++++++++++ .../test/test_pycoverage_report_generator.py | 52 +++++++++++++++++ 8 files changed, 268 insertions(+) create mode 100644 quality/private/python/pycoverage/BUILD create mode 100755 quality/private/python/pycoverage/output_generator.py create mode 100755 quality/private/python/pycoverage/report_generator.py create mode 100644 quality/private/python/pycoverage/test/BUILD create mode 100644 quality/private/python/pycoverage/test/data/lcov_files.tmp create mode 100644 quality/private/python/pycoverage/test/data/lcov_files_no_py_targets.tmp create mode 100644 quality/private/python/pycoverage/test/test_pycoverage_output_generator.py create mode 100644 quality/private/python/pycoverage/test/test_pycoverage_report_generator.py diff --git a/quality/private/python/pycoverage/BUILD b/quality/private/python/pycoverage/BUILD new file mode 100644 index 0000000..906de66 --- /dev/null +++ b/quality/private/python/pycoverage/BUILD @@ -0,0 +1,23 @@ +# Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG). All rights reserved. + +load("@rules_python//python:defs.bzl", "py_binary") +load("@swf_bazel_rules_quality_pip_hub//:loaders.bzl", "pkg") + +package(default_visibility = ["//visibility:public"]) + +py_binary( + name = "report_generator", + srcs = ["report_generator.py"], + visibility = ["//:__subpackages__"], + deps = [ + pkg("termcolor"), + pkg("coverage"), + ], +) + +py_binary( + name = "output_generator", + srcs = ["output_generator.py"], + visibility = ["//:__subpackages__"], + deps = [pkg("termcolor")], +) diff --git a/quality/private/python/pycoverage/output_generator.py b/quality/private/python/pycoverage/output_generator.py new file mode 100755 index 0000000..0deb439 --- /dev/null +++ b/quality/private/python/pycoverage/output_generator.py @@ -0,0 +1,58 @@ +# Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG). All rights reserved. + +"""Custom coverage report generator for pycoverage.""" + +import argparse +import logging +import os +import shutil +import sys +from pathlib import Path +from pprint import pformat + +from termcolor import colored + + +def main() -> None: + """Main entry point.""" + logging.basicConfig(level=logging.INFO) + logging.info(colored("Running MERGER on all executed tests", "magenta")) + + args = parse_args() + logging.debug(msg=f"Arguments:\n{pformat(args)}") + + # Always create an output file to avoid bazel errors. + args.output_file.touch() + + # Search for the .coverage file. + coverage_file = list(Path(os.environ["RUNFILES_DIR"]).rglob("*.coverage")) + + if not coverage_file: + return + + if len(coverage_file) > 1: + logging.error("Found more than one .coverage file.") + sys.exit(1) + + # Overwrite the empty output file with the found .coverage file. + shutil.copy2(coverage_file[0], args.output_file) + + logging.info(f"Copied {coverage_file[0]} to {args.output_file}.") + + +def parse_args() -> argparse.Namespace: + """Parse args.""" + + parser = argparse.ArgumentParser() + + parser.add_argument("--coverage_dir", type=Path) + parser.add_argument("--filter_sources", action="append") + parser.add_argument("--output_file", type=Path) + parser.add_argument("--source_file_manifest", type=Path) + parser.add_argument("--sources_to_replace_file") + + return parser.parse_args() + + +if __name__ == "__main__": + main() diff --git a/quality/private/python/pycoverage/report_generator.py b/quality/private/python/pycoverage/report_generator.py new file mode 100755 index 0000000..53ad67e --- /dev/null +++ b/quality/private/python/pycoverage/report_generator.py @@ -0,0 +1,58 @@ +# Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG). All rights reserved. + +"""Custom coverage report generator for pycoverage.""" + +import argparse +import logging +from pathlib import Path +from pprint import pformat + +from coverage import Coverage +from termcolor import colored + + +def main() -> None: + """Main entry point.""" + logging.basicConfig(level=logging.INFO) + logging.info(colored("Running REPORTER on all executed tests", "magenta")) + + args = parse_args() + logging.debug(msg=f"Arguments:\n{pformat(args)}") + + # Always create an output file to avoid bazel errors. + args.output_file.touch() + + # Read the reports file + coverage_files = [] + with open(args.reports_file, encoding="utf-8") as reports_file: + for line in reports_file: + coverage_file = Path(line.strip()) + if coverage_file.parts[-1] != "coverage.dat" or coverage_file.stat().st_size == 0: + logging.debug(f"Ignoring {coverage_file}.") + continue + coverage_files.append(str(coverage_file)) + + if not coverage_files: + logging.error("No python coverage reports found.") + return + + # Combine the coverage data + logging.debug(f"Combining coverage data from {len(coverage_files)} files.") + cov = Coverage(data_file=args.output_file) + cov.combine(data_paths=coverage_files, keep=True) + cov.save() + + +def parse_args() -> argparse.Namespace: + """Parse args.""" + + parser = argparse.ArgumentParser() + + parser.add_argument("--output_file", type=Path) + parser.add_argument("--reports_file", type=Path) + + return parser.parse_args() + + +if __name__ == "__main__": + main() diff --git a/quality/private/python/pycoverage/test/BUILD b/quality/private/python/pycoverage/test/BUILD new file mode 100644 index 0000000..9532add --- /dev/null +++ b/quality/private/python/pycoverage/test/BUILD @@ -0,0 +1,24 @@ +# Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG). All rights reserved. + +load("@swf_bazel_rules_quality//quality:defs.bzl", "py_pytest") +load("@swf_bazel_rules_quality_pip_hub//:loaders.bzl", "pkg") + +py_pytest( + name = "test_pycoverage_report_generator", + srcs = ["test_pycoverage_report_generator.py"], + data = glob(["data/**"]), + deps = [ + "@swf_bazel_rules_quality//quality/private/python/pycoverage:report_generator", + pkg("coverage"), + ], +) + +py_pytest( + name = "test_pycoverage_output_generator", + srcs = ["test_pycoverage_output_generator.py"], + data = glob(["data/**"]), + deps = [ + "@swf_bazel_rules_quality//quality/private/python/pycoverage:output_generator", + pkg("coverage"), + ], +) diff --git a/quality/private/python/pycoverage/test/data/lcov_files.tmp b/quality/private/python/pycoverage/test/data/lcov_files.tmp new file mode 100644 index 0000000..ba12b4d --- /dev/null +++ b/quality/private/python/pycoverage/test/data/lcov_files.tmp @@ -0,0 +1,3 @@ +bazel-out/k8-fastbuild/testlogs/common/utility/test/test_string_conversion/coverage.dat +bazel-out/k8-fastbuild/testlogs/common/communication/common_crc_v1_validate/coverage.dat +bazel-out/k8-fastbuild/testlogs/common/communication/test/unit_test/coverage.dat diff --git a/quality/private/python/pycoverage/test/data/lcov_files_no_py_targets.tmp b/quality/private/python/pycoverage/test/data/lcov_files_no_py_targets.tmp new file mode 100644 index 0000000..d2cfbe1 --- /dev/null +++ b/quality/private/python/pycoverage/test/data/lcov_files_no_py_targets.tmp @@ -0,0 +1,2 @@ +bazel-out/k8-fastbuild/testlogs/common/signal_processing/debounce/debounce/coverage.dat +bazel-out/k8-fastbuild/testlogs/common/geometry/algorithm/test/algorithm_unit_tests/baseline_coverage.dat diff --git a/quality/private/python/pycoverage/test/test_pycoverage_output_generator.py b/quality/private/python/pycoverage/test/test_pycoverage_output_generator.py new file mode 100644 index 0000000..8f2e123 --- /dev/null +++ b/quality/private/python/pycoverage/test/test_pycoverage_output_generator.py @@ -0,0 +1,48 @@ +# Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG). All rights reserved. + +"""Tests for the pycoverage merger module.""" + +from pathlib import Path + +import pytest + +from quality.private.python.pycoverage import output_generator as merger + + +@pytest.mark.parametrize( + "coverage_files, output_file", + [ + ([Path(".coverage")], Path("coverage.dat")), # Happy path. + ([], Path("coverage.dat")), # Bad path: no coverage files found. + ( + [Path("dir1/.coverage"), Path("dir2/.coverage")], + Path("coverage.dat"), + ), # Bad path: More than one coverage files. + ], +) +def test_pycoverage_merger_main(mocker, caplog, coverage_files, output_file): + """Tests pycoverage merger main function.""" + mocker.patch("pathlib.Path.rglob", return_value=coverage_files) + mocker.patch("pathlib.Path.touch", lambda self: None) + mocker.patch("shutil.copy2", lambda *args, **kwargs: None) + mocker.patch( + "sys.argv", + new=[ + "merger", + "--output_file", + str(output_file), + ], + ) + + if len(coverage_files) > 1: + with pytest.raises(SystemExit): + merger.main() + assert "Found more than one .coverage file." in caplog.text + return + + merger.main() + + if not coverage_files: + return + + assert f"Copied {coverage_files[0]} to {output_file}." in caplog.text diff --git a/quality/private/python/pycoverage/test/test_pycoverage_report_generator.py b/quality/private/python/pycoverage/test/test_pycoverage_report_generator.py new file mode 100644 index 0000000..0416ea0 --- /dev/null +++ b/quality/private/python/pycoverage/test/test_pycoverage_report_generator.py @@ -0,0 +1,52 @@ +# Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG). All rights reserved. + +"""Tests for the pycoverage reporter module.""" + +from pathlib import Path + +import pytest + +from quality.private.python.pycoverage import report_generator as reporter + + +@pytest.mark.parametrize( + "reports_file, st_size, output_file", + [ + ("lcov_files.tmp", [1, 1, 1], Path(__file__)), # Happy path. + ( + "lcov_files_no_py_targets.tmp", + [0, 1], + Path(__file__), + ), # Bad path: no python targets. + ( + "lcov_files.tmp", + [1, 1, 1], + Path("non_existing"), + ), # Bad path: output_file not generated. + ], +) +def test_pycoverage_reporter_main(mocker, caplog, reports_file, st_size, output_file): + """Tests pycoverage reporter main function.""" + mocker.patch("pathlib.Path.touch", lambda self: None) + # mock st_size + mock_stat = mocker.Mock() + mock_stat.st_size = st_size.pop(0) + mocker.patch("pathlib.Path.stat", return_value=mock_stat) + mocker.patch("coverage.Coverage.combine", lambda *args, **kwargs: None) + mocker.patch("coverage.Coverage.save", lambda self: None) + mocker.patch( + "sys.argv", + new=[ + "reporter", + "--output_file", + str(output_file), + "--reports_file", + str(Path(__file__).parent / "data" / reports_file), + ], + ) + + reporter.main() + + if reports_file == "lcov_files_no_py_targets.tmp": + assert "No python coverage reports found." in caplog.text + return From 539dd0f6b9b3fa928ab483d845fcf2ad7189abef Mon Sep 17 00:00:00 2001 From: "jan.nowotsch" Date: Thu, 24 Jul 2025 14:37:26 +0200 Subject: [PATCH 61/74] [pycoverage] Add entry point Add entry point, allowing pycoverage to be called as a py_binary. --- quality/private/python/BUILD | 7 +++++++ quality/private/python/pycoverage_entry_point.py | 11 +++++++++++ 2 files changed, 18 insertions(+) create mode 100644 quality/private/python/pycoverage_entry_point.py diff --git a/quality/private/python/BUILD b/quality/private/python/BUILD index 9d5936b..c067e1c 100644 --- a/quality/private/python/BUILD +++ b/quality/private/python/BUILD @@ -76,3 +76,10 @@ py_binary( visibility = ["//visibility:public"], deps = [pkg("pip-audit")], ) + +py_binary( + name = "pycoverage_entry_point", + srcs = ["pycoverage_entry_point.py"], + visibility = ["//visibility:public"], + deps = [pkg("coverage")], +) diff --git a/quality/private/python/pycoverage_entry_point.py b/quality/private/python/pycoverage_entry_point.py new file mode 100644 index 0000000..f30fea4 --- /dev/null +++ b/quality/private/python/pycoverage_entry_point.py @@ -0,0 +1,11 @@ +"""Entry point for pycoverage. + +This executes pycoverage by importing, thus executing, its main entry point. +""" + +# ruff: noqa: F401 +# We do not want to use __main__ but only import it. +# That is because when we import it, python already runs the tool entry point. + +if __name__ == "__main__": + from coverage import __main__ # type: ignore[import-untyped] From 3fed409e4b68d1e6c6106dc4f12ad60b9b1e6713 Mon Sep 17 00:00:00 2001 From: "jan.nowotsch" Date: Tue, 29 Jul 2025 09:37:19 +0200 Subject: [PATCH 62/74] [pycoverage] Adjust generator names Update the names of output- and report generators in log messages and tests according to the actual file name. --- quality/private/python/pycoverage/output_generator.py | 2 +- quality/private/python/pycoverage/report_generator.py | 2 +- .../test/test_pycoverage_output_generator.py | 10 +++++----- .../test/test_pycoverage_report_generator.py | 10 +++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/quality/private/python/pycoverage/output_generator.py b/quality/private/python/pycoverage/output_generator.py index 0deb439..9e9cedd 100755 --- a/quality/private/python/pycoverage/output_generator.py +++ b/quality/private/python/pycoverage/output_generator.py @@ -16,7 +16,7 @@ def main() -> None: """Main entry point.""" logging.basicConfig(level=logging.INFO) - logging.info(colored("Running MERGER on all executed tests", "magenta")) + logging.info(colored("Running output-generator on all executed tests", "magenta")) args = parse_args() logging.debug(msg=f"Arguments:\n{pformat(args)}") diff --git a/quality/private/python/pycoverage/report_generator.py b/quality/private/python/pycoverage/report_generator.py index 53ad67e..e426fed 100755 --- a/quality/private/python/pycoverage/report_generator.py +++ b/quality/private/python/pycoverage/report_generator.py @@ -14,7 +14,7 @@ def main() -> None: """Main entry point.""" logging.basicConfig(level=logging.INFO) - logging.info(colored("Running REPORTER on all executed tests", "magenta")) + logging.info(colored("Running report-generator on all executed tests", "magenta")) args = parse_args() logging.debug(msg=f"Arguments:\n{pformat(args)}") diff --git a/quality/private/python/pycoverage/test/test_pycoverage_output_generator.py b/quality/private/python/pycoverage/test/test_pycoverage_output_generator.py index 8f2e123..164fc2e 100644 --- a/quality/private/python/pycoverage/test/test_pycoverage_output_generator.py +++ b/quality/private/python/pycoverage/test/test_pycoverage_output_generator.py @@ -1,12 +1,12 @@ # Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG). All rights reserved. -"""Tests for the pycoverage merger module.""" +"""Tests for the pycoverage output_generator module.""" from pathlib import Path import pytest -from quality.private.python.pycoverage import output_generator as merger +from quality.private.python.pycoverage import output_generator @pytest.mark.parametrize( @@ -21,14 +21,14 @@ ], ) def test_pycoverage_merger_main(mocker, caplog, coverage_files, output_file): - """Tests pycoverage merger main function.""" + """Tests pycoverage output_generator main function.""" mocker.patch("pathlib.Path.rglob", return_value=coverage_files) mocker.patch("pathlib.Path.touch", lambda self: None) mocker.patch("shutil.copy2", lambda *args, **kwargs: None) mocker.patch( "sys.argv", new=[ - "merger", + "output_generator", "--output_file", str(output_file), ], @@ -40,7 +40,7 @@ def test_pycoverage_merger_main(mocker, caplog, coverage_files, output_file): assert "Found more than one .coverage file." in caplog.text return - merger.main() + output_generator.main() if not coverage_files: return diff --git a/quality/private/python/pycoverage/test/test_pycoverage_report_generator.py b/quality/private/python/pycoverage/test/test_pycoverage_report_generator.py index 0416ea0..69fde5f 100644 --- a/quality/private/python/pycoverage/test/test_pycoverage_report_generator.py +++ b/quality/private/python/pycoverage/test/test_pycoverage_report_generator.py @@ -1,12 +1,12 @@ # Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG). All rights reserved. -"""Tests for the pycoverage reporter module.""" +"""Tests for the pycoverage report_generator module.""" from pathlib import Path import pytest -from quality.private.python.pycoverage import report_generator as reporter +from quality.private.python.pycoverage import report_generator @pytest.mark.parametrize( @@ -26,7 +26,7 @@ ], ) def test_pycoverage_reporter_main(mocker, caplog, reports_file, st_size, output_file): - """Tests pycoverage reporter main function.""" + """Tests pycoverage report_generator main function.""" mocker.patch("pathlib.Path.touch", lambda self: None) # mock st_size mock_stat = mocker.Mock() @@ -37,7 +37,7 @@ def test_pycoverage_reporter_main(mocker, caplog, reports_file, st_size, output_ mocker.patch( "sys.argv", new=[ - "reporter", + "report_generator", "--output_file", str(output_file), "--reports_file", @@ -45,7 +45,7 @@ def test_pycoverage_reporter_main(mocker, caplog, reports_file, st_size, output_ ], ) - reporter.main() + report_generator.main() if reports_file == "lcov_files_no_py_targets.tmp": assert "No python coverage reports found." in caplog.text From 99bc74e8bc855f01a9128f33134cf097362a4156 Mon Sep 17 00:00:00 2001 From: "jan.nowotsch" Date: Tue, 29 Jul 2025 09:40:16 +0200 Subject: [PATCH 63/74] [pycoverage] Update script exit code handling Update the way the pycoverage generators report their exit code, actually returning something from the main function and use sys.exit() to report that value to the system. --- quality/private/python/pycoverage/output_generator.py | 10 ++++++---- quality/private/python/pycoverage/report_generator.py | 9 ++++++--- .../test/test_pycoverage_output_generator.py | 3 +-- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/quality/private/python/pycoverage/output_generator.py b/quality/private/python/pycoverage/output_generator.py index 9e9cedd..110cd67 100755 --- a/quality/private/python/pycoverage/output_generator.py +++ b/quality/private/python/pycoverage/output_generator.py @@ -13,7 +13,7 @@ from termcolor import colored -def main() -> None: +def main() -> int: """Main entry point.""" logging.basicConfig(level=logging.INFO) logging.info(colored("Running output-generator on all executed tests", "magenta")) @@ -28,17 +28,19 @@ def main() -> None: coverage_file = list(Path(os.environ["RUNFILES_DIR"]).rglob("*.coverage")) if not coverage_file: - return + return 0 if len(coverage_file) > 1: logging.error("Found more than one .coverage file.") - sys.exit(1) + return 1 # Overwrite the empty output file with the found .coverage file. shutil.copy2(coverage_file[0], args.output_file) logging.info(f"Copied {coverage_file[0]} to {args.output_file}.") + return 0 + def parse_args() -> argparse.Namespace: """Parse args.""" @@ -55,4 +57,4 @@ def parse_args() -> argparse.Namespace: if __name__ == "__main__": - main() + sys.exit(main()) diff --git a/quality/private/python/pycoverage/report_generator.py b/quality/private/python/pycoverage/report_generator.py index e426fed..cb1224f 100755 --- a/quality/private/python/pycoverage/report_generator.py +++ b/quality/private/python/pycoverage/report_generator.py @@ -4,6 +4,7 @@ import argparse import logging +import sys from pathlib import Path from pprint import pformat @@ -11,7 +12,7 @@ from termcolor import colored -def main() -> None: +def main() -> int: """Main entry point.""" logging.basicConfig(level=logging.INFO) logging.info(colored("Running report-generator on all executed tests", "magenta")) @@ -34,7 +35,7 @@ def main() -> None: if not coverage_files: logging.error("No python coverage reports found.") - return + return 0 # Combine the coverage data logging.debug(f"Combining coverage data from {len(coverage_files)} files.") @@ -42,6 +43,8 @@ def main() -> None: cov.combine(data_paths=coverage_files, keep=True) cov.save() + return 0 + def parse_args() -> argparse.Namespace: """Parse args.""" @@ -55,4 +58,4 @@ def parse_args() -> argparse.Namespace: if __name__ == "__main__": - main() + sys.exit(main()) diff --git a/quality/private/python/pycoverage/test/test_pycoverage_output_generator.py b/quality/private/python/pycoverage/test/test_pycoverage_output_generator.py index 164fc2e..1689148 100644 --- a/quality/private/python/pycoverage/test/test_pycoverage_output_generator.py +++ b/quality/private/python/pycoverage/test/test_pycoverage_output_generator.py @@ -35,8 +35,7 @@ def test_pycoverage_merger_main(mocker, caplog, coverage_files, output_file): ) if len(coverage_files) > 1: - with pytest.raises(SystemExit): - merger.main() + assert output_generator.main() == 1 assert "Found more than one .coverage file." in caplog.text return From 58c41c320e0de195ddd34cc6e450f40695462cd5 Mon Sep 17 00:00:00 2001 From: "jan.nowotsch" Date: Tue, 29 Jul 2025 12:20:27 +0200 Subject: [PATCH 64/74] [pycoverage] Refactor report-generator Update the report-generator implementation that identifies coverage data files. --- .../private/python/pycoverage/report_generator.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/quality/private/python/pycoverage/report_generator.py b/quality/private/python/pycoverage/report_generator.py index cb1224f..ff8da66 100755 --- a/quality/private/python/pycoverage/report_generator.py +++ b/quality/private/python/pycoverage/report_generator.py @@ -12,6 +12,14 @@ from termcolor import colored +def coverage_file(line: str) -> bool: + """Check if the given string is a path to a coverage.dat file.""" + + file = Path(line) + + return file.parts[-1] == "coverage.dat" and file.stat().st_size != 0 + + def main() -> int: """Main entry point.""" logging.basicConfig(level=logging.INFO) @@ -26,12 +34,7 @@ def main() -> int: # Read the reports file coverage_files = [] with open(args.reports_file, encoding="utf-8") as reports_file: - for line in reports_file: - coverage_file = Path(line.strip()) - if coverage_file.parts[-1] != "coverage.dat" or coverage_file.stat().st_size == 0: - logging.debug(f"Ignoring {coverage_file}.") - continue - coverage_files.append(str(coverage_file)) + coverage_files = list(filter(coverage_file, map(lambda line: line.strip(), reports_file.readlines()))) if not coverage_files: logging.error("No python coverage reports found.") From 3500a726640be7705d78188f56582cec50b8014a Mon Sep 17 00:00:00 2001 From: "jan.nowotsch" Date: Thu, 31 Jul 2025 16:04:36 +0200 Subject: [PATCH 65/74] [all] Rename to bazel-tools-python Adjust identifiers to new repository. --- quality/private/python/BUILD | 4 +- quality/private/python/README.md | 34 +++++++-------- quality/private/python/py_pytest.bzl | 6 +-- quality/private/python/pycoverage/BUILD | 2 +- quality/private/python/pycoverage/test/BUILD | 8 ++-- .../private/python/python_collect_aspect.bzl | 4 +- .../private/python/python_pip_audit_rule.bzl | 2 +- quality/private/python/python_tool_aspect.bzl | 42 +++++++++---------- quality/private/python/tools/BUILD | 26 ++++++------ quality/private/python/tools/test/BUILD | 4 +- 10 files changed, 66 insertions(+), 66 deletions(-) diff --git a/quality/private/python/BUILD b/quality/private/python/BUILD index c067e1c..919a4c6 100644 --- a/quality/private/python/BUILD +++ b/quality/private/python/BUILD @@ -1,5 +1,5 @@ +load("@bazel_tools_python_pip_hub//:loaders.bzl", "pkg") load("@rules_python//python:defs.bzl", "py_binary", "py_library") -load("@swf_bazel_rules_quality_pip_hub//:loaders.bzl", "pkg") py_binary( name = "pylint_entry_point", @@ -23,7 +23,7 @@ py_library( label_flag( name = "py_test_deps", - build_setting_default = "@swf_bazel_rules_quality//quality/private/python:default_py_test_deps", + build_setting_default = "@bazel_tools_python//quality/private/python:default_py_test_deps", visibility = ["//visibility:public"], ) diff --git a/quality/private/python/README.md b/quality/private/python/README.md index d295f46..cbf8ab8 100644 --- a/quality/private/python/README.md +++ b/quality/private/python/README.md @@ -29,7 +29,7 @@ To achieve that, a custom configuration rule must be created using the [`python_ ```py # path/to/BUILD -load("@swf_bazel_rules_quality//quality:defs.bzl", "python_tool_config") +load("@bazel_tools_python//quality:defs.bzl", "python_tool_config") filegroup( name = "pyproject_toml", srcs = ["pyproject.toml"], @@ -43,12 +43,12 @@ python_tool_config( Finally, each selected tool configuration can be overloaded with the newly created custom configuration target. target overload shortcut can be added to the `.bazelrc` file: # .bazelrc -build:pylint --@swf_bazel_rules_quality//quality:quality_pylint_config=//path/to:pylint_config -build:mypy --@swf_bazel_rules_quality//quality:quality_mypy_config=//path/to:python_config -build:black --@swf_bazel_rules_quality//quality:quality_black_config=//path/to:python_config -build:isort --@swf_bazel_rules_quality//quality:quality_isort_config=//path/to:python_config -build:ruff_check --@swf_bazel_rules_quality//quality:quality_ruff_config=//path/to:python_config -build:ruff_format --@swf_bazel_rules_quality//quality:quality_ruff_config=//path/to:python_config +build:pylint --@bazel_tools_python//quality:quality_pylint_config=//path/to:pylint_config +build:mypy --@bazel_tools_python//quality:quality_mypy_config=//path/to:python_config +build:black --@bazel_tools_python//quality:quality_black_config=//path/to:python_config +build:isort --@bazel_tools_python//quality:quality_isort_config=//path/to:python_config +build:ruff_check --@bazel_tools_python//quality:quality_ruff_config=//path/to:python_config +build:ruff_format --@bazel_tools_python//quality:quality_ruff_config=//path/to:python_config ``` ### Basic Usage @@ -73,21 +73,21 @@ Finally in the `.bazelrc` file we can configure the build for each tool. # .bazelrc build:pylint --output_groups=python_tool_output -build:pylint --aspects=@swf_bazel_rules_quality//quality:defs.bzl%pylint_aspect +build:pylint --aspects=@bazel_tools_python//quality:defs.bzl%pylint_aspect build:black --output_groups=python_tool_output -build:black --aspects=@swf_bazel_rules_quality//quality:defs.bzl%black_aspect +build:black --aspects=@bazel_tools_python//quality:defs.bzl%black_aspect build:isort --output_groups=python_tool_output -build:isort --aspects=@swf_bazel_rules_quality//quality:defs.bzl%isort_aspect +build:isort --aspects=@bazel_tools_python//quality:defs.bzl%isort_aspect build:mypy --output_groups=python_tool_output -build:mypy --aspects=@swf_bazel_rules_quality//quality:defs.bzl%mypy_aspect +build:mypy --aspects=@bazel_tools_python//quality:defs.bzl%mypy_aspect build:ruff_check --output_groups=python_tool_output -build:ruff_check --aspects=@swf_bazel_rules_quality//quality:defs.bzl%ruff_check_aspect +build:ruff_check --aspects=@bazel_tools_python//quality:defs.bzl%ruff_check_aspect build:ruff_format --output_groups=python_tool_output -build:ruff_format --aspects=@swf_bazel_rules_quality//quality:defs.bzl%ruff_format_aspect +build:ruff_format --aspects=@bazel_tools_python//quality:defs.bzl%ruff_format_aspect ``` ### Available Tools @@ -163,7 +163,7 @@ To use the pip-audit rule, load it from the quality module and create a target w ```starlark # BUILD -load("@swf_bazel_rules_quality//quality:defs.bzl", "pip_audit_rule") +load("@bazel_tools_python//quality:defs.bzl", "pip_audit_rule") pip_audit_rule( name = "pip_audit", @@ -193,14 +193,14 @@ To use a custom configuration file, one can add the following command line in th ```bash # .bazelrc -build --@swf_bazel_rules_quality//quality:quality_pytest_config=//quality/private/python/support:awesome_pyproject +build --@bazel_tools_python//quality:quality_pytest_config=//quality/private/python/support:awesome_pyproject ``` To use a custom default dependency list, one can add the following command line in the `.bazelrc`: ```bash # .bazelrc -build --@swf_bazel_rules_quality//quality/private/python:py_test_deps=//:py_custom_test_deps +build --@bazel_tools_python//quality/private/python:py_test_deps=//:py_custom_test_deps ``` ### How to use @@ -209,7 +209,7 @@ The custom `py_pytest` rule is basically a drop-in replacement to Bazel's defaul ```bash # BUILD -load("@swf_bazel_rules_quality//quality:defs.bzl", "py_pytest") +load("@bazel_tools_python//quality:defs.bzl", "py_pytest") py_pytest( name = "test_dummy", diff --git a/quality/private/python/py_pytest.bzl b/quality/private/python/py_pytest.bzl index e26dc7b..533cbcd 100644 --- a/quality/private/python/py_pytest.bzl +++ b/quality/private/python/py_pytest.bzl @@ -2,14 +2,14 @@ load("@rules_python//python:defs.bzl", "py_test") -_CONFIG = "@swf_bazel_rules_quality//quality:quality_pytest_config" +_CONFIG = "@bazel_tools_python//quality:quality_pytest_config" -_RUNNER_LABEL = "@swf_bazel_rules_quality//quality/private/python/tools:pytest_runner" +_RUNNER_LABEL = "@bazel_tools_python//quality/private/python/tools:pytest_runner" _RUNNER_FILE = "pytest_runner.py" _BASE_SRCS = [] _BASE_DATA = [] -_BASE_DEPS = "@swf_bazel_rules_quality//quality/private/python:py_test_deps" +_BASE_DEPS = "@bazel_tools_python//quality/private/python:py_test_deps" def py_pytest( name, diff --git a/quality/private/python/pycoverage/BUILD b/quality/private/python/pycoverage/BUILD index 906de66..d12613a 100644 --- a/quality/private/python/pycoverage/BUILD +++ b/quality/private/python/pycoverage/BUILD @@ -1,7 +1,7 @@ # Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG). All rights reserved. +load("@bazel_tools_python_pip_hub//:loaders.bzl", "pkg") load("@rules_python//python:defs.bzl", "py_binary") -load("@swf_bazel_rules_quality_pip_hub//:loaders.bzl", "pkg") package(default_visibility = ["//visibility:public"]) diff --git a/quality/private/python/pycoverage/test/BUILD b/quality/private/python/pycoverage/test/BUILD index 9532add..faee4da 100644 --- a/quality/private/python/pycoverage/test/BUILD +++ b/quality/private/python/pycoverage/test/BUILD @@ -1,14 +1,14 @@ # Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG). All rights reserved. -load("@swf_bazel_rules_quality//quality:defs.bzl", "py_pytest") -load("@swf_bazel_rules_quality_pip_hub//:loaders.bzl", "pkg") +load("@bazel_tools_python//quality:defs.bzl", "py_pytest") +load("@bazel_tools_python_pip_hub//:loaders.bzl", "pkg") py_pytest( name = "test_pycoverage_report_generator", srcs = ["test_pycoverage_report_generator.py"], data = glob(["data/**"]), deps = [ - "@swf_bazel_rules_quality//quality/private/python/pycoverage:report_generator", + "@bazel_tools_python//quality/private/python/pycoverage:report_generator", pkg("coverage"), ], ) @@ -18,7 +18,7 @@ py_pytest( srcs = ["test_pycoverage_output_generator.py"], data = glob(["data/**"]), deps = [ - "@swf_bazel_rules_quality//quality/private/python/pycoverage:output_generator", + "@bazel_tools_python//quality/private/python/pycoverage:output_generator", pkg("coverage"), ], ) diff --git a/quality/private/python/python_collect_aspect.bzl b/quality/private/python/python_collect_aspect.bzl index edab4fd..2715e9a 100644 --- a/quality/private/python/python_collect_aspect.bzl +++ b/quality/private/python/python_collect_aspect.bzl @@ -1,7 +1,7 @@ """Aspect that collects python targets information and output it to a provider.""" -load("@swf_bazel_rules_quality//quality/private/python:python_helper.bzl", "is_valid_label") -load("@swf_bazel_rules_quality//quality/private/python:python_providers.bzl", "PythonCollectInfo") +load("@bazel_tools_python//quality/private/python:python_helper.bzl", "is_valid_label") +load("@bazel_tools_python//quality/private/python:python_providers.bzl", "PythonCollectInfo") def _get_collect_transitive_outputs(ctx): deps = [] diff --git a/quality/private/python/python_pip_audit_rule.bzl b/quality/private/python/python_pip_audit_rule.bzl index 0fa5200..072313b 100644 --- a/quality/private/python/python_pip_audit_rule.bzl +++ b/quality/private/python/python_pip_audit_rule.bzl @@ -66,7 +66,7 @@ pip_audit_rule = rule( ), ), "_pip_audit_tool": attr.label( - default = "@swf_bazel_rules_quality//quality/private/python:pip_audit_entry_point", + default = "@bazel_tools_python//quality/private/python:pip_audit_entry_point", executable = True, cfg = "exec", doc = "Bazel py_binary target with python's pip-audit package entry point.", diff --git a/quality/private/python/python_tool_aspect.bzl b/quality/private/python/python_tool_aspect.bzl index b6d8b39..bf58323 100644 --- a/quality/private/python/python_tool_aspect.bzl +++ b/quality/private/python/python_tool_aspect.bzl @@ -1,8 +1,8 @@ """Aspect that call a tool runner on top of a python target.""" -load("@swf_bazel_rules_quality//quality/private/python:python_collect_aspect.bzl", "python_collect_aspect") -load("@swf_bazel_rules_quality//quality/private/python:python_helper.bzl", "is_valid_label") -load("@swf_bazel_rules_quality//quality/private/python:python_providers.bzl", "PythonCollectInfo", "PythonToolInfo") +load("@bazel_tools_python//quality/private/python:python_collect_aspect.bzl", "python_collect_aspect") +load("@bazel_tools_python//quality/private/python:python_helper.bzl", "is_valid_label") +load("@bazel_tools_python//quality/private/python:python_providers.bzl", "PythonCollectInfo", "PythonToolInfo") _AVAILABLE_FEATURES = [ # Tools that are able to refactor will automatically fix findings using this feature. @@ -143,37 +143,37 @@ def _python_tool_aspect(tool, runner, config): ) pylint_aspect = _python_tool_aspect( - tool = "@swf_bazel_rules_quality//quality/private/python:pylint_entry_point", - runner = "@swf_bazel_rules_quality//quality/private/python/tools:pylint", - config = "@swf_bazel_rules_quality//quality:quality_pylint_config", + tool = "@bazel_tools_python//quality/private/python:pylint_entry_point", + runner = "@bazel_tools_python//quality/private/python/tools:pylint", + config = "@bazel_tools_python//quality:quality_pylint_config", ) black_aspect = _python_tool_aspect( - tool = "@swf_bazel_rules_quality//quality/private/python:black_entry_point", - runner = "@swf_bazel_rules_quality//quality/private/python/tools:black", - config = "@swf_bazel_rules_quality//quality:quality_black_config", + tool = "@bazel_tools_python//quality/private/python:black_entry_point", + runner = "@bazel_tools_python//quality/private/python/tools:black", + config = "@bazel_tools_python//quality:quality_black_config", ) isort_aspect = _python_tool_aspect( - tool = "@swf_bazel_rules_quality//quality/private/python:isort_entry_point", - runner = "@swf_bazel_rules_quality//quality/private/python/tools:isort", - config = "@swf_bazel_rules_quality//quality:quality_isort_config", + tool = "@bazel_tools_python//quality/private/python:isort_entry_point", + runner = "@bazel_tools_python//quality/private/python/tools:isort", + config = "@bazel_tools_python//quality:quality_isort_config", ) mypy_aspect = _python_tool_aspect( - tool = "@swf_bazel_rules_quality//quality/private/python:mypy_entry_point", - runner = "@swf_bazel_rules_quality//quality/private/python/tools:mypy", - config = "@swf_bazel_rules_quality//quality:quality_mypy_config", + tool = "@bazel_tools_python//quality/private/python:mypy_entry_point", + runner = "@bazel_tools_python//quality/private/python/tools:mypy", + config = "@bazel_tools_python//quality:quality_mypy_config", ) ruff_check_aspect = _python_tool_aspect( - tool = "@swf_bazel_rules_quality//quality/private/python:ruff_entry_point", - runner = "@swf_bazel_rules_quality//quality/private/python/tools:ruff_check", - config = "@swf_bazel_rules_quality//quality:quality_ruff_config", + tool = "@bazel_tools_python//quality/private/python:ruff_entry_point", + runner = "@bazel_tools_python//quality/private/python/tools:ruff_check", + config = "@bazel_tools_python//quality:quality_ruff_config", ) ruff_format_aspect = _python_tool_aspect( - tool = "@swf_bazel_rules_quality//quality/private/python:ruff_entry_point", - runner = "@swf_bazel_rules_quality//quality/private/python/tools:ruff_format", - config = "@swf_bazel_rules_quality//quality:quality_ruff_config", + tool = "@bazel_tools_python//quality/private/python:ruff_entry_point", + runner = "@bazel_tools_python//quality/private/python/tools:ruff_format", + config = "@bazel_tools_python//quality:quality_ruff_config", ) diff --git a/quality/private/python/tools/BUILD b/quality/private/python/tools/BUILD index 1a33f35..4dbbeca 100644 --- a/quality/private/python/tools/BUILD +++ b/quality/private/python/tools/BUILD @@ -1,5 +1,5 @@ +load("@bazel_tools_python_pip_hub//:loaders.bzl", "pkg") load("@rules_python//python:defs.bzl", "py_binary", "py_library") -load("@swf_bazel_rules_quality_pip_hub//:loaders.bzl", "pkg") py_binary( name = "pytest_runner", @@ -17,37 +17,37 @@ py_library( py_binary( name = "pylint", srcs = ["pylint_runner.py"], - data = ["@swf_bazel_rules_quality//quality/private/python:pylint_entry_point"], + data = ["@bazel_tools_python//quality/private/python:pylint_entry_point"], main = "pylint_runner.py", visibility = ["//visibility:public"], - deps = ["@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common"], + deps = ["@bazel_tools_python//quality/private/python/tools:python_tool_common"], ) py_binary( name = "black", srcs = ["black_runner.py"], - data = ["@swf_bazel_rules_quality//quality/private/python:black_entry_point"], + data = ["@bazel_tools_python//quality/private/python:black_entry_point"], main = "black_runner.py", visibility = ["//visibility:public"], - deps = ["@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common"], + deps = ["@bazel_tools_python//quality/private/python/tools:python_tool_common"], ) py_binary( name = "isort", srcs = ["isort_runner.py"], - data = ["@swf_bazel_rules_quality//quality/private/python:isort_entry_point"], + data = ["@bazel_tools_python//quality/private/python:isort_entry_point"], main = "isort_runner.py", visibility = ["//visibility:public"], - deps = ["@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common"], + deps = ["@bazel_tools_python//quality/private/python/tools:python_tool_common"], ) py_binary( name = "mypy", srcs = ["mypy_runner.py"], - data = ["@swf_bazel_rules_quality//quality/private/python:mypy_entry_point"], + data = ["@bazel_tools_python//quality/private/python:mypy_entry_point"], main = "mypy_runner.py", visibility = ["//visibility:public"], - deps = ["@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common"], + deps = ["@bazel_tools_python//quality/private/python/tools:python_tool_common"], ) py_binary( @@ -56,8 +56,8 @@ py_binary( main = "ruff_check_runner.py", visibility = ["//visibility:public"], deps = [ - "@swf_bazel_rules_quality//quality/private/python:ruff_entry_point", - "@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common", + "@bazel_tools_python//quality/private/python:ruff_entry_point", + "@bazel_tools_python//quality/private/python/tools:python_tool_common", ], ) @@ -67,8 +67,8 @@ py_binary( main = "ruff_format_runner.py", visibility = ["//visibility:public"], deps = [ - "@swf_bazel_rules_quality//quality/private/python:ruff_entry_point", - "@swf_bazel_rules_quality//quality/private/python/tools:python_tool_common", + "@bazel_tools_python//quality/private/python:ruff_entry_point", + "@bazel_tools_python//quality/private/python/tools:python_tool_common", ], ) diff --git a/quality/private/python/tools/test/BUILD b/quality/private/python/tools/test/BUILD index ceb5c7b..468849c 100644 --- a/quality/private/python/tools/test/BUILD +++ b/quality/private/python/tools/test/BUILD @@ -1,6 +1,6 @@ +load("@bazel_tools_python//quality:defs.bzl", "py_pytest") +load("@bazel_tools_python_pip_hub//:loaders.bzl", "pkg") load("@rules_python//python:defs.bzl", "py_library") -load("@swf_bazel_rules_quality//quality:defs.bzl", "py_pytest") -load("@swf_bazel_rules_quality_pip_hub//:loaders.bzl", "pkg") py_pytest( name = "test_isort_runner", From cdc889d2cc45a51ba5fac29153b126f3f4068c21 Mon Sep 17 00:00:00 2001 From: "jan.nowotsch" Date: Thu, 31 Jul 2025 16:11:08 +0200 Subject: [PATCH 66/74] [infra] Add bazel infrastructure Setup the bazel workspace, build files and dependencies. --- .bazelignore | 1 + .bazelrc | 56 +- .bazelversion | 2 +- .gitignore | 8 + .gitlint | 34 + BUILD | 11 +- MODULE.bazel | 138 ++- WORKSPACE | 33 + WORKSPACE.bzlmod | 0 bazel/buildifier/BUILD | 22 + bazel/rules/BUILD | 0 bazel/rules/rules_python_pip_hub.bzl | 158 ++++ bazel/toolchains/BUILD | 0 bazel/toolchains/python/BUILD | 106 +++ bazel/toolchains/python/versions.bzl | 27 + bazel/toolchains/toolchains.bzl | 8 + project_config.bzl | 6 +- pyproject.toml | 71 ++ quality/BUILD | 80 ++ quality/defs.bzl | 36 + third_party/BUILD | 0 third_party/bazel_skylib/BUILD | 0 third_party/bazel_skylib/bazel_skylib.bzl | 15 + third_party/buildifier/BUILD | 0 third_party/buildifier/buildifier.bzl | 11 + third_party/extensions.bzl | 18 + third_party/internal_dependencies.bzl | 12 + .../internal_transitive_dependencies.bzl | 16 + third_party/pip/BUILD | 52 ++ third_party/pip/check_vulnerabilities.sh | 5 + third_party/pip/requirements.in | 31 + third_party/pip/requirements_lock_3_10.txt | 833 +++++++++++++++++ third_party/pip/requirements_lock_3_11.txt | 788 +++++++++++++++++ third_party/pip/requirements_lock_3_12.txt | 788 +++++++++++++++++ third_party/pip/requirements_lock_3_8.txt | 780 ++++++++++++++++ third_party/pip/requirements_lock_3_9.txt | 834 ++++++++++++++++++ third_party/pip/test.sh | 5 + third_party/pip/update.sh | 5 + third_party/python_pip_hub.bzl | 29 + third_party/python_pip_parse.bzl | 32 + third_party/python_toolchains.bzl | 25 + third_party/rules_python/BUILD | 5 + third_party/rules_python/rules_python.bzl | 16 + third_party/rules_python/rules_python.patch | 17 + 44 files changed, 5069 insertions(+), 45 deletions(-) create mode 100644 .bazelignore create mode 100644 .gitignore create mode 100644 .gitlint create mode 100644 WORKSPACE create mode 100644 WORKSPACE.bzlmod create mode 100644 bazel/buildifier/BUILD create mode 100644 bazel/rules/BUILD create mode 100644 bazel/rules/rules_python_pip_hub.bzl create mode 100644 bazel/toolchains/BUILD create mode 100644 bazel/toolchains/python/BUILD create mode 100644 bazel/toolchains/python/versions.bzl create mode 100644 bazel/toolchains/toolchains.bzl create mode 100644 pyproject.toml create mode 100644 quality/BUILD create mode 100644 quality/defs.bzl create mode 100644 third_party/BUILD create mode 100644 third_party/bazel_skylib/BUILD create mode 100644 third_party/bazel_skylib/bazel_skylib.bzl create mode 100644 third_party/buildifier/BUILD create mode 100644 third_party/buildifier/buildifier.bzl create mode 100644 third_party/extensions.bzl create mode 100644 third_party/internal_dependencies.bzl create mode 100644 third_party/internal_transitive_dependencies.bzl create mode 100644 third_party/pip/BUILD create mode 100755 third_party/pip/check_vulnerabilities.sh create mode 100644 third_party/pip/requirements.in create mode 100644 third_party/pip/requirements_lock_3_10.txt create mode 100644 third_party/pip/requirements_lock_3_11.txt create mode 100644 third_party/pip/requirements_lock_3_12.txt create mode 100644 third_party/pip/requirements_lock_3_8.txt create mode 100644 third_party/pip/requirements_lock_3_9.txt create mode 100755 third_party/pip/test.sh create mode 100755 third_party/pip/update.sh create mode 100644 third_party/python_pip_hub.bzl create mode 100644 third_party/python_pip_parse.bzl create mode 100644 third_party/python_toolchains.bzl create mode 100644 third_party/rules_python/BUILD create mode 100644 third_party/rules_python/rules_python.bzl create mode 100644 third_party/rules_python/rules_python.patch diff --git a/.bazelignore b/.bazelignore new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/.bazelignore @@ -0,0 +1 @@ +test diff --git a/.bazelrc b/.bazelrc index 51cac5a..0165dc9 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,9 +1,53 @@ -build --java_language_version=17 -build --tool_java_language_version=17 -build --java_runtime_version=remotejdk_17 -build --tool_java_runtime_version=remotejdk_17 - -test --test_output=errors +common --lockfile_mode=off +common --enable_platform_specific_config common --registry=https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/ common --registry=https://bcr.bazel.build + +build --incompatible_default_to_explicit_init_py +build --verbose_failures + +test --test_output=errors + +common:use_workspace_mode --noenable_bzlmod +common:use_workspace_mode --enable_workspace + +coverage --combined_report=lcov +coverage --instrument_test_targets +coverage --instrumentation_filter="[:]" + +# Python toolchain configurations +common --flag_alias=python=@rules_python//python/config_settings:python_version +build:python_3_8 --python=3.8 +build:python_3_9 --python=3.9 +build:python_3_10 --python=3.10 +build:python_3_11 --python=3.11 +build:python_3_12 --python=3.12 +build --config=python_3_12 + +common --flag_alias=use_bazel_tools_python_toolchains=//bazel/toolchains/python:bazel_tools_python_toolchains +build:use_bazel_tools_python_toolchains --use_bazel_tools_python_toolchains=True +build:do_not_use_bazel_tools_python_toolchains --use_bazel_tools_python_toolchains=False +build --config=use_bazel_tools_python_toolchains + +# Pylint configuration +build:pylint --output_groups=python_tool_output +build:pylint --aspects=@bazel_tools_python//quality:defs.bzl%pylint_aspect + +# Black configuration +build:black --output_groups=python_tool_output +build:black --aspects=@bazel_tools_python//quality:defs.bzl%black_aspect + +# Isort configuration +build:isort --output_groups=python_tool_output +build:isort --aspects=@bazel_tools_python//quality:defs.bzl%isort_aspect + +# Mypy configuration +build:mypy --output_groups=python_tool_output +build:mypy --aspects=@bazel_tools_python//quality:defs.bzl%mypy_aspect + +# Ruff configuration +build:ruff_check --output_groups=python_tool_output +build:ruff_check --aspects=@bazel_tools_python//quality:defs.bzl%ruff_check_aspect +build:ruff_format --output_groups=python_tool_output +build:ruff_format --aspects=@bazel_tools_python//quality:defs.bzl%ruff_format_aspect diff --git a/.bazelversion b/.bazelversion index 2bf50aa..18bb418 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -8.3.0 +7.5.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..492d392 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.orig +.vscode +/out +__pycache__ +bazel-* +/.coverage +/dist +*.stamp diff --git a/.gitlint b/.gitlint new file mode 100644 index 0000000..508ed33 --- /dev/null +++ b/.gitlint @@ -0,0 +1,34 @@ +# Available rules: +# +# T1: title-max-length +# T2: title-trailing-whitespace +# T3: title-trailing-punctuation (disabled) +# T4: title-hard-tab +# T5: title-must-not-contain-word (disabled) +# T6: title-leading-whitespace +# T7: title-match-regex +# B1: body-max-line-length +# B2: body-trailing-whitespace +# B3: body-hard-tab +# B4: body-first-line-empty +# B5: body-min-length (disabled) +# B6: body-is-missing (disabled) +# B7: body-changed-file-mention (disabled) +# +# See http://jorisroovers.github.io/gitlint/rules/ for a full description. +[general] +ignore=T3,T5,B5,B6,B7 +# Ensure every title starts with a capital letter +[title-match-regex] +regex=^([A-Z]|(\[[a-z-]+\]))\w* + +[body-max-line-length] +line-length=80 + +[title-max-length] +line-length=80 + +# Allow lines starting with URLs to pass line length limit +[ignore-by-body] +regex=^https?:\/\/ +ignore=body-max-line-length diff --git a/BUILD b/BUILD index 94478ea..dcdc8c6 100644 --- a/BUILD +++ b/BUILD @@ -25,6 +25,11 @@ setup_starpls( copyright_checker( name = "copyright", srcs = [ + "bazel", + "quality", + "scripts", + "test", + "third_party", "//:BUILD", "//:MODULE.bazel", ], @@ -34,7 +39,7 @@ copyright_checker( ) dash_license_checker( - src = "//examples:cargo_lock", + src = "//third_party/pip:requirement_locks", file_type = "", # let it auto-detect based on project_config project_config = PROJECT_CONFIG, visibility = ["//visibility:public"], @@ -46,3 +51,7 @@ use_format_targets() docs( source_dir = "docs", ) + +exports_files([ + "pyproject.toml", +]) diff --git a/MODULE.bazel b/MODULE.bazel index 44009a0..84e3aeb 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -11,55 +11,121 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* module( - name = "cpp_rust_template_repository", - version = "1.0", + name = "bazel_tools_python", + version = "0.1.0", + compatibility_level = 1, ) -bazel_dep(name = "rules_python", version = "1.4.1") +# Checker rule for CopyRight checks/fixs +bazel_dep(name = "score_cr_checker", version = "0.3.1", dev_dependency = True) +bazel_dep(name = "score_python_basics", version = "0.3.4", dev_dependency = True) + +# Dash license checker +bazel_dep(name = "score_dash_license_checker", version = "0.1.2", dev_dependency = True) -PYTHON_VERSION = "3.12" +# Format checker +bazel_dep(name = "score_format_checker", version = "0.1.1", dev_dependency = True) +bazel_dep(name = "aspect_rules_lint", version = "1.4.4", dev_dependency = True) +bazel_dep(name = "buildifier_prebuilt", version = "8.2.0.2", dev_dependency = True) -python = use_extension("@rules_python//python/extensions:python.bzl", "python") -python.toolchain( - is_default = True, - python_version = PYTHON_VERSION, +# docs-as-code +bazel_dep(name = "score_docs_as_code", version = "1.0.1", dev_dependency = True) +bazel_dep(name = "platforms", version = "0.0.11", dev_dependency = True) + +# Unfortunately bazel_skylib can not be dev_dependency because we use some of its libraries. +bazel_dep(name = "bazel_skylib", version = "1.7.1") + +# Unfortunately rules_python can not be dev_dependency because we provide our pip hub using it. +bazel_dep(name = "rules_python", version = "1.4.1") + +# We patch rules_python patch to python coverage files. +# This enable us to select our own .coveragerc file when issuing a bazel coverage command. +# Reference: https://github.com/bazelbuild/rules_python/blob/main/python/private/coverage.patch +single_version_override( + module_name = "rules_python", + patches = ["@bazel_tools_python//third_party/rules_python:rules_python.patch"], ) -use_repo(python) -# Add GoogleTest dependency -bazel_dep(name = "googletest", version = "1.14.0") +# Python toolchain and dependencies -# Rust rules for Bazel -bazel_dep(name = "rules_rust", version = "0.56.0") +PYTHON_VERSIONS = [ + "3.8", + "3.9", + "3.10", + "3.11", + "3.12", +] -# Checker rule for CopyRight checks/fixs -bazel_dep(name = "score_cr_checker", version = "0.3.1") -bazel_dep(name = "score_python_basics", version = "0.3.4") -bazel_dep(name = "score_starpls_lsp", version = "0.1.0") +# By default, one can't add `dev_dependency = True` to `python` extension from +# `@rules_python//python/extensions:python.bzl` if python dependencies are +# provided to the end user. To circumvent that, we rely on a conditional +# statement by the mentioned extension, see: +# https://github.com/bazelbuild/rules_python/blob/89d850aab819eb2dea9d6340beab1ca810dbe18d/python/private/pypi/extension.bzl#L111 -# C/C++ rules for Bazel -bazel_dep(name = "rules_cc", version = "0.1.1") +# The result is that `dev_dependency` can only be added to `python` extension +# if every `pip_parse` has `python_interpreter` set. We set `python_interpreter` +# to `python3` value because every hermetic toolchain has it as a symlink to +# the real executable. -# LLVM Toolchains Rules - host configuration -bazel_dep(name = "toolchains_llvm", version = "1.2.0") +# More info about this can be found at: +# https://github.com/bazelbuild/rules_python/issues/1818#issuecomment-2271227487 -llvm = use_extension("@toolchains_llvm//toolchain/extensions:llvm.bzl", "llvm") -llvm.toolchain( - cxx_standard = {"": "c++17"}, - llvm_version = "19.1.0", +python = use_extension("@rules_python//python/extensions:python.bzl", "python") + +[ + python.toolchain( + configure_coverage_tool = True, + ignore_root_user_error = True, + is_default = False, + python_version = "{}".format(version), + ) + for version in PYTHON_VERSIONS +] + +# We can't use a loop for the following `use_repo` because we change the name of each toolchain. +# buildifier: leave-alone +use_repo( + python, + bazel_tools_python_python_3_8 = "python_3_8", + bazel_tools_python_python_3_9 = "python_3_9", + bazel_tools_python_python_3_10 = "python_3_10", + bazel_tools_python_python_3_11 = "python_3_11", + bazel_tools_python_python_3_12 = "python_3_12", ) -use_repo(llvm, "llvm_toolchain") -use_repo(llvm, "llvm_toolchain_llvm") -register_toolchains("@llvm_toolchain//:all") +[ + register_toolchains( + "@bazel_tools_python//bazel/toolchains/python:bazel_tools_python_python_{}_toolchain".format(version.replace(".", "_")), + dev_dependency = True, + ) + for version in PYTHON_VERSIONS +] -# Dash license checker -bazel_dep(name = "score_dash_license_checker", version = "0.1.2") +register_toolchains( + "@bazel_tools_python//bazel/toolchains/python:py_undefined_trap", + dev_dependency = True, +) -# Format checker -bazel_dep(name = "score_format_checker", version = "0.1.1") -bazel_dep(name = "aspect_rules_lint", version = "1.4.4") -bazel_dep(name = "buildifier_prebuilt", version = "8.2.0.2") +register_toolchains( + "@bazel_tools_python//bazel/toolchains/python:do_not_use_bazel_tools_python_toolchains_trap", + dev_dependency = True, +) + +pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip") + +[ + pip.parse( + hub_name = "bazel_tools_python_pip_{}".format(version.replace(".", "_")), + python_version = "{}".format(version), + requirements_lock = "//third_party/pip:requirements_lock_{}.txt".format(version.replace(".", "_")), + ) + for version in PYTHON_VERSIONS +] + +[ + use_repo(pip, "bazel_tools_python_pip_{}".format(version.replace(".", "_"))) + for version in PYTHON_VERSIONS +] -#docs-as-code -bazel_dep(name = "score_docs_as_code", version = "1.0.1") +rules_python_pip_hub = use_extension("@bazel_tools_python//third_party:extensions.bzl", "rules_python_pip_hub") +use_repo(rules_python_pip_hub, "bazel_tools_python_pip_hub") diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 0000000..38921eb --- /dev/null +++ b/WORKSPACE @@ -0,0 +1,33 @@ +workspace(name = "bazel_tools_python") + +########################## +# Internal dependencies. # +########################## + +load("@bazel_tools_python//third_party:internal_dependencies.bzl", "internal_dependencies") + +internal_dependencies() + +load("@bazel_tools_python//third_party:internal_transitive_dependencies.bzl", "internal_transitive_dependencies") + +internal_transitive_dependencies() + +###################################### +# Python toolchain and dependencies. # +###################################### + +load("@bazel_tools_python//third_party:python_toolchains.bzl", "python_toolchains") + +python_toolchains() + +load("@bazel_tools_python//bazel/toolchains:toolchains.bzl", "toolchains") + +toolchains() + +load("@bazel_tools_python//third_party:python_pip_parse.bzl", "python_pip_parse") + +python_pip_parse() + +load("@bazel_tools_python//third_party:python_pip_hub.bzl", "python_pip_hub") + +python_pip_hub() diff --git a/WORKSPACE.bzlmod b/WORKSPACE.bzlmod new file mode 100644 index 0000000..e69de29 diff --git a/bazel/buildifier/BUILD b/bazel/buildifier/BUILD new file mode 100644 index 0000000..ebe596d --- /dev/null +++ b/bazel/buildifier/BUILD @@ -0,0 +1,22 @@ +load("@buildifier_prebuilt//:rules.bzl", "buildifier") + +BUILDIFIER_EXCLUDE_PATTERNS = [ + "./.git/*", + "./.tox/*", +] + +buildifier( + name = "check", + exclude_patterns = BUILDIFIER_EXCLUDE_PATTERNS, + lint_mode = "warn", + lint_warnings = ["all"], + mode = "diff", +) + +buildifier( + name = "fix", + exclude_patterns = BUILDIFIER_EXCLUDE_PATTERNS, + lint_mode = "fix", + lint_warnings = ["all"], + mode = "fix", +) diff --git a/bazel/rules/BUILD b/bazel/rules/BUILD new file mode 100644 index 0000000..e69de29 diff --git a/bazel/rules/rules_python_pip_hub.bzl b/bazel/rules/rules_python_pip_hub.bzl new file mode 100644 index 0000000..51a8ee3 --- /dev/null +++ b/bazel/rules/rules_python_pip_hub.bzl @@ -0,0 +1,158 @@ +"""Rule that creates an auxiliary pip hub. + +Using a pip hub is already a default workflow for rules_python pip integration. We create + our own hub to support things like multiple python toolchain and multiple platforms. + +The original owner of this implementation is EF or, more precisely, Martin Medler. +Please note that this rule was still in at early, test, stage when we copied it. +If they ever use this for production it would be nice to use a common solution for both parts. + +Main changes from the original version: +- `requirements.in` file lines can start with `--`; +- `_make_indirection` improved into `_generate_single_indirection` and `_generate_indirections`; +- `_generate_indirections` generate not only `pkg` but `data` target as well; +- new method `_generate_loaders` creates a `loaders.bzl` file to improve the user interface; +""" + +load("@rules_python//python:pip.bzl", "pip_utils") + +# Which targets from rules_python pip integration should be offered. +_INDIRECTIONS_TYPES = [ + "pkg", + "data", +] + +def _get_direct_deps(repo_ctx): + """Read a pip requirements.in file and return a list of direct dependencies.""" + requirements = [] + + lines = repo_ctx.read(repo_ctx.attr.requirements_in).split("\n") + for line in lines: + line = line.strip() + if not line: + continue # Skip empty lines. + if line.startswith("#"): + continue # Skip comments. + if line.startswith("--"): + continue # Skip possible pip custom configurations. + if "==" not in line: + fail("Line '{}' in '{}' is missing a precise pinning via '=='.".format(line, str(repo_ctx.attr.requirements_in)) + + " While this is technically possible it violates our project best practices.") + requirements.append(line.split("==", 1)[0]) + + return requirements + +def _generate_single_indirection(dep, deps_to_config_map, indirection_type): + """Generate the requested dependency indirection for a given dependency to config map.""" + dep_name = pip_utils.normalize_name(dep) + + deps_map = "" + for deps_repo, config in deps_to_config_map.items(): + deps_map += 8 * " " + '"{}": "{}//{}:{}",\n'.format(config, deps_repo, dep_name, indirection_type) + + target = """ +alias( + name = "{name}_{indirection_type}", + actual = select({{ +{map} + }}), + visibility = ["//visibility:public"], +) + """.strip().format(name = dep, indirection_type = indirection_type, map = deps_map) + return target + +def _generate_indirections(repo_ctx, direct_deps): + """Generate all indirections types for each direct dependency.""" + content = "" + + for dep in direct_deps: + for indirection_type in _INDIRECTIONS_TYPES: + content += _generate_single_indirection(dep, repo_ctx.attr.deps_to_config_map, indirection_type) + content += "\n\n" + + return content + +def _generate_single_loader(repo_name, indirection_type): + """Generate the requested loader indirection.""" + return """ +def {indirection_type}(package): + return "@{name}//:{{}}_{indirection_type}".format(package) + """.strip().format(indirection_type = indirection_type, name = repo_name) + +def _generate_loaders(repo_ctx): + """Generate all indirections types loaders.""" + content = [] + + # In bazel 8 the repo names are split by + instead of ~ + # See --incompatible_use_plus_in_repo_names https://github.com/bazelbuild/bazel/issues/23127 + # However it is a hard flip since the naming is no a stable api + use_bazel_8_repo_names = "+" in repo_ctx.name + + if use_bazel_8_repo_names: + name_separator = "+" + else: + name_separator = "~" + + repo_name = repo_ctx.name.split(name_separator)[-1] + + for indirection_type in _INDIRECTIONS_TYPES: + content.append(_generate_single_loader(repo_name, indirection_type)) + content.append("") + + return "\n\n".join(content) + +def _rules_python_pip_hub_impl(repo_ctx): + """Rules python pip hub repository implementation. + + This implementation will create a new Bazel workspace containing our custom pip hub implementation. + + Main steps are: + - get main direct dependencies from requirements.in file; + - create an empty WORKSPACE file; + - create a BUILD file containing all indirection types for each dependency; + - create a loaders.bzl file containing one loader for each indirection type; + """ + direct_deps = _get_direct_deps(repo_ctx) + + repo_ctx.file("WORKSPACE", content = "", executable = False) + repo_ctx.file("BUILD", content = _generate_indirections(repo_ctx, direct_deps), executable = False) + repo_ctx.file("loaders.bzl", content = _generate_loaders(repo_ctx), executable = False) + +rules_python_pip_hub = repository_rule( + implementation = _rules_python_pip_hub_impl, + attrs = { + "deps_to_config_map": attr.string_dict( + doc = """Dictionary that maps rules python pip hubs to Bazel config_settings. + +This is needed so this custom pip hub can select which rules python pip hub should be selected + according to Bazel configuration and toolchain resolution. + +``` +deps_to_config_map = { + "@rules_python_pip_hub_3_10": "@your_repo_name//label/to/your:config_setting_3_10", + "@rules_python_pip_hub_3_11": "@your_repo_name//label/to/your:config_setting_3_11", + "@rules_python_pip_hub_3_8": "@your_repo_name//label/to/your:config_setting_3_8", + "@rules_python_pip_hub_3_9": "@your_repo_name//label/to/your:config_setting_3_9", + } +``` + +Will lead, for example, `rules_python_pip_hub_3_10` to be selected when + `@your_repo_name//label/to/your:config_setting_3_10` conditions are met. +""", + mandatory = True, + ), + "requirements_in": attr.label( + doc = """Label of an usual pip requirements.in file. + +This rule current support: +- empty lines; +- comments lines (`#`); +- configuration lines (`--`); +- and lines with versioned dependencies (==). +""", + mandatory = True, + allow_single_file = True, + ), + }, + doc = "Custom pip hub implementation that support multiple python toolchain and platforms.", +) diff --git a/bazel/toolchains/BUILD b/bazel/toolchains/BUILD new file mode 100644 index 0000000..e69de29 diff --git a/bazel/toolchains/python/BUILD b/bazel/toolchains/python/BUILD new file mode 100644 index 0000000..17d5d87 --- /dev/null +++ b/bazel/toolchains/python/BUILD @@ -0,0 +1,106 @@ +load("@bazel_skylib//rules:common_settings.bzl", "bool_flag", "bool_setting") +load("@bazel_tools_python//bazel/toolchains/python:versions.bzl", "PYTHON_VERSIONS", "unsupported_python_configuration_error") +load("@rules_python//python:defs.bzl", "py_runtime_pair") + +bool_flag( + name = "bazel_tools_python_toolchains", + build_setting_default = False, +) + +config_setting( + name = "use_bazel_tools_python_toolchains", + flag_values = {":bazel_tools_python_toolchains": "True"}, +) + +config_setting( + name = "do_not_use_bazel_tools_python_toolchains", + flag_values = {":bazel_tools_python_toolchains": "False"}, +) + +config_setting( + name = "python_undefined", + flag_values = {"@rules_python//python/config_settings:python_version": ""}, + visibility = ["//visibility:public"], +) + +[ + config_setting( + name = "python_{}".format(version.replace(".", "_")), + flag_values = {"@rules_python//python/config_settings:python_version_major_minor": version}, + visibility = ["//visibility:public"], + ) + for version in PYTHON_VERSIONS +] + +[ + py_runtime_pair( + name = "bazel_tools_python_python_{}_runtime_pair".format(version.replace(".", "_")), + py3_runtime = "@bazel_tools_python_python_{}//:py3_runtime".format(version.replace(".", "_")), + ) + for version in PYTHON_VERSIONS +] + +[ + toolchain( + name = "bazel_tools_python_python_{}_toolchain".format(version.replace(".", "_")), + exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], + # Note that target_compatible_with must be empty to ensure that the + # toolchain resolution will be done only against the execution platform. + target_compatible_with = [], + target_settings = [ + ":python_{}".format(version.replace(".", "_")), + ":use_bazel_tools_python_toolchains", + ], + toolchain = ":bazel_tools_python_python_{}_runtime_pair".format(version.replace(".", "_")), + toolchain_type = "@rules_python//python:toolchain_type", + ) + for version in PYTHON_VERSIONS +] + +############################################# +# Incorrect toolchain configurations traps. # +############################################# + +# All code below this comment is related to traps that catch incorrect toolchain configurations. + +bool_setting( + name = "alway_false", + build_setting_default = False, +) + +config_setting( + name = "never_match", + flag_values = {":alway_false": "True"}, +) + +py_runtime_pair( + name = "py_undefined_pair", + py3_runtime = select( + {":never_match": None}, + no_match_error = unsupported_python_configuration_error("Undefined python toolchain error."), + ), + tags = ["manual"], # We do not want to manually build or test this with //... +) + +# When the user configures the build incorrectly and none of our Python toolchains matches, this trap will be chosen +# instead of the auto detected non hermetic host toolchain. This will immediately abort the build in the analysis phase +# due to the runtime_pair always hitting and unresolvable select. +toolchain( + name = "py_undefined_trap", + target_settings = [ + ":python_undefined", + ":use_bazel_tools_python_toolchains", + ], + toolchain = ":py_undefined_pair", + toolchain_type = "@rules_python//python:toolchain_type", +) + +toolchain( + name = "do_not_use_bazel_tools_python_toolchains_trap", + target_settings = [":do_not_use_bazel_tools_python_toolchains"], + toolchain = ":py_undefined_pair", + toolchain_type = "@rules_python//python:toolchain_type", +) diff --git a/bazel/toolchains/python/versions.bzl b/bazel/toolchains/python/versions.bzl new file mode 100644 index 0000000..08e644f --- /dev/null +++ b/bazel/toolchains/python/versions.bzl @@ -0,0 +1,27 @@ +# Copyright 2025 The BMW Group Authors. All rights reserved. + +"""This module defines which python version are supported.""" + +PYTHON_VERSIONS = [ + "3.8", + "3.9", + "3.10", + "3.11", + "3.12", +] + +def unsupported_python_configuration_error(custom_message): + default_message = """ +---------------------------------------------------------------------- +You may be using an invalid python version configuration. +Available options are: {py_versions}. +Please select a valid python version using rules_python's string_flag: +@rules_python//python/config_settings:python_version. +---------------------------------------------------------------------- +""".format(py_versions = PYTHON_VERSIONS).rstrip() + if custom_message: + return """ +---------------------------------------------------------------------- +{custom_message}{default_message} +""".format(custom_message = custom_message, default_message = default_message) + return default_message diff --git a/bazel/toolchains/toolchains.bzl b/bazel/toolchains/toolchains.bzl new file mode 100644 index 0000000..eebad97 --- /dev/null +++ b/bazel/toolchains/toolchains.bzl @@ -0,0 +1,8 @@ +# Copyright 2025 The BMW Group Authors. All rights reserved. + +"""Repository toolchain collection.""" + +def toolchains(name = "bazel_tools_python_toolchains"): # buildifier: disable=unused-variable + native.register_toolchains( + "@bazel_tools_python//bazel/toolchains/python:all", + ) diff --git a/project_config.bzl b/project_config.bzl index f764a1d..042ceb9 100644 --- a/project_config.bzl +++ b/project_config.bzl @@ -1,5 +1,9 @@ +""" +Eclipse project configuration. +""" + # project_config.bzl PROJECT_CONFIG = { "asil_level": "QM", - "source_code": ["rust"], + "source_code": ["python", "starlark"], } diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..7193e7e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,71 @@ +[tool.black] +line-length = 120 + +[tool.isort] +profile = "black" + +[tool.ruff] +# Same as Black. +line-length = 120 +indent-width = 4 +target-version = "py39" + +[tool.ruff.format] +# Like Black, use double quotes for strings. +quote-style = "double" +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +[tool.pytest.ini_options] +addopts = [ + "--tb=short", + "--import-mode=importlib", + "--ignore=third_party", + "--ignore=playbooks", + "--ignore-glob=bazel*", + "--ignore-glob=.*cache", +] +filterwarnings = [ + "ignore::Warning:_pytest*:", + "ignore::DeprecationWarning:pytest_pylint*:", +] + +[tool.pylint] +max-line-length=120 + +[tool.mypy] +disallow_untyped_defs = false +explicit_package_bases = true +incremental = false +python_version = "3.9" + +[tool.pylint.messages_control] +disable = [ + "duplicate-code", + "logging-fstring-interpolation", +] + +[tool.coverage.run] +# Do not run coverage for test files. +omit = [ + "*/test_*.py", +] +# Activate branch coverage analysis. +branch = true + +[tool.coverage.report] +# List the lines that were not covered by tests. +show_missing = true +# Restrict coverage reporting to the 'validation' targets. +include = ["*/quality/*", "*/tools/*"] +# Omit external files. +omit = ["*/external/*"] +# Exclude lines that are not supposed to have coverage +exclude_lines = [ + "pragma: no cover", + "if __name__ == .__main__.:", +] diff --git a/quality/BUILD b/quality/BUILD new file mode 100644 index 0000000..2c62678 --- /dev/null +++ b/quality/BUILD @@ -0,0 +1,80 @@ +# Copyright 2022 The BMW Group Authors. All rights reserved. + +load("@bazel_tools_python//quality:defs.bzl", "python_tool_config") + +python_tool_config( + name = "quality_python_default_config", + config = "@bazel_tools_python//quality/private/python/support:pyproject_toml", + visibility = ["//visibility:public"], +) + +label_flag( + name = "quality_pytest_config", + build_setting_default = "@bazel_tools_python//quality:quality_python_default_config", + visibility = ["//visibility:public"], +) + +label_flag( + name = "quality_pylint_config", + build_setting_default = "@bazel_tools_python//quality:quality_python_default_config", + visibility = ["//visibility:public"], +) + +label_flag( + name = "quality_black_config", + build_setting_default = "@bazel_tools_python//quality:quality_python_default_config", + visibility = ["//visibility:public"], +) + +label_flag( + name = "quality_isort_config", + build_setting_default = "@bazel_tools_python//quality:quality_python_default_config", + visibility = ["//visibility:public"], +) + +label_flag( + name = "quality_mypy_config", + build_setting_default = "@bazel_tools_python//quality:quality_python_default_config", + visibility = ["//visibility:public"], +) + +label_flag( + name = "quality_ruff_config", + build_setting_default = "@bazel_tools_python//quality:quality_python_default_config", + visibility = ["//visibility:public"], +) + +alias( + name = "pylint", + actual = "//quality/private/python:pylint_entry_point", +) + +alias( + name = "black", + actual = "//quality/private/python:black_entry_point", +) + +alias( + name = "isort", + actual = "//quality/private/python:isort_entry_point", +) + +alias( + name = "mypy", + actual = "//quality/private/python:mypy_entry_point", +) + +alias( + name = "ruff", + actual = "//quality/private/python:ruff_entry_point", +) + +alias( + name = "pip_audit", + actual = "//quality/private/python:pip_audit_entry_point", +) + +alias( + name = "pycoverage", + actual = "//quality/private/python:pycoverage_entry_point", +) diff --git a/quality/defs.bzl b/quality/defs.bzl new file mode 100644 index 0000000..6183c8c --- /dev/null +++ b/quality/defs.bzl @@ -0,0 +1,36 @@ +# Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG). All rights reserved. + +""" +Defines all public symbols for this modules to be used by the specific project. +""" + +load("@bazel_tools_python//quality/private/python:py_pytest.bzl", _py_pytest = "py_pytest") +load( + "@bazel_tools_python//quality/private/python:python_pip_audit_rule.bzl", + _pip_audit_rule = "pip_audit_rule", +) +load( + "@bazel_tools_python//quality/private/python:python_tool_aspect.bzl", + _black_aspect = "black_aspect", + _isort_aspect = "isort_aspect", + _mypy_aspect = "mypy_aspect", + _pylint_aspect = "pylint_aspect", + _python_tool_config = "python_tool_config", + _ruff_check_aspect = "ruff_check_aspect", + _ruff_format_aspect = "ruff_format_aspect", +) + +python_tool_config = _python_tool_config +py_pytest = _py_pytest + +# Pylint only works with workspace. +# The problem is related with rules_python bzlmod support. +# See: https://github.com/bazelbuild/rules_python/issues/1575 +pylint_aspect = _pylint_aspect +black_aspect = _black_aspect +isort_aspect = _isort_aspect +mypy_aspect = _mypy_aspect +ruff_check_aspect = _ruff_check_aspect +ruff_format_aspect = _ruff_format_aspect + +pip_audit_rule = _pip_audit_rule diff --git a/third_party/BUILD b/third_party/BUILD new file mode 100644 index 0000000..e69de29 diff --git a/third_party/bazel_skylib/BUILD b/third_party/bazel_skylib/BUILD new file mode 100644 index 0000000..e69de29 diff --git a/third_party/bazel_skylib/bazel_skylib.bzl b/third_party/bazel_skylib/bazel_skylib.bzl new file mode 100644 index 0000000..b9c5eed --- /dev/null +++ b/third_party/bazel_skylib/bazel_skylib.bzl @@ -0,0 +1,15 @@ +"""Third-party dependency definition for bazel_skylib""" + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") + +_VERSION = "1.7.1" +_PATH = "bazelbuild/bazel-skylib/releases/download/{version}/bazel-skylib-{version}.tar.gz".format(version = _VERSION) + +def bazel_skylib(): + maybe( + http_archive, + name = "bazel_skylib", + sha256 = "bc283cdfcd526a52c3201279cda4bc298652efa898b10b4db0837dc51652756f", + url = "https://github.com/{path}".format(path = _PATH), + ) diff --git a/third_party/buildifier/BUILD b/third_party/buildifier/BUILD new file mode 100644 index 0000000..e69de29 diff --git a/third_party/buildifier/buildifier.bzl b/third_party/buildifier/buildifier.bzl new file mode 100644 index 0000000..9944721 --- /dev/null +++ b/third_party/buildifier/buildifier.bzl @@ -0,0 +1,11 @@ +"""Third-party dependency definition for buildifier.""" + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +def buildifier(): + http_archive( + name = "buildifier_prebuilt", + sha256 = "8ada9d88e51ebf5a1fdff37d75ed41d51f5e677cdbeafb0a22dda54747d6e07e", + strip_prefix = "buildifier-prebuilt-6.4.0", + urls = ["https://github.com/keith/buildifier-prebuilt/archive/refs/tags/6.4.0.tar.gz"], + ) diff --git a/third_party/extensions.bzl b/third_party/extensions.bzl new file mode 100644 index 0000000..c1d7ae9 --- /dev/null +++ b/third_party/extensions.bzl @@ -0,0 +1,18 @@ +"""Collection of the repository thid-party extensions.""" + +load("@bazel_tools_python//bazel/rules:rules_python_pip_hub.bzl", _rules_python_pip_hub = "rules_python_pip_hub") +load("@bazel_tools_python//bazel/toolchains/python:versions.bzl", "PYTHON_VERSIONS") + +def _rules_python_pip_hub_impl(): + """Make non module rules_python_pip_hub dependencies known to bazel.""" + + _rules_python_pip_hub( + name = "bazel_tools_python_pip_hub", + deps_to_config_map = { + "@bazel_tools_python_pip_{}".format(version.replace(".", "_")): "@bazel_tools_python//bazel/toolchains/python:python_{}".format(version.replace(".", "_")) + for version in PYTHON_VERSIONS + }, + requirements_in = "@bazel_tools_python//third_party/pip:requirements.in", + ) + +rules_python_pip_hub = module_extension(lambda ctx: _rules_python_pip_hub_impl()) diff --git a/third_party/internal_dependencies.bzl b/third_party/internal_dependencies.bzl new file mode 100644 index 0000000..0ad478c --- /dev/null +++ b/third_party/internal_dependencies.bzl @@ -0,0 +1,12 @@ +"""Collection of the repository internal thid-party dependencies.""" + +load("@bazel_tools_python//third_party/bazel_skylib:bazel_skylib.bzl", "bazel_skylib") +load("@bazel_tools_python//third_party/buildifier:buildifier.bzl", "buildifier") +load("@bazel_tools_python//third_party/rules_python:rules_python.bzl", "rules_python") + +def internal_dependencies(): + """Make internal third-party dependencies known to bazel.""" + + bazel_skylib() + buildifier() + rules_python() diff --git a/third_party/internal_transitive_dependencies.bzl b/third_party/internal_transitive_dependencies.bzl new file mode 100644 index 0000000..66ffd89 --- /dev/null +++ b/third_party/internal_transitive_dependencies.bzl @@ -0,0 +1,16 @@ +"""First set of internal transitive dependencies required for this module.""" + +load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") +load("@buildifier_prebuilt//:defs.bzl", "buildifier_prebuilt_register_toolchains") +load("@buildifier_prebuilt//:deps.bzl", "buildifier_prebuilt_deps") +load("@rules_python//python:repositories.bzl", "py_repositories") + +def internal_transitive_dependencies(): + """Load transitive macros of the internal dependencies.""" + + bazel_skylib_workspace() + + buildifier_prebuilt_deps() + buildifier_prebuilt_register_toolchains() + + py_repositories() diff --git a/third_party/pip/BUILD b/third_party/pip/BUILD new file mode 100644 index 0000000..6c327f7 --- /dev/null +++ b/third_party/pip/BUILD @@ -0,0 +1,52 @@ +load("@bazel_tools_python//bazel/toolchains/python:versions.bzl", "PYTHON_VERSIONS") +load("@bazel_tools_python//quality:defs.bzl", "pip_audit_rule") +load("@rules_python//python:pip.bzl", "compile_pip_requirements") + +# Rule compile_pip_requirements handles pip requirements. +# It has two important targets: +# - requirements.update: A runnable target that updates the lock file +# - requirements_test: A test target that validates the lock file +# +# To run the update script in Windows, make sure your user or system .bazelrc has the +# following set: +# startup --windows_enable_symlinks +# build --enable_runfiles +# This is necessary until `rules_python` is updated to 1.0.0, which fixes it. +# Check https://github.com/bazel-contrib/rules_python/issues/1431 + +REQUIREMENTS_SOURCE = "requirements.in" + +EXTRA_ARGS = [ + "--no-strip-extras", + "--quiet", +] + +[ + compile_pip_requirements( + name = "requirements_{}".format(version.replace(".", "_")), + src = REQUIREMENTS_SOURCE, + extra_args = EXTRA_ARGS, + requirements_txt = "requirements_lock_{}.txt".format(version.replace(".", "_")), + # Manual tag is needed because each pip requirement should be tested with its + # matching python version. Therefore we don't want these targets to be tested + # when targeting for example `//...` with only one python toolchain. + tags = ["manual"], + ) + for version in PYTHON_VERSIONS +] + +[ + pip_audit_rule( + name = "pip_audit_requirements_{}".format(version.replace(".", "_")), + requirement = "requirements_lock_{}.txt".format(version.replace(".", "_")), + ) + for version in PYTHON_VERSIONS +] + +filegroup( + name = "requirement_locks", + srcs = [ + "requirements_lock_3_10.txt", + ], + visibility = ["//visibility:public"], +) diff --git a/third_party/pip/check_vulnerabilities.sh b/third_party/pip/check_vulnerabilities.sh new file mode 100755 index 0000000..fd18a74 --- /dev/null +++ b/third_party/pip/check_vulnerabilities.sh @@ -0,0 +1,5 @@ +bazel run //third_party/pip:pip_audit_requirements_3_8 +bazel run //third_party/pip:pip_audit_requirements_3_9 +bazel run //third_party/pip:pip_audit_requirements_3_10 +bazel run //third_party/pip:pip_audit_requirements_3_11 +bazel run //third_party/pip:pip_audit_requirements_3_12 diff --git a/third_party/pip/requirements.in b/third_party/pip/requirements.in new file mode 100644 index 0000000..3a49df5 --- /dev/null +++ b/third_party/pip/requirements.in @@ -0,0 +1,31 @@ +# This file should follow PEP 508: +# https://peps.python.org/pep-0508/ + +termcolor==2.0.0 +coverage==7.5.1 +pylint==3.1.0 +black==24.4.2 +isort==5.13.2 +ruff==0.5.5 +mypy==1.11.1 +types-jsonschema==4.23.0.20240712 +types-PyYAML==6.0.12.20240724 +types-requests==2.32.0.20240712 +types-typed-ast==1.5.8.7 +types-six==1.16.21.20240513 +types-setuptools==72.2.0.20240821 +types-python-dateutil==2.9.0.20240821 +types-certifi==2021.10.8.3 +types-chardet==5.0.4.6 +types-urllib3==1.26.25.14 +types-html5lib==1.1.11.20240806 +pytest==8.1.1 +pytest-benchmark==4.0.0 +pytest-bdd==7.1.1 +pytest-mock==3.12.0 +pytest-rerunfailures==14.0 +pytest-timeout==2.3.1 +pip-audit==2.7.0 + +# Indirect dependencies to be windows compatible +colorama==0.4.6 diff --git a/third_party/pip/requirements_lock_3_10.txt b/third_party/pip/requirements_lock_3_10.txt new file mode 100644 index 0000000..5114296 --- /dev/null +++ b/third_party/pip/requirements_lock_3_10.txt @@ -0,0 +1,833 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# bazel run //third_party/pip:requirements_3_10.update +# +astroid==3.1.0 \ + --hash=sha256:951798f922990137ac090c53af473db7ab4e70c770e6d7fae0cec59f74411819 \ + --hash=sha256:ac248253bfa4bd924a0de213707e7ebeeb3138abeb48d798784ead1e56d419d4 + # via pylint +attrs==25.3.0 \ + --hash=sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3 \ + --hash=sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b + # via referencing +black==24.4.2 \ + --hash=sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474 \ + --hash=sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1 \ + --hash=sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0 \ + --hash=sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8 \ + --hash=sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96 \ + --hash=sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1 \ + --hash=sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04 \ + --hash=sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021 \ + --hash=sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94 \ + --hash=sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d \ + --hash=sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c \ + --hash=sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7 \ + --hash=sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c \ + --hash=sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc \ + --hash=sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7 \ + --hash=sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d \ + --hash=sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c \ + --hash=sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741 \ + --hash=sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce \ + --hash=sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb \ + --hash=sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063 \ + --hash=sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e + # via -r third_party/pip/requirements.in +boolean-py==5.0 \ + --hash=sha256:60cbc4bad079753721d32649545505362c754e121570ada4658b852a3a318d95 \ + --hash=sha256:ef28a70bd43115208441b53a045d1549e2f0ec6e3d08a9d142cbc41c1938e8d9 + # via license-expression +cachecontrol[filecache]==0.14.3 \ + --hash=sha256:73e7efec4b06b20d9267b441c1f733664f989fb8688391b670ca812d70795d11 \ + --hash=sha256:b35e44a3113f17d2a31c1e6b27b9de6d4405f84ae51baa8c1d3cc5b633010cae + # via + # cachecontrol + # pip-audit +certifi==2025.8.3 \ + --hash=sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407 \ + --hash=sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5 + # via requests +charset-normalizer==3.4.3 \ + --hash=sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91 \ + --hash=sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0 \ + --hash=sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154 \ + --hash=sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601 \ + --hash=sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884 \ + --hash=sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07 \ + --hash=sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c \ + --hash=sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64 \ + --hash=sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe \ + --hash=sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f \ + --hash=sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432 \ + --hash=sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc \ + --hash=sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa \ + --hash=sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9 \ + --hash=sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae \ + --hash=sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19 \ + --hash=sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d \ + --hash=sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e \ + --hash=sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4 \ + --hash=sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7 \ + --hash=sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312 \ + --hash=sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92 \ + --hash=sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31 \ + --hash=sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c \ + --hash=sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f \ + --hash=sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99 \ + --hash=sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b \ + --hash=sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15 \ + --hash=sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392 \ + --hash=sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f \ + --hash=sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8 \ + --hash=sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491 \ + --hash=sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0 \ + --hash=sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc \ + --hash=sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0 \ + --hash=sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f \ + --hash=sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a \ + --hash=sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40 \ + --hash=sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927 \ + --hash=sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849 \ + --hash=sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce \ + --hash=sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14 \ + --hash=sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05 \ + --hash=sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c \ + --hash=sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c \ + --hash=sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a \ + --hash=sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc \ + --hash=sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34 \ + --hash=sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9 \ + --hash=sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096 \ + --hash=sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14 \ + --hash=sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30 \ + --hash=sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b \ + --hash=sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b \ + --hash=sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942 \ + --hash=sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db \ + --hash=sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5 \ + --hash=sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b \ + --hash=sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce \ + --hash=sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669 \ + --hash=sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0 \ + --hash=sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018 \ + --hash=sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93 \ + --hash=sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe \ + --hash=sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049 \ + --hash=sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a \ + --hash=sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef \ + --hash=sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2 \ + --hash=sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca \ + --hash=sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16 \ + --hash=sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f \ + --hash=sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb \ + --hash=sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1 \ + --hash=sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557 \ + --hash=sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37 \ + --hash=sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7 \ + --hash=sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72 \ + --hash=sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c \ + --hash=sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9 + # via requests +click==8.2.1 \ + --hash=sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202 \ + --hash=sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b + # via black +colorama==0.4.6 \ + --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ + --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 + # via -r third_party/pip/requirements.in +coverage==7.5.1 \ + --hash=sha256:0646599e9b139988b63704d704af8e8df7fa4cbc4a1f33df69d97f36cb0a38de \ + --hash=sha256:0cdcbc320b14c3e5877ee79e649677cb7d89ef588852e9583e6b24c2e5072661 \ + --hash=sha256:0d0a0f5e06881ecedfe6f3dd2f56dcb057b6dbeb3327fd32d4b12854df36bf26 \ + --hash=sha256:1434e088b41594baa71188a17533083eabf5609e8e72f16ce8c186001e6b8c41 \ + --hash=sha256:16db7f26000a07efcf6aea00316f6ac57e7d9a96501e990a36f40c965ec7a95d \ + --hash=sha256:1cc0fe9b0b3a8364093c53b0b4c0c2dd4bb23acbec4c9240b5f284095ccf7981 \ + --hash=sha256:1fc81d5878cd6274ce971e0a3a18a8803c3fe25457165314271cf78e3aae3aa2 \ + --hash=sha256:2ec92012fefebee89a6b9c79bc39051a6cb3891d562b9270ab10ecfdadbc0c34 \ + --hash=sha256:39afcd3d4339329c5f58de48a52f6e4e50f6578dd6099961cf22228feb25f38f \ + --hash=sha256:4a7b0ceee8147444347da6a66be737c9d78f3353b0681715b668b72e79203e4a \ + --hash=sha256:4a9ca3f2fae0088c3c71d743d85404cec8df9be818a005ea065495bedc33da35 \ + --hash=sha256:4bf0655ab60d754491004a5efd7f9cccefcc1081a74c9ef2da4735d6ee4a6223 \ + --hash=sha256:4cc37def103a2725bc672f84bd939a6fe4522310503207aae4d56351644682f1 \ + --hash=sha256:4fc84a37bfd98db31beae3c2748811a3fa72bf2007ff7902f68746d9757f3746 \ + --hash=sha256:5037f8fcc2a95b1f0e80585bd9d1ec31068a9bcb157d9750a172836e98bc7a90 \ + --hash=sha256:54de9ef3a9da981f7af93eafde4ede199e0846cd819eb27c88e2b712aae9708c \ + --hash=sha256:556cf1a7cbc8028cb60e1ff0be806be2eded2daf8129b8811c63e2b9a6c43bca \ + --hash=sha256:57e0204b5b745594e5bc14b9b50006da722827f0b8c776949f1135677e88d0b8 \ + --hash=sha256:5a5740d1fb60ddf268a3811bcd353de34eb56dc24e8f52a7f05ee513b2d4f596 \ + --hash=sha256:5c3721c2c9e4c4953a41a26c14f4cef64330392a6d2d675c8b1db3b645e31f0e \ + --hash=sha256:5fa567e99765fe98f4e7d7394ce623e794d7cabb170f2ca2ac5a4174437e90dd \ + --hash=sha256:5fd215c0c7d7aab005221608a3c2b46f58c0285a819565887ee0b718c052aa4e \ + --hash=sha256:6175d1a0559986c6ee3f7fccfc4a90ecd12ba0a383dcc2da30c2b9918d67d8a3 \ + --hash=sha256:61c4bf1ba021817de12b813338c9be9f0ad5b1e781b9b340a6d29fc13e7c1b5e \ + --hash=sha256:6537e7c10cc47c595828b8a8be04c72144725c383c4702703ff4e42e44577312 \ + --hash=sha256:68f962d9b72ce69ea8621f57551b2fa9c70509af757ee3b8105d4f51b92b41a7 \ + --hash=sha256:7352b9161b33fd0b643ccd1f21f3a3908daaddf414f1c6cb9d3a2fd618bf2572 \ + --hash=sha256:796a79f63eca8814ca3317a1ea443645c9ff0d18b188de470ed7ccd45ae79428 \ + --hash=sha256:79afb6197e2f7f60c4824dd4b2d4c2ec5801ceb6ba9ce5d2c3080e5660d51a4f \ + --hash=sha256:7a588d39e0925f6a2bff87154752481273cdb1736270642aeb3635cb9b4cad07 \ + --hash=sha256:8748731ad392d736cc9ccac03c9845b13bb07d020a33423fa5b3a36521ac6e4e \ + --hash=sha256:8fe7502616b67b234482c3ce276ff26f39ffe88adca2acf0261df4b8454668b4 \ + --hash=sha256:9314d5678dcc665330df5b69c1e726a0e49b27df0461c08ca12674bcc19ef136 \ + --hash=sha256:9735317685ba6ec7e3754798c8871c2f49aa5e687cc794a0b1d284b2389d1bd5 \ + --hash=sha256:9981706d300c18d8b220995ad22627647be11a4276721c10911e0e9fa44c83e8 \ + --hash=sha256:9e78295f4144f9dacfed4f92935fbe1780021247c2fabf73a819b17f0ccfff8d \ + --hash=sha256:b016ea6b959d3b9556cb401c55a37547135a587db0115635a443b2ce8f1c7228 \ + --hash=sha256:b6cf3764c030e5338e7f61f95bd21147963cf6aa16e09d2f74f1fa52013c1206 \ + --hash=sha256:beccf7b8a10b09c4ae543582c1319c6df47d78fd732f854ac68d518ee1fb97fa \ + --hash=sha256:c0884920835a033b78d1c73b6d3bbcda8161a900f38a488829a83982925f6c2e \ + --hash=sha256:c3e757949f268364b96ca894b4c342b41dc6f8f8b66c37878aacef5930db61be \ + --hash=sha256:ca498687ca46a62ae590253fba634a1fe9836bc56f626852fb2720f334c9e4e5 \ + --hash=sha256:d1d0d98d95dd18fe29dc66808e1accf59f037d5716f86a501fc0256455219668 \ + --hash=sha256:d21918e9ef11edf36764b93101e2ae8cc82aa5efdc7c5a4e9c6c35a48496d601 \ + --hash=sha256:d7fed867ee50edf1a0b4a11e8e5d0895150e572af1cd6d315d557758bfa9c057 \ + --hash=sha256:db66fc317a046556a96b453a58eced5024af4582a8dbdc0c23ca4dbc0d5b3146 \ + --hash=sha256:dde0070c40ea8bb3641e811c1cfbf18e265d024deff6de52c5950677a8fb1e0f \ + --hash=sha256:df4e745a81c110e7446b1cc8131bf986157770fa405fe90e15e850aaf7619bc8 \ + --hash=sha256:e2213def81a50519d7cc56ed643c9e93e0247f5bbe0d1247d15fa520814a7cd7 \ + --hash=sha256:ef48e2707fb320c8f139424a596f5b69955a85b178f15af261bab871873bb987 \ + --hash=sha256:f152cbf5b88aaeb836127d920dd0f5e7edff5a66f10c079157306c4343d86c19 \ + --hash=sha256:fc0b4d8bfeabd25ea75e94632f5b6e047eef8adaed0c2161ada1e922e7f7cece + # via -r third_party/pip/requirements.in +cyclonedx-python-lib==6.4.4 \ + --hash=sha256:1b6f9109b6b9e91636dff822c2de90a05c0c8af120317713c1b879dbfdebdff8 \ + --hash=sha256:c366619cc4effd528675f1f7a7a00be30b6695ff03f49c64880ad15acbebc341 + # via pip-audit +defusedxml==0.7.1 \ + --hash=sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69 \ + --hash=sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61 + # via py-serializable +dill==0.4.0 \ + --hash=sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0 \ + --hash=sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049 + # via pylint +exceptiongroup==1.3.0 \ + --hash=sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10 \ + --hash=sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88 + # via pytest +filelock==3.18.0 \ + --hash=sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2 \ + --hash=sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de + # via cachecontrol +html5lib==1.1 \ + --hash=sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d \ + --hash=sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f + # via pip-audit +idna==3.10 \ + --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ + --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 + # via requests +iniconfig==2.1.0 \ + --hash=sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7 \ + --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 + # via pytest +isort==5.13.2 \ + --hash=sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109 \ + --hash=sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6 + # via + # -r third_party/pip/requirements.in + # pylint +license-expression==30.4.4 \ + --hash=sha256:421788fdcadb41f049d2dc934ce666626265aeccefddd25e162a26f23bcbf8a4 \ + --hash=sha256:73448f0aacd8d0808895bdc4b2c8e01a8d67646e4188f887375398c761f340fd + # via cyclonedx-python-lib +mako==1.3.10 \ + --hash=sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28 \ + --hash=sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59 + # via pytest-bdd +markdown-it-py==4.0.0 \ + --hash=sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147 \ + --hash=sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3 + # via rich +markupsafe==3.0.2 \ + --hash=sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4 \ + --hash=sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30 \ + --hash=sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0 \ + --hash=sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9 \ + --hash=sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396 \ + --hash=sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13 \ + --hash=sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028 \ + --hash=sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca \ + --hash=sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557 \ + --hash=sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832 \ + --hash=sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0 \ + --hash=sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b \ + --hash=sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579 \ + --hash=sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a \ + --hash=sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c \ + --hash=sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff \ + --hash=sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c \ + --hash=sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22 \ + --hash=sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094 \ + --hash=sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb \ + --hash=sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e \ + --hash=sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5 \ + --hash=sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a \ + --hash=sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d \ + --hash=sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a \ + --hash=sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b \ + --hash=sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8 \ + --hash=sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225 \ + --hash=sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c \ + --hash=sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144 \ + --hash=sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f \ + --hash=sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87 \ + --hash=sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d \ + --hash=sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93 \ + --hash=sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf \ + --hash=sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158 \ + --hash=sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84 \ + --hash=sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb \ + --hash=sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48 \ + --hash=sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171 \ + --hash=sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c \ + --hash=sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6 \ + --hash=sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd \ + --hash=sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d \ + --hash=sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1 \ + --hash=sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d \ + --hash=sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca \ + --hash=sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a \ + --hash=sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29 \ + --hash=sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe \ + --hash=sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798 \ + --hash=sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c \ + --hash=sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8 \ + --hash=sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f \ + --hash=sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f \ + --hash=sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a \ + --hash=sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178 \ + --hash=sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0 \ + --hash=sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79 \ + --hash=sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430 \ + --hash=sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50 + # via mako +mccabe==0.7.0 \ + --hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \ + --hash=sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e + # via pylint +mdurl==0.1.2 \ + --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ + --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba + # via markdown-it-py +msgpack==1.1.1 \ + --hash=sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8 \ + --hash=sha256:1abfc6e949b352dadf4bce0eb78023212ec5ac42f6abfd469ce91d783c149c2a \ + --hash=sha256:1b13fe0fb4aac1aa5320cd693b297fe6fdef0e7bea5518cbc2dd5299f873ae90 \ + --hash=sha256:1d75f3807a9900a7d575d8d6674a3a47e9f227e8716256f35bc6f03fc597ffbf \ + --hash=sha256:2fbbc0b906a24038c9958a1ba7ae0918ad35b06cb449d398b76a7d08470b0ed9 \ + --hash=sha256:33be9ab121df9b6b461ff91baac6f2731f83d9b27ed948c5b9d1978ae28bf157 \ + --hash=sha256:353b6fc0c36fde68b661a12949d7d49f8f51ff5fa019c1e47c87c4ff34b080ed \ + --hash=sha256:36043272c6aede309d29d56851f8841ba907a1a3d04435e43e8a19928e243c1d \ + --hash=sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0 \ + --hash=sha256:3a89cd8c087ea67e64844287ea52888239cbd2940884eafd2dcd25754fb72232 \ + --hash=sha256:40eae974c873b2992fd36424a5d9407f93e97656d999f43fca9d29f820899084 \ + --hash=sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5 \ + --hash=sha256:435807eeb1bc791ceb3247d13c79868deb22184e1fc4224808750f0d7d1affc1 \ + --hash=sha256:4835d17af722609a45e16037bb1d4d78b7bdf19d6c0128116d178956618c4e88 \ + --hash=sha256:4a28e8072ae9779f20427af07f53bbb8b4aa81151054e882aee333b158da8752 \ + --hash=sha256:4d3237b224b930d58e9d83c81c0dba7aacc20fcc2f89c1e5423aa0529a4cd142 \ + --hash=sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac \ + --hash=sha256:4fd6b577e4541676e0cc9ddc1709d25014d3ad9a66caa19962c4f5de30fc09ef \ + --hash=sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323 \ + --hash=sha256:5692095123007180dca3e788bb4c399cc26626da51629a31d40207cb262e67f4 \ + --hash=sha256:5fd1b58e1431008a57247d6e7cc4faa41c3607e8e7d4aaf81f7c29ea013cb458 \ + --hash=sha256:61abccf9de335d9efd149e2fff97ed5974f2481b3353772e8e2dd3402ba2bd57 \ + --hash=sha256:61e35a55a546a1690d9d09effaa436c25ae6130573b6ee9829c37ef0f18d5e78 \ + --hash=sha256:6640fd979ca9a212e4bcdf6eb74051ade2c690b862b679bfcb60ae46e6dc4bfd \ + --hash=sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69 \ + --hash=sha256:6f64ae8fe7ffba251fecb8408540c34ee9df1c26674c50c4544d72dbf792e5ce \ + --hash=sha256:71ef05c1726884e44f8b1d1773604ab5d4d17729d8491403a705e649116c9558 \ + --hash=sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd \ + --hash=sha256:78426096939c2c7482bf31ef15ca219a9e24460289c00dd0b94411040bb73ad2 \ + --hash=sha256:79c408fcf76a958491b4e3b103d1c417044544b68e96d06432a189b43d1215c8 \ + --hash=sha256:7a17ac1ea6ec3c7687d70201cfda3b1e8061466f28f686c24f627cae4ea8efd0 \ + --hash=sha256:7da8831f9a0fdb526621ba09a281fadc58ea12701bc709e7b8cbc362feabc295 \ + --hash=sha256:870b9a626280c86cff9c576ec0d9cbcc54a1e5ebda9cd26dab12baf41fee218c \ + --hash=sha256:88d1e966c9235c1d4e2afac21ca83933ba59537e2e2727a999bf3f515ca2af26 \ + --hash=sha256:88daaf7d146e48ec71212ce21109b66e06a98e5e44dca47d853cbfe171d6c8d2 \ + --hash=sha256:8a8b10fdb84a43e50d38057b06901ec9da52baac6983d3f709d8507f3889d43f \ + --hash=sha256:8b17ba27727a36cb73aabacaa44b13090feb88a01d012c0f4be70c00f75048b4 \ + --hash=sha256:8b65b53204fe1bd037c40c4148d00ef918eb2108d24c9aaa20bc31f9810ce0a8 \ + --hash=sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9 \ + --hash=sha256:96decdfc4adcbc087f5ea7ebdcfd3dee9a13358cae6e81d54be962efc38f6338 \ + --hash=sha256:996f2609ddf0142daba4cefd767d6db26958aac8439ee41db9cc0db9f4c4c3a6 \ + --hash=sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a \ + --hash=sha256:a32747b1b39c3ac27d0670122b57e6e57f28eefb725e0b625618d1b59bf9d1e0 \ + --hash=sha256:a494554874691720ba5891c9b0b39474ba43ffb1aaf32a5dac874effb1619e1a \ + --hash=sha256:a8ef6e342c137888ebbfb233e02b8fbd689bb5b5fcc59b34711ac47ebd504478 \ + --hash=sha256:ae497b11f4c21558d95de9f64fff7053544f4d1a17731c866143ed6bb4591238 \ + --hash=sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7 \ + --hash=sha256:b8f93dcddb243159c9e4109c9750ba5b335ab8d48d9522c5308cd05d7e3ce600 \ + --hash=sha256:ba0c325c3f485dc54ec298d8b024e134acf07c10d494ffa24373bea729acf704 \ + --hash=sha256:bb29aaa613c0a1c40d1af111abf025f1732cab333f96f285d6a93b934738a68a \ + --hash=sha256:bba1be28247e68994355e028dcd668316db30c1f758d3241a7b903ac78dcd285 \ + --hash=sha256:cb643284ab0ed26f6957d969fe0dd8bb17beb567beb8998140b5e38a90974f6c \ + --hash=sha256:d182dac0221eb8faef2e6f44701812b467c02674a322c739355c39e94730cdbf \ + --hash=sha256:d275a9e3c81b1093c060c3837e580c37f47c51eca031f7b5fb76f7b8470f5f9b \ + --hash=sha256:d8b55ea20dc59b181d3f47103f113e6f28a5e1c89fd5b67b9140edb442ab67f2 \ + --hash=sha256:da8f41e602574ece93dbbda1fab24650d6bf2a24089f9e9dbb4f5730ec1e58ad \ + --hash=sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b \ + --hash=sha256:f5be6b6bc52fad84d010cb45433720327ce886009d862f46b26d4d154001994b \ + --hash=sha256:f6d58656842e1b2ddbe07f43f56b10a60f2ba5826164910968f5933e5178af75 + # via cachecontrol +mypy==1.11.1 \ + --hash=sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54 \ + --hash=sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a \ + --hash=sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72 \ + --hash=sha256:11965c2f571ded6239977b14deebd3f4c3abd9a92398712d6da3a772974fad69 \ + --hash=sha256:1a81cf05975fd61aec5ae16501a091cfb9f605dc3e3c878c0da32f250b74760b \ + --hash=sha256:2684d3f693073ab89d76da8e3921883019ea8a3ec20fa5d8ecca6a2db4c54bbe \ + --hash=sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4 \ + --hash=sha256:45df906e8b6804ef4b666af29a87ad9f5921aad091c79cc38e12198e220beabd \ + --hash=sha256:4c956b49c5d865394d62941b109728c5c596a415e9c5b2be663dd26a1ff07bc0 \ + --hash=sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525 \ + --hash=sha256:749fd3213916f1751fff995fccf20c6195cae941dc968f3aaadf9bb4e430e5a2 \ + --hash=sha256:79c07eb282cb457473add5052b63925e5cc97dfab9812ee65a7c7ab5e3cb551c \ + --hash=sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5 \ + --hash=sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de \ + --hash=sha256:a2b43895a0f8154df6519706d9bca8280cda52d3d9d1514b2d9c3e26792a0b74 \ + --hash=sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c \ + --hash=sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e \ + --hash=sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58 \ + --hash=sha256:b639dce63a0b19085213ec5fdd8cffd1d81988f47a2dec7100e93564f3e8fb3b \ + --hash=sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417 \ + --hash=sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411 \ + --hash=sha256:d44be7551689d9d47b7abc27c71257adfdb53f03880841a5db15ddb22dc63edb \ + --hash=sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03 \ + --hash=sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca \ + --hash=sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8 \ + --hash=sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08 \ + --hash=sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809 + # via -r third_party/pip/requirements.in +mypy-extensions==1.1.0 \ + --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \ + --hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558 + # via + # black + # mypy +packageurl-python==0.17.5 \ + --hash=sha256:a7be3f3ba70d705f738ace9bf6124f31920245a49fa69d4b416da7037dd2de61 \ + --hash=sha256:f0e55452ab37b5c192c443de1458e3f3b4d8ac27f747df6e8c48adeab081d321 + # via cyclonedx-python-lib +packaging==25.0 \ + --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ + --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f + # via + # black + # pip-audit + # pip-requirements-parser + # pytest + # pytest-bdd + # pytest-rerunfailures +parse==1.20.2 \ + --hash=sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558 \ + --hash=sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce + # via + # parse-type + # pytest-bdd +parse-type==0.6.6 \ + --hash=sha256:3ca79bbe71e170dfccc8ec6c341edfd1c2a0fc1e5cfd18330f93af938de2348c \ + --hash=sha256:513a3784104839770d690e04339a8b4d33439fcd5dd99f2e4580f9fc1097bfb2 + # via pytest-bdd +pathspec==0.12.1 \ + --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ + --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712 + # via black +pip-api==0.0.34 \ + --hash=sha256:8b2d7d7c37f2447373aa2cf8b1f60a2f2b27a84e1e9e0294a3f6ef10eb3ba6bb \ + --hash=sha256:9b75e958f14c5a2614bae415f2adf7eeb54d50a2cfbe7e24fd4826471bac3625 + # via pip-audit +pip-audit==2.7.0 \ + --hash=sha256:67740c5b1d5d967a258c3dfefc46f9713a2819c48062505ddf4b29de101c2b75 \ + --hash=sha256:83e039740653eb9ef1a78b1540ed441600cd88a560588ba2c0a169180685a522 + # via -r third_party/pip/requirements.in +pip-requirements-parser==32.0.1 \ + --hash=sha256:4659bc2a667783e7a15d190f6fccf8b2486685b6dba4c19c3876314769c57526 \ + --hash=sha256:b4fa3a7a0be38243123cf9d1f3518da10c51bdb165a2b2985566247f9155a7d3 + # via pip-audit +platformdirs==4.3.8 \ + --hash=sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc \ + --hash=sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4 + # via + # black + # pylint +pluggy==1.6.0 \ + --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ + --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + # via pytest +py-cpuinfo==9.0.0 \ + --hash=sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690 \ + --hash=sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5 + # via pytest-benchmark +py-serializable==1.1.2 \ + --hash=sha256:801be61b0a1ba64c3861f7c624f1de5cfbbabf8b458acc9cdda91e8f7e5effa1 \ + --hash=sha256:89af30bc319047d4aa0d8708af412f6ce73835e18bacf1a080028bb9e2f42bdb + # via cyclonedx-python-lib +pygments==2.19.2 \ + --hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \ + --hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b + # via rich +pylint==3.1.0 \ + --hash=sha256:507a5b60953874766d8a366e8e8c7af63e058b26345cfcb5f91f89d987fd6b74 \ + --hash=sha256:6a69beb4a6f63debebaab0a3477ecd0f559aa726af4954fc948c51f7a2549e23 + # via -r third_party/pip/requirements.in +pyparsing==3.2.3 \ + --hash=sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf \ + --hash=sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be + # via pip-requirements-parser +pytest==8.1.1 \ + --hash=sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7 \ + --hash=sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044 + # via + # -r third_party/pip/requirements.in + # pytest-bdd + # pytest-benchmark + # pytest-mock + # pytest-rerunfailures + # pytest-timeout +pytest-bdd==7.1.1 \ + --hash=sha256:1b41a10a8391f1849f0a1524d77b2991d0d8042d2253ace9b616b89b4a9b97a7 \ + --hash=sha256:331621122a46f9881110ae0dd43fedb29e96e8202de29256be3d76c53b57c1d2 + # via -r third_party/pip/requirements.in +pytest-benchmark==4.0.0 \ + --hash=sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1 \ + --hash=sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6 + # via -r third_party/pip/requirements.in +pytest-mock==3.12.0 \ + --hash=sha256:0972719a7263072da3a21c7f4773069bcc7486027d7e8e1f81d98a47e701bc4f \ + --hash=sha256:31a40f038c22cad32287bb43932054451ff5583ff094bca6f675df2f8bc1a6e9 + # via -r third_party/pip/requirements.in +pytest-rerunfailures==14.0 \ + --hash=sha256:4197bdd2eaeffdbf50b5ea6e7236f47ff0e44d1def8dae08e409f536d84e7b32 \ + --hash=sha256:4a400bcbcd3c7a4ad151ab8afac123d90eca3abe27f98725dc4d9702887d2e92 + # via -r third_party/pip/requirements.in +pytest-timeout==2.3.1 \ + --hash=sha256:12397729125c6ecbdaca01035b9e5239d4db97352320af155b3f5de1ba5165d9 \ + --hash=sha256:68188cb703edfc6a18fad98dc25a3c61e9f24d644b0b70f33af545219fc7813e + # via -r third_party/pip/requirements.in +referencing==0.36.2 \ + --hash=sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa \ + --hash=sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0 + # via types-jsonschema +requests==2.32.4 \ + --hash=sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c \ + --hash=sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422 + # via + # cachecontrol + # pip-audit +rich==14.1.0 \ + --hash=sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f \ + --hash=sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8 + # via pip-audit +rpds-py==0.27.0 \ + --hash=sha256:010c4843a3b92b54373e3d2291a7447d6c3fc29f591772cc2ea0e9f5c1da434b \ + --hash=sha256:05284439ebe7d9f5f5a668d4d8a0a1d851d16f7d47c78e1fab968c8ad30cab04 \ + --hash=sha256:0665be515767dc727ffa5f74bd2ef60b0ff85dad6bb8f50d91eaa6b5fb226f51 \ + --hash=sha256:069e0384a54f427bd65d7fda83b68a90606a3835901aaff42185fcd94f5a9295 \ + --hash=sha256:08680820d23df1df0a0260f714d12966bc6c42d02e8055a91d61e03f0c47dda0 \ + --hash=sha256:0954e3a92e1d62e83a54ea7b3fdc9efa5d61acef8488a8a3d31fdafbfb00460d \ + --hash=sha256:09965b314091829b378b60607022048953e25f0b396c2b70e7c4c81bcecf932e \ + --hash=sha256:0c431bfb91478d7cbe368d0a699978050d3b112d7f1d440a41e90faa325557fd \ + --hash=sha256:0f401c369186a5743694dd9fc08cba66cf70908757552e1f714bfc5219c655b5 \ + --hash=sha256:0f4f69d7a4300fbf91efb1fb4916421bd57804c01ab938ab50ac9c4aa2212f03 \ + --hash=sha256:11e8e28c0ba0373d052818b600474cfee2fafa6c9f36c8587d217b13ee28ca7d \ + --hash=sha256:130c1ffa5039a333f5926b09e346ab335f0d4ec393b030a18549a7c7e7c2cea4 \ + --hash=sha256:1321bce595ad70e80f97f998db37356b2e22cf98094eba6fe91782e626da2f71 \ + --hash=sha256:13bbc4846ae4c993f07c93feb21a24d8ec637573d567a924b1001e81c8ae80f9 \ + --hash=sha256:14f028eb47f59e9169bfdf9f7ceafd29dd64902141840633683d0bad5b04ff34 \ + --hash=sha256:15ea4d2e182345dd1b4286593601d766411b43f868924afe297570658c31a62b \ + --hash=sha256:181bc29e59e5e5e6e9d63b143ff4d5191224d355e246b5a48c88ce6b35c4e466 \ + --hash=sha256:183f5e221ba3e283cd36fdfbe311d95cd87699a083330b4f792543987167eff1 \ + --hash=sha256:184f0d7b342967f6cda94a07d0e1fae177d11d0b8f17d73e06e36ac02889f303 \ + --hash=sha256:190d7285cd3bb6d31d37a0534d7359c1ee191eb194c511c301f32a4afa5a1dd4 \ + --hash=sha256:19c990fdf5acecbf0623e906ae2e09ce1c58947197f9bced6bbd7482662231c4 \ + --hash=sha256:1d66f45b9399036e890fb9c04e9f70c33857fd8f58ac8db9f3278cfa835440c3 \ + --hash=sha256:203f581accef67300a942e49a37d74c12ceeef4514874c7cede21b012613ca2c \ + --hash=sha256:20e222a44ae9f507d0f2678ee3dd0c45ec1e930f6875d99b8459631c24058aec \ + --hash=sha256:2406d034635d1497c596c40c85f86ecf2bf9611c1df73d14078af8444fe48031 \ + --hash=sha256:249ab91ceaa6b41abc5f19513cb95b45c6f956f6b89f1fe3d99c81255a849f9e \ + --hash=sha256:25a4aebf8ca02bbb90a9b3e7a463bbf3bee02ab1c446840ca07b1695a68ce424 \ + --hash=sha256:27bac29bbbf39601b2aab474daf99dbc8e7176ca3389237a23944b17f8913d97 \ + --hash=sha256:299a245537e697f28a7511d01038c310ac74e8ea213c0019e1fc65f52c0dcb23 \ + --hash=sha256:2cff9bdd6c7b906cc562a505c04a57d92e82d37200027e8d362518df427f96cd \ + --hash=sha256:2e307cb5f66c59ede95c00e93cd84190a5b7f3533d7953690b2036780622ba81 \ + --hash=sha256:2e39169ac6aae06dd79c07c8a69d9da867cef6a6d7883a0186b46bb46ccfb0c3 \ + --hash=sha256:2fe6e18e5c8581f0361b35ae575043c7029d0a92cb3429e6e596c2cdde251432 \ + --hash=sha256:3001013dae10f806380ba739d40dee11db1ecb91684febb8406a87c2ded23dae \ + --hash=sha256:32196b5a99821476537b3f7732432d64d93a58d680a52c5e12a190ee0135d8b5 \ + --hash=sha256:33ba649a6e55ae3808e4c39e01580dc9a9b0d5b02e77b66bb86ef117922b1264 \ + --hash=sha256:341d8acb6724c0c17bdf714319c393bb27f6d23d39bc74f94221b3e59fc31828 \ + --hash=sha256:343cf24de9ed6c728abefc5d5c851d5de06497caa7ac37e5e65dd572921ed1b5 \ + --hash=sha256:36184b44bf60a480863e51021c26aca3dfe8dd2f5eeabb33622b132b9d8b8b54 \ + --hash=sha256:3841f66c1ffdc6cebce8aed64e36db71466f1dc23c0d9a5592e2a782a3042c79 \ + --hash=sha256:4045e2fc4b37ec4b48e8907a5819bdd3380708c139d7cc358f03a3653abedb89 \ + --hash=sha256:419dd9c98bcc9fb0242be89e0c6e922df333b975d4268faa90d58499fd9c9ebe \ + --hash=sha256:42894616da0fc0dcb2ec08a77896c3f56e9cb2f4b66acd76fc8992c3557ceb1c \ + --hash=sha256:42ccc57ff99166a55a59d8c7d14f1a357b7749f9ed3584df74053fd098243451 \ + --hash=sha256:4300e15e7d03660f04be84a125d1bdd0e6b2f674bc0723bc0fd0122f1a4585dc \ + --hash=sha256:443d239d02d9ae55b74015234f2cd8eb09e59fbba30bf60baeb3123ad4c6d5ff \ + --hash=sha256:44524b96481a4c9b8e6c46d6afe43fa1fb485c261e359fbe32b63ff60e3884d8 \ + --hash=sha256:45d04a73c54b6a5fd2bab91a4b5bc8b426949586e61340e212a8484919183859 \ + --hash=sha256:46f48482c1a4748ab2773f75fffbdd1951eb59794e32788834b945da857c47a8 \ + --hash=sha256:4790c9d5dd565ddb3e9f656092f57268951398cef52e364c405ed3112dc7c7c1 \ + --hash=sha256:4bc262ace5a1a7dc3e2eac2fa97b8257ae795389f688b5adf22c5db1e2431c43 \ + --hash=sha256:4c3f8a0d4802df34fcdbeb3dfe3a4d8c9a530baea8fafdf80816fcaac5379d83 \ + --hash=sha256:5355527adaa713ab693cbce7c1e0ec71682f599f61b128cf19d07e5c13c9b1f1 \ + --hash=sha256:555ed147cbe8c8f76e72a4c6cd3b7b761cbf9987891b9448808148204aed74a5 \ + --hash=sha256:55d42a0ef2bdf6bc81e1cc2d49d12460f63c6ae1423c4f4851b828e454ccf6f1 \ + --hash=sha256:59195dc244fc183209cf8a93406889cadde47dfd2f0a6b137783aa9c56d67c85 \ + --hash=sha256:59714ab0a5af25d723d8e9816638faf7f4254234decb7d212715c1aa71eee7be \ + --hash=sha256:5b3a5c8089eed498a3af23ce87a80805ff98f6ef8f7bdb70bd1b7dae5105f6ac \ + --hash=sha256:5d6790ff400254137b81b8053b34417e2c46921e302d655181d55ea46df58cf7 \ + --hash=sha256:5df559e9e7644d9042f626f2c3997b555f347d7a855a15f170b253f6c5bfe358 \ + --hash=sha256:5fa01b3d5e3b7d97efab65bd3d88f164e289ec323a8c033c5c38e53ee25c007e \ + --hash=sha256:61490d57e82e23b45c66f96184237994bfafa914433b8cd1a9bb57fecfced59d \ + --hash=sha256:6168af0be75bba990a39f9431cdfae5f0ad501f4af32ae62e8856307200517b8 \ + --hash=sha256:64a0fe3f334a40b989812de70160de6b0ec7e3c9e4a04c0bbc48d97c5d3600ae \ + --hash=sha256:64f689ab822f9b5eb6dfc69893b4b9366db1d2420f7db1f6a2adf2a9ca15ad64 \ + --hash=sha256:699c346abc73993962cac7bb4f02f58e438840fa5458a048d3a178a7a670ba86 \ + --hash=sha256:6b96b0b784fe5fd03beffff2b1533dc0d85e92bab8d1b2c24ef3a5dc8fac5669 \ + --hash=sha256:6bde37765564cd22a676dd8101b657839a1854cfaa9c382c5abf6ff7accfd4ae \ + --hash=sha256:6c135708e987f46053e0a1246a206f53717f9fadfba27174a9769ad4befba5c3 \ + --hash=sha256:6c27a7054b5224710fcfb1a626ec3ff4f28bcb89b899148c72873b18210e446b \ + --hash=sha256:6de6a7f622860af0146cb9ee148682ff4d0cea0b8fd3ad51ce4d40efb2f061d0 \ + --hash=sha256:737005088449ddd3b3df5a95476ee1c2c5c669f5c30eed909548a92939c0e12d \ + --hash=sha256:7451ede3560086abe1aa27dcdcf55cd15c96b56f543fb12e5826eee6f721f858 \ + --hash=sha256:7873b65686a6471c0037139aa000d23fe94628e0daaa27b6e40607c90e3f5ec4 \ + --hash=sha256:79af163a4b40bbd8cfd7ca86ec8b54b81121d3b213b4435ea27d6568bcba3e9d \ + --hash=sha256:7aed8118ae20515974650d08eb724150dc2e20c2814bcc307089569995e88a14 \ + --hash=sha256:7cf9bc4508efb18d8dff6934b602324eb9f8c6644749627ce001d6f38a490889 \ + --hash=sha256:7e57906e38583a2cba67046a09c2637e23297618dc1f3caddbc493f2be97c93f \ + --hash=sha256:7ec85994f96a58cf7ed288caa344b7fe31fd1d503bdf13d7331ead5f70ab60d5 \ + --hash=sha256:81f81bbd7cdb4bdc418c09a73809abeda8f263a6bf8f9c7f93ed98b5597af39d \ + --hash=sha256:86aca1616922b40d8ac1b3073a1ead4255a2f13405e5700c01f7c8d29a03972d \ + --hash=sha256:88051c3b7d5325409f433c5a40328fcb0685fc04e5db49ff936e910901d10114 \ + --hash=sha256:887ab1f12b0d227e9260558a4a2320024b20102207ada65c43e1ffc4546df72e \ + --hash=sha256:8a06aa1197ec0281eb1d7daf6073e199eb832fe591ffa329b88bae28f25f5fe5 \ + --hash=sha256:8a1dca5507fa1337f75dcd5070218b20bc68cf8844271c923c1b79dfcbc20391 \ + --hash=sha256:8b23cf252f180cda89220b378d917180f29d313cd6a07b2431c0d3b776aae86f \ + --hash=sha256:8d0e09cf4863c74106b5265c2c310f36146e2b445ff7b3018a56799f28f39f6f \ + --hash=sha256:8de567dec6d451649a781633d36f5c7501711adee329d76c095be2178855b042 \ + --hash=sha256:90fb790138c1a89a2e58c9282fe1089638401f2f3b8dddd758499041bc6e0774 \ + --hash=sha256:92f3b3ec3e6008a1fe00b7c0946a170f161ac00645cde35e3c9a68c2475e8156 \ + --hash=sha256:935afcdea4751b0ac918047a2df3f720212892347767aea28f5b3bf7be4f27c0 \ + --hash=sha256:9a0ff7ee28583ab30a52f371b40f54e7138c52ca67f8ca17ccb7ccf0b383cb5f \ + --hash=sha256:9ad08547995a57e74fea6abaf5940d399447935faebbd2612b3b0ca6f987946b \ + --hash=sha256:9b2a4e17bfd68536c3b801800941c95a1d4a06e3cada11c146093ba939d9638d \ + --hash=sha256:9b78430703cfcf5f5e86eb74027a1ed03a93509273d7c705babb547f03e60016 \ + --hash=sha256:9d0f92b78cfc3b74a42239fdd8c1266f4715b573204c234d2f9fc3fc7a24f185 \ + --hash=sha256:9da162b718b12c4219eeeeb68a5b7552fbc7aadedf2efee440f88b9c0e54b45d \ + --hash=sha256:a00c91104c173c9043bc46f7b30ee5e6d2f6b1149f11f545580f5d6fdff42c0b \ + --hash=sha256:a029be818059870664157194e46ce0e995082ac49926f1423c1f058534d2aaa9 \ + --hash=sha256:a1b3db5fae5cbce2131b7420a3f83553d4d89514c03d67804ced36161fe8b6b2 \ + --hash=sha256:a4cf32a26fa744101b67bfd28c55d992cd19438aff611a46cac7f066afca8fd4 \ + --hash=sha256:aa0bf113d15e8abdfee92aa4db86761b709a09954083afcb5bf0f952d6065fdb \ + --hash=sha256:ab47fe727c13c09d0e6f508e3a49e545008e23bf762a245b020391b621f5b726 \ + --hash=sha256:af22763a0a1eff106426a6e1f13c4582e0d0ad89c1493ab6c058236174cd6c6a \ + --hash=sha256:af9d4fd79ee1cc8e7caf693ee02737daabfc0fcf2773ca0a4735b356c8ad6f7c \ + --hash=sha256:b1fef1f13c842a39a03409e30ca0bf87b39a1e2a305a9924deadb75a43105d23 \ + --hash=sha256:b2eff8ee57c5996b0d2a07c3601fb4ce5fbc37547344a26945dd9e5cbd1ed27a \ + --hash=sha256:b4c4fbbcff474e1e5f38be1bf04511c03d492d42eec0babda5d03af3b5589374 \ + --hash=sha256:b8a4131698b6992b2a56015f51646711ec5d893a0b314a4b985477868e240c87 \ + --hash=sha256:b8a7acf04fda1f30f1007f3cc96d29d8cf0a53e626e4e1655fdf4eabc082d367 \ + --hash=sha256:ba783541be46f27c8faea5a6645e193943c17ea2f0ffe593639d906a327a9bcc \ + --hash=sha256:be0744661afbc4099fef7f4e604e7f1ea1be1dd7284f357924af12a705cc7d5c \ + --hash=sha256:be3964f7312ea05ed283b20f87cb533fdc555b2e428cc7be64612c0b2124f08c \ + --hash=sha256:be806e2961cd390a89d6c3ce8c2ae34271cfcd05660f716257838bb560f1c3b6 \ + --hash=sha256:bec77545d188f8bdd29d42bccb9191682a46fb2e655e3d1fb446d47c55ac3b8d \ + --hash=sha256:c10d92fb6d7fd827e44055fcd932ad93dac6a11e832d51534d77b97d1d85400f \ + --hash=sha256:c3782fb753aa825b4ccabc04292e07897e2fd941448eabf666856c5530277626 \ + --hash=sha256:c9ce7a9e967afc0a2af7caa0d15a3e9c1054815f73d6a8cb9225b61921b419bd \ + --hash=sha256:cb0702c12983be3b2fab98ead349ac63a98216d28dda6f518f52da5498a27a1b \ + --hash=sha256:cbc619e84a5e3ab2d452de831c88bdcad824414e9c2d28cd101f94dbdf26329c \ + --hash=sha256:ce4ed8e0c7dbc5b19352b9c2c6131dd23b95fa8698b5cdd076307a33626b72dc \ + --hash=sha256:ce96ab0bdfcef1b8c371ada2100767ace6804ea35aacce0aef3aeb4f3f499ca8 \ + --hash=sha256:cf824aceaeffff029ccfba0da637d432ca71ab21f13e7f6f5179cd88ebc77a8a \ + --hash=sha256:d2a81bdcfde4245468f7030a75a37d50400ac2455c3a4819d9d550c937f90ab5 \ + --hash=sha256:d2cc2b34f9e1d31ce255174da82902ad75bd7c0d88a33df54a77a22f2ef421ee \ + --hash=sha256:d2f184336bc1d6abfaaa1262ed42739c3789b1e3a65a29916a615307d22ffd2e \ + --hash=sha256:d3c622c39f04d5751408f5b801ecb527e6e0a471b367f420a877f7a660d583f6 \ + --hash=sha256:d7cf5e726b6fa977e428a61880fb108a62f28b6d0c7ef675b117eaff7076df49 \ + --hash=sha256:d85d784c619370d9329bbd670f41ff5f2ae62ea4519761b679d0f57f0f0ee267 \ + --hash=sha256:d93ebdb82363d2e7bec64eecdc3632b59e84bd270d74fe5be1659f7787052f9b \ + --hash=sha256:db8a6313dbac934193fc17fe7610f70cd8181c542a91382531bef5ed785e5615 \ + --hash=sha256:dbc2ab5d10544eb485baa76c63c501303b716a5c405ff2469a1d8ceffaabf622 \ + --hash=sha256:dbd749cff1defbde270ca346b69b3baf5f1297213ef322254bf2a28537f0b046 \ + --hash=sha256:dc662bc9375a6a394b62dfd331874c434819f10ee3902123200dbcf116963f89 \ + --hash=sha256:dc6b0d5a1ea0318ef2def2b6a55dccf1dcaf77d605672347271ed7b829860765 \ + --hash=sha256:dc79d192fb76fc0c84f2c58672c17bbbc383fd26c3cdc29daae16ce3d927e8b2 \ + --hash=sha256:dd2c1d27ebfe6a015cfa2005b7fe8c52d5019f7bbdd801bc6f7499aab9ae739e \ + --hash=sha256:dea0808153f1fbbad772669d906cddd92100277533a03845de6893cadeffc8be \ + --hash=sha256:e0d7151a1bd5d0a203a5008fc4ae51a159a610cb82ab0a9b2c4d80241745582e \ + --hash=sha256:e14aab02258cb776a108107bd15f5b5e4a1bbaa61ef33b36693dfab6f89d54f9 \ + --hash=sha256:e24d8031a2c62f34853756d9208eeafa6b940a1efcbfe36e8f57d99d52bb7261 \ + --hash=sha256:e36c80c49853b3ffda7aa1831bf175c13356b210c73128c861f3aa93c3cc4015 \ + --hash=sha256:e377e4cf8795cdbdff75b8f0223d7b6c68ff4fef36799d88ccf3a995a91c0112 \ + --hash=sha256:e3acb9c16530362aeaef4e84d57db357002dc5cbfac9a23414c3e73c08301ab2 \ + --hash=sha256:e3dc8d4ede2dbae6c0fc2b6c958bf51ce9fd7e9b40c0f5b8835c3fde44f5807d \ + --hash=sha256:e6491658dd2569f05860bad645569145c8626ac231877b0fb2d5f9bcb7054089 \ + --hash=sha256:eb91d252b35004a84670dfeafadb042528b19842a0080d8b53e5ec1128e8f433 \ + --hash=sha256:f0396e894bd1e66c74ecbc08b4f6a03dc331140942c4b1d345dd131b68574a60 \ + --hash=sha256:f09c9d4c26fa79c1bad927efb05aca2391350b8e61c38cbc0d7d3c814e463124 \ + --hash=sha256:f3cd110e02c5bf17d8fb562f6c9df5c20e73029d587cf8602a2da6c5ef1e32cb \ + --hash=sha256:f7a37dd208f0d658e0487522078b1ed68cd6bce20ef4b5a915d2809b9094b410 \ + --hash=sha256:fae4a01ef8c4cb2bbe92ef2063149596907dc4a881a8d26743b3f6b304713171 \ + --hash=sha256:fc327f4497b7087d06204235199daf208fd01c82d80465dc5efa4ec9df1c5b4e \ + --hash=sha256:fcc01c57ce6e70b728af02b2401c5bc853a9e14eb07deda30624374f0aebfe42 \ + --hash=sha256:fde355b02934cc6b07200cc3b27ab0c15870a757d1a72fd401aa92e2ea3c6bfe + # via referencing +ruff==0.5.5 \ + --hash=sha256:00817603822a3e42b80f7c3298c8269e09f889ee94640cd1fc7f9329788d7bf8 \ + --hash=sha256:187a60f555e9f865a2ff2c6984b9afeffa7158ba6e1eab56cb830404c942b0f3 \ + --hash=sha256:3191317d967af701f1b73a31ed5788795936e423b7acce82a2b63e26eb3e89d6 \ + --hash=sha256:3687d002f911e8a5faf977e619a034d159a8373514a587249cc00f211c67a091 \ + --hash=sha256:4ad25dd9c5faac95c8e9efb13e15803cd8bbf7f4600645a60ffe17c73f60779b \ + --hash=sha256:50f36d77f52d4c9c2f1361ccbfbd09099a1b2ea5d2b2222c586ab08885cf3445 \ + --hash=sha256:605d589ec35d1da9213a9d4d7e7a9c761d90bba78fc8790d1c5e65026c1b9eaf \ + --hash=sha256:696f18463b47a94575db635ebb4c178188645636f05e934fdf361b74edf1bb2d \ + --hash=sha256:a09b43e02f76ac0145f86a08e045e2ea452066f7ba064fd6b0cdccb486f7c3e7 \ + --hash=sha256:ac9dc814e510436e30d0ba535f435a7f3dc97f895f844f5b3f347ec8c228a523 \ + --hash=sha256:af9bdf6c389b5add40d89b201425b531e0a5cceb3cfdcc69f04d3d531c6be74f \ + --hash=sha256:cab904683bf9e2ecbbe9ff235bfe056f0eba754d0168ad5407832928d579e7ab \ + --hash=sha256:cc5516bdb4858d972fbc31d246bdb390eab8df1a26e2353be2dbc0c2d7f5421a \ + --hash=sha256:cfd7de17cef6ab559e9f5ab859f0d3296393bc78f69030967ca4d87a541b97a0 \ + --hash=sha256:d0b856cb19c60cd40198be5d8d4b556228e3dcd545b4f423d1ad812bfdca5884 \ + --hash=sha256:d40a8533ed545390ef8315b8e25c4bb85739b90bd0f3fe1280a29ae364cc55d8 \ + --hash=sha256:f70737c157d7edf749bcb952d13854e8f745cec695a01bdc6e29c29c288fc36e \ + --hash=sha256:fe26fc46fa8c6e0ae3f47ddccfbb136253c831c3289bba044befe68f467bfb16 + # via -r third_party/pip/requirements.in +six==1.17.0 \ + --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ + --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 + # via + # html5lib + # parse-type +sortedcontainers==2.4.0 \ + --hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \ + --hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0 + # via cyclonedx-python-lib +termcolor==2.0.0 \ + --hash=sha256:4889f2243b1da3934fc6cf4b57ee50a0ce98065ec06bfddac3fa6db24a9fcde2 \ + --hash=sha256:6e1a4b8e9c064ad8f9dfe2f9b35a7871d6ea5b6bb4f9da55bc0fd494b2302204 + # via -r third_party/pip/requirements.in +toml==0.10.2 \ + --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ + --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f + # via pip-audit +tomli==2.2.1 \ + --hash=sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6 \ + --hash=sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd \ + --hash=sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c \ + --hash=sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b \ + --hash=sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8 \ + --hash=sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6 \ + --hash=sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77 \ + --hash=sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff \ + --hash=sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea \ + --hash=sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192 \ + --hash=sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249 \ + --hash=sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee \ + --hash=sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4 \ + --hash=sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98 \ + --hash=sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8 \ + --hash=sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4 \ + --hash=sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281 \ + --hash=sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744 \ + --hash=sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69 \ + --hash=sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13 \ + --hash=sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140 \ + --hash=sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e \ + --hash=sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e \ + --hash=sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc \ + --hash=sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff \ + --hash=sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec \ + --hash=sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2 \ + --hash=sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222 \ + --hash=sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106 \ + --hash=sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272 \ + --hash=sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a \ + --hash=sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7 + # via + # black + # mypy + # pylint + # pytest +tomlkit==0.13.3 \ + --hash=sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1 \ + --hash=sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0 + # via pylint +types-certifi==2021.10.8.3 \ + --hash=sha256:72cf7798d165bc0b76e1c10dd1ea3097c7063c42c21d664523b928e88b554a4f \ + --hash=sha256:b2d1e325e69f71f7c78e5943d410e650b4707bb0ef32e4ddf3da37f54176e88a + # via -r third_party/pip/requirements.in +types-chardet==5.0.4.6 \ + --hash=sha256:caf4c74cd13ccfd8b3313c314aba943b159de562a2573ed03137402b2bb37818 \ + --hash=sha256:ea832d87e798abf1e4dfc73767807c2b7fee35d0003ae90348aea4ae00fb004d + # via -r third_party/pip/requirements.in +types-html5lib==1.1.11.20240806 \ + --hash=sha256:575c4fd84ba8eeeaa8520c7e4c7042b7791f5ec3e9c0a5d5c418124c42d9e7e4 \ + --hash=sha256:8060dc98baf63d6796a765bbbc809fff9f7a383f6e3a9add526f814c086545ef + # via -r third_party/pip/requirements.in +types-jsonschema==4.23.0.20240712 \ + --hash=sha256:8c33177ce95336241c1d61ccb56a9964d4361b99d5f1cd81a1ab4909b0dd7cf4 \ + --hash=sha256:b20db728dcf7ea3e80e9bdeb55e8b8420c6c040cda14e8cf284465adee71d217 + # via -r third_party/pip/requirements.in +types-python-dateutil==2.9.0.20240821 \ + --hash=sha256:9649d1dcb6fef1046fb18bebe9ea2aa0028b160918518c34589a46045f6ebd98 \ + --hash=sha256:f5889fcb4e63ed4aaa379b44f93c32593d50b9a94c9a60a0c854d8cc3511cd57 + # via -r third_party/pip/requirements.in +types-pyyaml==6.0.12.20240724 \ + --hash=sha256:cf7b31ae67e0c5b2919c703d2affc415485099d3fe6666a6912f040fd05cb67f \ + --hash=sha256:e5becec598f3aa3a2ddf671de4a75fa1c6856fbf73b2840286c9d50fae2d5d48 + # via -r third_party/pip/requirements.in +types-requests==2.32.0.20240712 \ + --hash=sha256:90c079ff05e549f6bf50e02e910210b98b8ff1ebdd18e19c873cd237737c1358 \ + --hash=sha256:f754283e152c752e46e70942fa2a146b5bc70393522257bb85bd1ef7e019dcc3 + # via -r third_party/pip/requirements.in +types-setuptools==72.2.0.20240821 \ + --hash=sha256:260e89d6d3b42cc35f9f0f382d030713b7b547344a664c05c9175e6ba124fac7 \ + --hash=sha256:e349b8015608879939f27ee370672f801287c46f5caa2d188d416336172c4965 + # via -r third_party/pip/requirements.in +types-six==1.16.21.20240513 \ + --hash=sha256:af2a105be6d504339bfed81319cc8e8697865f0ee5c6baa63658f127b33b9e63 \ + --hash=sha256:cdf445b5161bf17753500713a475ab79a45bd0d87728b8bfcecd86e2fbf66402 + # via -r third_party/pip/requirements.in +types-typed-ast==1.5.8.7 \ + --hash=sha256:97bdd9b4228f96c6904a76e10a050305ddadb529bd35e4d8234711e09c41b543 \ + --hash=sha256:f7795f6f9d597b35212314040b993f6613b51d81738edce3c1e3a3e9ef655124 + # via -r third_party/pip/requirements.in +types-urllib3==1.26.25.14 \ + --hash=sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f \ + --hash=sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e + # via -r third_party/pip/requirements.in +typing-extensions==4.14.1 \ + --hash=sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36 \ + --hash=sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76 + # via + # astroid + # black + # exceptiongroup + # mypy + # pytest-bdd + # referencing +urllib3==2.5.0 \ + --hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \ + --hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc + # via + # requests + # types-requests +webencodings==0.5.1 \ + --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ + --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 + # via html5lib + +# The following packages are considered to be unsafe in a requirements file: +pip==25.2 \ + --hash=sha256:578283f006390f85bb6282dffb876454593d637f5d1be494b5202ce4877e71f2 \ + --hash=sha256:6d67a2b4e7f14d8b31b8b52648866fa717f45a1eb70e83002f4331d07e953717 + # via pip-api diff --git a/third_party/pip/requirements_lock_3_11.txt b/third_party/pip/requirements_lock_3_11.txt new file mode 100644 index 0000000..fe64180 --- /dev/null +++ b/third_party/pip/requirements_lock_3_11.txt @@ -0,0 +1,788 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# bazel run //third_party/pip:requirements_3_11.update +# +astroid==3.1.0 \ + --hash=sha256:951798f922990137ac090c53af473db7ab4e70c770e6d7fae0cec59f74411819 \ + --hash=sha256:ac248253bfa4bd924a0de213707e7ebeeb3138abeb48d798784ead1e56d419d4 + # via pylint +attrs==25.3.0 \ + --hash=sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3 \ + --hash=sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b + # via referencing +black==24.4.2 \ + --hash=sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474 \ + --hash=sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1 \ + --hash=sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0 \ + --hash=sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8 \ + --hash=sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96 \ + --hash=sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1 \ + --hash=sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04 \ + --hash=sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021 \ + --hash=sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94 \ + --hash=sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d \ + --hash=sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c \ + --hash=sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7 \ + --hash=sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c \ + --hash=sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc \ + --hash=sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7 \ + --hash=sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d \ + --hash=sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c \ + --hash=sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741 \ + --hash=sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce \ + --hash=sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb \ + --hash=sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063 \ + --hash=sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e + # via -r third_party/pip/requirements.in +boolean-py==5.0 \ + --hash=sha256:60cbc4bad079753721d32649545505362c754e121570ada4658b852a3a318d95 \ + --hash=sha256:ef28a70bd43115208441b53a045d1549e2f0ec6e3d08a9d142cbc41c1938e8d9 + # via license-expression +cachecontrol[filecache]==0.14.3 \ + --hash=sha256:73e7efec4b06b20d9267b441c1f733664f989fb8688391b670ca812d70795d11 \ + --hash=sha256:b35e44a3113f17d2a31c1e6b27b9de6d4405f84ae51baa8c1d3cc5b633010cae + # via + # cachecontrol + # pip-audit +certifi==2025.8.3 \ + --hash=sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407 \ + --hash=sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5 + # via requests +charset-normalizer==3.4.3 \ + --hash=sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91 \ + --hash=sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0 \ + --hash=sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154 \ + --hash=sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601 \ + --hash=sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884 \ + --hash=sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07 \ + --hash=sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c \ + --hash=sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64 \ + --hash=sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe \ + --hash=sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f \ + --hash=sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432 \ + --hash=sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc \ + --hash=sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa \ + --hash=sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9 \ + --hash=sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae \ + --hash=sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19 \ + --hash=sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d \ + --hash=sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e \ + --hash=sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4 \ + --hash=sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7 \ + --hash=sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312 \ + --hash=sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92 \ + --hash=sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31 \ + --hash=sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c \ + --hash=sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f \ + --hash=sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99 \ + --hash=sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b \ + --hash=sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15 \ + --hash=sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392 \ + --hash=sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f \ + --hash=sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8 \ + --hash=sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491 \ + --hash=sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0 \ + --hash=sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc \ + --hash=sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0 \ + --hash=sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f \ + --hash=sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a \ + --hash=sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40 \ + --hash=sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927 \ + --hash=sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849 \ + --hash=sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce \ + --hash=sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14 \ + --hash=sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05 \ + --hash=sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c \ + --hash=sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c \ + --hash=sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a \ + --hash=sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc \ + --hash=sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34 \ + --hash=sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9 \ + --hash=sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096 \ + --hash=sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14 \ + --hash=sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30 \ + --hash=sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b \ + --hash=sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b \ + --hash=sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942 \ + --hash=sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db \ + --hash=sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5 \ + --hash=sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b \ + --hash=sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce \ + --hash=sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669 \ + --hash=sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0 \ + --hash=sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018 \ + --hash=sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93 \ + --hash=sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe \ + --hash=sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049 \ + --hash=sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a \ + --hash=sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef \ + --hash=sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2 \ + --hash=sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca \ + --hash=sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16 \ + --hash=sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f \ + --hash=sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb \ + --hash=sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1 \ + --hash=sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557 \ + --hash=sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37 \ + --hash=sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7 \ + --hash=sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72 \ + --hash=sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c \ + --hash=sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9 + # via requests +click==8.2.1 \ + --hash=sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202 \ + --hash=sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b + # via black +colorama==0.4.6 \ + --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ + --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 + # via -r third_party/pip/requirements.in +coverage==7.5.1 \ + --hash=sha256:0646599e9b139988b63704d704af8e8df7fa4cbc4a1f33df69d97f36cb0a38de \ + --hash=sha256:0cdcbc320b14c3e5877ee79e649677cb7d89ef588852e9583e6b24c2e5072661 \ + --hash=sha256:0d0a0f5e06881ecedfe6f3dd2f56dcb057b6dbeb3327fd32d4b12854df36bf26 \ + --hash=sha256:1434e088b41594baa71188a17533083eabf5609e8e72f16ce8c186001e6b8c41 \ + --hash=sha256:16db7f26000a07efcf6aea00316f6ac57e7d9a96501e990a36f40c965ec7a95d \ + --hash=sha256:1cc0fe9b0b3a8364093c53b0b4c0c2dd4bb23acbec4c9240b5f284095ccf7981 \ + --hash=sha256:1fc81d5878cd6274ce971e0a3a18a8803c3fe25457165314271cf78e3aae3aa2 \ + --hash=sha256:2ec92012fefebee89a6b9c79bc39051a6cb3891d562b9270ab10ecfdadbc0c34 \ + --hash=sha256:39afcd3d4339329c5f58de48a52f6e4e50f6578dd6099961cf22228feb25f38f \ + --hash=sha256:4a7b0ceee8147444347da6a66be737c9d78f3353b0681715b668b72e79203e4a \ + --hash=sha256:4a9ca3f2fae0088c3c71d743d85404cec8df9be818a005ea065495bedc33da35 \ + --hash=sha256:4bf0655ab60d754491004a5efd7f9cccefcc1081a74c9ef2da4735d6ee4a6223 \ + --hash=sha256:4cc37def103a2725bc672f84bd939a6fe4522310503207aae4d56351644682f1 \ + --hash=sha256:4fc84a37bfd98db31beae3c2748811a3fa72bf2007ff7902f68746d9757f3746 \ + --hash=sha256:5037f8fcc2a95b1f0e80585bd9d1ec31068a9bcb157d9750a172836e98bc7a90 \ + --hash=sha256:54de9ef3a9da981f7af93eafde4ede199e0846cd819eb27c88e2b712aae9708c \ + --hash=sha256:556cf1a7cbc8028cb60e1ff0be806be2eded2daf8129b8811c63e2b9a6c43bca \ + --hash=sha256:57e0204b5b745594e5bc14b9b50006da722827f0b8c776949f1135677e88d0b8 \ + --hash=sha256:5a5740d1fb60ddf268a3811bcd353de34eb56dc24e8f52a7f05ee513b2d4f596 \ + --hash=sha256:5c3721c2c9e4c4953a41a26c14f4cef64330392a6d2d675c8b1db3b645e31f0e \ + --hash=sha256:5fa567e99765fe98f4e7d7394ce623e794d7cabb170f2ca2ac5a4174437e90dd \ + --hash=sha256:5fd215c0c7d7aab005221608a3c2b46f58c0285a819565887ee0b718c052aa4e \ + --hash=sha256:6175d1a0559986c6ee3f7fccfc4a90ecd12ba0a383dcc2da30c2b9918d67d8a3 \ + --hash=sha256:61c4bf1ba021817de12b813338c9be9f0ad5b1e781b9b340a6d29fc13e7c1b5e \ + --hash=sha256:6537e7c10cc47c595828b8a8be04c72144725c383c4702703ff4e42e44577312 \ + --hash=sha256:68f962d9b72ce69ea8621f57551b2fa9c70509af757ee3b8105d4f51b92b41a7 \ + --hash=sha256:7352b9161b33fd0b643ccd1f21f3a3908daaddf414f1c6cb9d3a2fd618bf2572 \ + --hash=sha256:796a79f63eca8814ca3317a1ea443645c9ff0d18b188de470ed7ccd45ae79428 \ + --hash=sha256:79afb6197e2f7f60c4824dd4b2d4c2ec5801ceb6ba9ce5d2c3080e5660d51a4f \ + --hash=sha256:7a588d39e0925f6a2bff87154752481273cdb1736270642aeb3635cb9b4cad07 \ + --hash=sha256:8748731ad392d736cc9ccac03c9845b13bb07d020a33423fa5b3a36521ac6e4e \ + --hash=sha256:8fe7502616b67b234482c3ce276ff26f39ffe88adca2acf0261df4b8454668b4 \ + --hash=sha256:9314d5678dcc665330df5b69c1e726a0e49b27df0461c08ca12674bcc19ef136 \ + --hash=sha256:9735317685ba6ec7e3754798c8871c2f49aa5e687cc794a0b1d284b2389d1bd5 \ + --hash=sha256:9981706d300c18d8b220995ad22627647be11a4276721c10911e0e9fa44c83e8 \ + --hash=sha256:9e78295f4144f9dacfed4f92935fbe1780021247c2fabf73a819b17f0ccfff8d \ + --hash=sha256:b016ea6b959d3b9556cb401c55a37547135a587db0115635a443b2ce8f1c7228 \ + --hash=sha256:b6cf3764c030e5338e7f61f95bd21147963cf6aa16e09d2f74f1fa52013c1206 \ + --hash=sha256:beccf7b8a10b09c4ae543582c1319c6df47d78fd732f854ac68d518ee1fb97fa \ + --hash=sha256:c0884920835a033b78d1c73b6d3bbcda8161a900f38a488829a83982925f6c2e \ + --hash=sha256:c3e757949f268364b96ca894b4c342b41dc6f8f8b66c37878aacef5930db61be \ + --hash=sha256:ca498687ca46a62ae590253fba634a1fe9836bc56f626852fb2720f334c9e4e5 \ + --hash=sha256:d1d0d98d95dd18fe29dc66808e1accf59f037d5716f86a501fc0256455219668 \ + --hash=sha256:d21918e9ef11edf36764b93101e2ae8cc82aa5efdc7c5a4e9c6c35a48496d601 \ + --hash=sha256:d7fed867ee50edf1a0b4a11e8e5d0895150e572af1cd6d315d557758bfa9c057 \ + --hash=sha256:db66fc317a046556a96b453a58eced5024af4582a8dbdc0c23ca4dbc0d5b3146 \ + --hash=sha256:dde0070c40ea8bb3641e811c1cfbf18e265d024deff6de52c5950677a8fb1e0f \ + --hash=sha256:df4e745a81c110e7446b1cc8131bf986157770fa405fe90e15e850aaf7619bc8 \ + --hash=sha256:e2213def81a50519d7cc56ed643c9e93e0247f5bbe0d1247d15fa520814a7cd7 \ + --hash=sha256:ef48e2707fb320c8f139424a596f5b69955a85b178f15af261bab871873bb987 \ + --hash=sha256:f152cbf5b88aaeb836127d920dd0f5e7edff5a66f10c079157306c4343d86c19 \ + --hash=sha256:fc0b4d8bfeabd25ea75e94632f5b6e047eef8adaed0c2161ada1e922e7f7cece + # via -r third_party/pip/requirements.in +cyclonedx-python-lib==6.4.4 \ + --hash=sha256:1b6f9109b6b9e91636dff822c2de90a05c0c8af120317713c1b879dbfdebdff8 \ + --hash=sha256:c366619cc4effd528675f1f7a7a00be30b6695ff03f49c64880ad15acbebc341 + # via pip-audit +defusedxml==0.7.1 \ + --hash=sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69 \ + --hash=sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61 + # via py-serializable +dill==0.4.0 \ + --hash=sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0 \ + --hash=sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049 + # via pylint +filelock==3.18.0 \ + --hash=sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2 \ + --hash=sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de + # via cachecontrol +html5lib==1.1 \ + --hash=sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d \ + --hash=sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f + # via pip-audit +idna==3.10 \ + --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ + --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 + # via requests +iniconfig==2.1.0 \ + --hash=sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7 \ + --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 + # via pytest +isort==5.13.2 \ + --hash=sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109 \ + --hash=sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6 + # via + # -r third_party/pip/requirements.in + # pylint +license-expression==30.4.4 \ + --hash=sha256:421788fdcadb41f049d2dc934ce666626265aeccefddd25e162a26f23bcbf8a4 \ + --hash=sha256:73448f0aacd8d0808895bdc4b2c8e01a8d67646e4188f887375398c761f340fd + # via cyclonedx-python-lib +mako==1.3.10 \ + --hash=sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28 \ + --hash=sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59 + # via pytest-bdd +markdown-it-py==4.0.0 \ + --hash=sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147 \ + --hash=sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3 + # via rich +markupsafe==3.0.2 \ + --hash=sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4 \ + --hash=sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30 \ + --hash=sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0 \ + --hash=sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9 \ + --hash=sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396 \ + --hash=sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13 \ + --hash=sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028 \ + --hash=sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca \ + --hash=sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557 \ + --hash=sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832 \ + --hash=sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0 \ + --hash=sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b \ + --hash=sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579 \ + --hash=sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a \ + --hash=sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c \ + --hash=sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff \ + --hash=sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c \ + --hash=sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22 \ + --hash=sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094 \ + --hash=sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb \ + --hash=sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e \ + --hash=sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5 \ + --hash=sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a \ + --hash=sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d \ + --hash=sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a \ + --hash=sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b \ + --hash=sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8 \ + --hash=sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225 \ + --hash=sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c \ + --hash=sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144 \ + --hash=sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f \ + --hash=sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87 \ + --hash=sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d \ + --hash=sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93 \ + --hash=sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf \ + --hash=sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158 \ + --hash=sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84 \ + --hash=sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb \ + --hash=sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48 \ + --hash=sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171 \ + --hash=sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c \ + --hash=sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6 \ + --hash=sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd \ + --hash=sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d \ + --hash=sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1 \ + --hash=sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d \ + --hash=sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca \ + --hash=sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a \ + --hash=sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29 \ + --hash=sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe \ + --hash=sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798 \ + --hash=sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c \ + --hash=sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8 \ + --hash=sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f \ + --hash=sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f \ + --hash=sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a \ + --hash=sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178 \ + --hash=sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0 \ + --hash=sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79 \ + --hash=sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430 \ + --hash=sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50 + # via mako +mccabe==0.7.0 \ + --hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \ + --hash=sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e + # via pylint +mdurl==0.1.2 \ + --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ + --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba + # via markdown-it-py +msgpack==1.1.1 \ + --hash=sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8 \ + --hash=sha256:1abfc6e949b352dadf4bce0eb78023212ec5ac42f6abfd469ce91d783c149c2a \ + --hash=sha256:1b13fe0fb4aac1aa5320cd693b297fe6fdef0e7bea5518cbc2dd5299f873ae90 \ + --hash=sha256:1d75f3807a9900a7d575d8d6674a3a47e9f227e8716256f35bc6f03fc597ffbf \ + --hash=sha256:2fbbc0b906a24038c9958a1ba7ae0918ad35b06cb449d398b76a7d08470b0ed9 \ + --hash=sha256:33be9ab121df9b6b461ff91baac6f2731f83d9b27ed948c5b9d1978ae28bf157 \ + --hash=sha256:353b6fc0c36fde68b661a12949d7d49f8f51ff5fa019c1e47c87c4ff34b080ed \ + --hash=sha256:36043272c6aede309d29d56851f8841ba907a1a3d04435e43e8a19928e243c1d \ + --hash=sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0 \ + --hash=sha256:3a89cd8c087ea67e64844287ea52888239cbd2940884eafd2dcd25754fb72232 \ + --hash=sha256:40eae974c873b2992fd36424a5d9407f93e97656d999f43fca9d29f820899084 \ + --hash=sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5 \ + --hash=sha256:435807eeb1bc791ceb3247d13c79868deb22184e1fc4224808750f0d7d1affc1 \ + --hash=sha256:4835d17af722609a45e16037bb1d4d78b7bdf19d6c0128116d178956618c4e88 \ + --hash=sha256:4a28e8072ae9779f20427af07f53bbb8b4aa81151054e882aee333b158da8752 \ + --hash=sha256:4d3237b224b930d58e9d83c81c0dba7aacc20fcc2f89c1e5423aa0529a4cd142 \ + --hash=sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac \ + --hash=sha256:4fd6b577e4541676e0cc9ddc1709d25014d3ad9a66caa19962c4f5de30fc09ef \ + --hash=sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323 \ + --hash=sha256:5692095123007180dca3e788bb4c399cc26626da51629a31d40207cb262e67f4 \ + --hash=sha256:5fd1b58e1431008a57247d6e7cc4faa41c3607e8e7d4aaf81f7c29ea013cb458 \ + --hash=sha256:61abccf9de335d9efd149e2fff97ed5974f2481b3353772e8e2dd3402ba2bd57 \ + --hash=sha256:61e35a55a546a1690d9d09effaa436c25ae6130573b6ee9829c37ef0f18d5e78 \ + --hash=sha256:6640fd979ca9a212e4bcdf6eb74051ade2c690b862b679bfcb60ae46e6dc4bfd \ + --hash=sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69 \ + --hash=sha256:6f64ae8fe7ffba251fecb8408540c34ee9df1c26674c50c4544d72dbf792e5ce \ + --hash=sha256:71ef05c1726884e44f8b1d1773604ab5d4d17729d8491403a705e649116c9558 \ + --hash=sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd \ + --hash=sha256:78426096939c2c7482bf31ef15ca219a9e24460289c00dd0b94411040bb73ad2 \ + --hash=sha256:79c408fcf76a958491b4e3b103d1c417044544b68e96d06432a189b43d1215c8 \ + --hash=sha256:7a17ac1ea6ec3c7687d70201cfda3b1e8061466f28f686c24f627cae4ea8efd0 \ + --hash=sha256:7da8831f9a0fdb526621ba09a281fadc58ea12701bc709e7b8cbc362feabc295 \ + --hash=sha256:870b9a626280c86cff9c576ec0d9cbcc54a1e5ebda9cd26dab12baf41fee218c \ + --hash=sha256:88d1e966c9235c1d4e2afac21ca83933ba59537e2e2727a999bf3f515ca2af26 \ + --hash=sha256:88daaf7d146e48ec71212ce21109b66e06a98e5e44dca47d853cbfe171d6c8d2 \ + --hash=sha256:8a8b10fdb84a43e50d38057b06901ec9da52baac6983d3f709d8507f3889d43f \ + --hash=sha256:8b17ba27727a36cb73aabacaa44b13090feb88a01d012c0f4be70c00f75048b4 \ + --hash=sha256:8b65b53204fe1bd037c40c4148d00ef918eb2108d24c9aaa20bc31f9810ce0a8 \ + --hash=sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9 \ + --hash=sha256:96decdfc4adcbc087f5ea7ebdcfd3dee9a13358cae6e81d54be962efc38f6338 \ + --hash=sha256:996f2609ddf0142daba4cefd767d6db26958aac8439ee41db9cc0db9f4c4c3a6 \ + --hash=sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a \ + --hash=sha256:a32747b1b39c3ac27d0670122b57e6e57f28eefb725e0b625618d1b59bf9d1e0 \ + --hash=sha256:a494554874691720ba5891c9b0b39474ba43ffb1aaf32a5dac874effb1619e1a \ + --hash=sha256:a8ef6e342c137888ebbfb233e02b8fbd689bb5b5fcc59b34711ac47ebd504478 \ + --hash=sha256:ae497b11f4c21558d95de9f64fff7053544f4d1a17731c866143ed6bb4591238 \ + --hash=sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7 \ + --hash=sha256:b8f93dcddb243159c9e4109c9750ba5b335ab8d48d9522c5308cd05d7e3ce600 \ + --hash=sha256:ba0c325c3f485dc54ec298d8b024e134acf07c10d494ffa24373bea729acf704 \ + --hash=sha256:bb29aaa613c0a1c40d1af111abf025f1732cab333f96f285d6a93b934738a68a \ + --hash=sha256:bba1be28247e68994355e028dcd668316db30c1f758d3241a7b903ac78dcd285 \ + --hash=sha256:cb643284ab0ed26f6957d969fe0dd8bb17beb567beb8998140b5e38a90974f6c \ + --hash=sha256:d182dac0221eb8faef2e6f44701812b467c02674a322c739355c39e94730cdbf \ + --hash=sha256:d275a9e3c81b1093c060c3837e580c37f47c51eca031f7b5fb76f7b8470f5f9b \ + --hash=sha256:d8b55ea20dc59b181d3f47103f113e6f28a5e1c89fd5b67b9140edb442ab67f2 \ + --hash=sha256:da8f41e602574ece93dbbda1fab24650d6bf2a24089f9e9dbb4f5730ec1e58ad \ + --hash=sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b \ + --hash=sha256:f5be6b6bc52fad84d010cb45433720327ce886009d862f46b26d4d154001994b \ + --hash=sha256:f6d58656842e1b2ddbe07f43f56b10a60f2ba5826164910968f5933e5178af75 + # via cachecontrol +mypy==1.11.1 \ + --hash=sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54 \ + --hash=sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a \ + --hash=sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72 \ + --hash=sha256:11965c2f571ded6239977b14deebd3f4c3abd9a92398712d6da3a772974fad69 \ + --hash=sha256:1a81cf05975fd61aec5ae16501a091cfb9f605dc3e3c878c0da32f250b74760b \ + --hash=sha256:2684d3f693073ab89d76da8e3921883019ea8a3ec20fa5d8ecca6a2db4c54bbe \ + --hash=sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4 \ + --hash=sha256:45df906e8b6804ef4b666af29a87ad9f5921aad091c79cc38e12198e220beabd \ + --hash=sha256:4c956b49c5d865394d62941b109728c5c596a415e9c5b2be663dd26a1ff07bc0 \ + --hash=sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525 \ + --hash=sha256:749fd3213916f1751fff995fccf20c6195cae941dc968f3aaadf9bb4e430e5a2 \ + --hash=sha256:79c07eb282cb457473add5052b63925e5cc97dfab9812ee65a7c7ab5e3cb551c \ + --hash=sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5 \ + --hash=sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de \ + --hash=sha256:a2b43895a0f8154df6519706d9bca8280cda52d3d9d1514b2d9c3e26792a0b74 \ + --hash=sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c \ + --hash=sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e \ + --hash=sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58 \ + --hash=sha256:b639dce63a0b19085213ec5fdd8cffd1d81988f47a2dec7100e93564f3e8fb3b \ + --hash=sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417 \ + --hash=sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411 \ + --hash=sha256:d44be7551689d9d47b7abc27c71257adfdb53f03880841a5db15ddb22dc63edb \ + --hash=sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03 \ + --hash=sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca \ + --hash=sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8 \ + --hash=sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08 \ + --hash=sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809 + # via -r third_party/pip/requirements.in +mypy-extensions==1.1.0 \ + --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \ + --hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558 + # via + # black + # mypy +packageurl-python==0.17.5 \ + --hash=sha256:a7be3f3ba70d705f738ace9bf6124f31920245a49fa69d4b416da7037dd2de61 \ + --hash=sha256:f0e55452ab37b5c192c443de1458e3f3b4d8ac27f747df6e8c48adeab081d321 + # via cyclonedx-python-lib +packaging==25.0 \ + --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ + --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f + # via + # black + # pip-audit + # pip-requirements-parser + # pytest + # pytest-bdd + # pytest-rerunfailures +parse==1.20.2 \ + --hash=sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558 \ + --hash=sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce + # via + # parse-type + # pytest-bdd +parse-type==0.6.6 \ + --hash=sha256:3ca79bbe71e170dfccc8ec6c341edfd1c2a0fc1e5cfd18330f93af938de2348c \ + --hash=sha256:513a3784104839770d690e04339a8b4d33439fcd5dd99f2e4580f9fc1097bfb2 + # via pytest-bdd +pathspec==0.12.1 \ + --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ + --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712 + # via black +pip-api==0.0.34 \ + --hash=sha256:8b2d7d7c37f2447373aa2cf8b1f60a2f2b27a84e1e9e0294a3f6ef10eb3ba6bb \ + --hash=sha256:9b75e958f14c5a2614bae415f2adf7eeb54d50a2cfbe7e24fd4826471bac3625 + # via pip-audit +pip-audit==2.7.0 \ + --hash=sha256:67740c5b1d5d967a258c3dfefc46f9713a2819c48062505ddf4b29de101c2b75 \ + --hash=sha256:83e039740653eb9ef1a78b1540ed441600cd88a560588ba2c0a169180685a522 + # via -r third_party/pip/requirements.in +pip-requirements-parser==32.0.1 \ + --hash=sha256:4659bc2a667783e7a15d190f6fccf8b2486685b6dba4c19c3876314769c57526 \ + --hash=sha256:b4fa3a7a0be38243123cf9d1f3518da10c51bdb165a2b2985566247f9155a7d3 + # via pip-audit +platformdirs==4.3.8 \ + --hash=sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc \ + --hash=sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4 + # via + # black + # pylint +pluggy==1.6.0 \ + --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ + --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + # via pytest +py-cpuinfo==9.0.0 \ + --hash=sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690 \ + --hash=sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5 + # via pytest-benchmark +py-serializable==1.1.2 \ + --hash=sha256:801be61b0a1ba64c3861f7c624f1de5cfbbabf8b458acc9cdda91e8f7e5effa1 \ + --hash=sha256:89af30bc319047d4aa0d8708af412f6ce73835e18bacf1a080028bb9e2f42bdb + # via cyclonedx-python-lib +pygments==2.19.2 \ + --hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \ + --hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b + # via rich +pylint==3.1.0 \ + --hash=sha256:507a5b60953874766d8a366e8e8c7af63e058b26345cfcb5f91f89d987fd6b74 \ + --hash=sha256:6a69beb4a6f63debebaab0a3477ecd0f559aa726af4954fc948c51f7a2549e23 + # via -r third_party/pip/requirements.in +pyparsing==3.2.3 \ + --hash=sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf \ + --hash=sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be + # via pip-requirements-parser +pytest==8.1.1 \ + --hash=sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7 \ + --hash=sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044 + # via + # -r third_party/pip/requirements.in + # pytest-bdd + # pytest-benchmark + # pytest-mock + # pytest-rerunfailures + # pytest-timeout +pytest-bdd==7.1.1 \ + --hash=sha256:1b41a10a8391f1849f0a1524d77b2991d0d8042d2253ace9b616b89b4a9b97a7 \ + --hash=sha256:331621122a46f9881110ae0dd43fedb29e96e8202de29256be3d76c53b57c1d2 + # via -r third_party/pip/requirements.in +pytest-benchmark==4.0.0 \ + --hash=sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1 \ + --hash=sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6 + # via -r third_party/pip/requirements.in +pytest-mock==3.12.0 \ + --hash=sha256:0972719a7263072da3a21c7f4773069bcc7486027d7e8e1f81d98a47e701bc4f \ + --hash=sha256:31a40f038c22cad32287bb43932054451ff5583ff094bca6f675df2f8bc1a6e9 + # via -r third_party/pip/requirements.in +pytest-rerunfailures==14.0 \ + --hash=sha256:4197bdd2eaeffdbf50b5ea6e7236f47ff0e44d1def8dae08e409f536d84e7b32 \ + --hash=sha256:4a400bcbcd3c7a4ad151ab8afac123d90eca3abe27f98725dc4d9702887d2e92 + # via -r third_party/pip/requirements.in +pytest-timeout==2.3.1 \ + --hash=sha256:12397729125c6ecbdaca01035b9e5239d4db97352320af155b3f5de1ba5165d9 \ + --hash=sha256:68188cb703edfc6a18fad98dc25a3c61e9f24d644b0b70f33af545219fc7813e + # via -r third_party/pip/requirements.in +referencing==0.36.2 \ + --hash=sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa \ + --hash=sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0 + # via types-jsonschema +requests==2.32.4 \ + --hash=sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c \ + --hash=sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422 + # via + # cachecontrol + # pip-audit +rich==14.1.0 \ + --hash=sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f \ + --hash=sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8 + # via pip-audit +rpds-py==0.27.0 \ + --hash=sha256:010c4843a3b92b54373e3d2291a7447d6c3fc29f591772cc2ea0e9f5c1da434b \ + --hash=sha256:05284439ebe7d9f5f5a668d4d8a0a1d851d16f7d47c78e1fab968c8ad30cab04 \ + --hash=sha256:0665be515767dc727ffa5f74bd2ef60b0ff85dad6bb8f50d91eaa6b5fb226f51 \ + --hash=sha256:069e0384a54f427bd65d7fda83b68a90606a3835901aaff42185fcd94f5a9295 \ + --hash=sha256:08680820d23df1df0a0260f714d12966bc6c42d02e8055a91d61e03f0c47dda0 \ + --hash=sha256:0954e3a92e1d62e83a54ea7b3fdc9efa5d61acef8488a8a3d31fdafbfb00460d \ + --hash=sha256:09965b314091829b378b60607022048953e25f0b396c2b70e7c4c81bcecf932e \ + --hash=sha256:0c431bfb91478d7cbe368d0a699978050d3b112d7f1d440a41e90faa325557fd \ + --hash=sha256:0f401c369186a5743694dd9fc08cba66cf70908757552e1f714bfc5219c655b5 \ + --hash=sha256:0f4f69d7a4300fbf91efb1fb4916421bd57804c01ab938ab50ac9c4aa2212f03 \ + --hash=sha256:11e8e28c0ba0373d052818b600474cfee2fafa6c9f36c8587d217b13ee28ca7d \ + --hash=sha256:130c1ffa5039a333f5926b09e346ab335f0d4ec393b030a18549a7c7e7c2cea4 \ + --hash=sha256:1321bce595ad70e80f97f998db37356b2e22cf98094eba6fe91782e626da2f71 \ + --hash=sha256:13bbc4846ae4c993f07c93feb21a24d8ec637573d567a924b1001e81c8ae80f9 \ + --hash=sha256:14f028eb47f59e9169bfdf9f7ceafd29dd64902141840633683d0bad5b04ff34 \ + --hash=sha256:15ea4d2e182345dd1b4286593601d766411b43f868924afe297570658c31a62b \ + --hash=sha256:181bc29e59e5e5e6e9d63b143ff4d5191224d355e246b5a48c88ce6b35c4e466 \ + --hash=sha256:183f5e221ba3e283cd36fdfbe311d95cd87699a083330b4f792543987167eff1 \ + --hash=sha256:184f0d7b342967f6cda94a07d0e1fae177d11d0b8f17d73e06e36ac02889f303 \ + --hash=sha256:190d7285cd3bb6d31d37a0534d7359c1ee191eb194c511c301f32a4afa5a1dd4 \ + --hash=sha256:19c990fdf5acecbf0623e906ae2e09ce1c58947197f9bced6bbd7482662231c4 \ + --hash=sha256:1d66f45b9399036e890fb9c04e9f70c33857fd8f58ac8db9f3278cfa835440c3 \ + --hash=sha256:203f581accef67300a942e49a37d74c12ceeef4514874c7cede21b012613ca2c \ + --hash=sha256:20e222a44ae9f507d0f2678ee3dd0c45ec1e930f6875d99b8459631c24058aec \ + --hash=sha256:2406d034635d1497c596c40c85f86ecf2bf9611c1df73d14078af8444fe48031 \ + --hash=sha256:249ab91ceaa6b41abc5f19513cb95b45c6f956f6b89f1fe3d99c81255a849f9e \ + --hash=sha256:25a4aebf8ca02bbb90a9b3e7a463bbf3bee02ab1c446840ca07b1695a68ce424 \ + --hash=sha256:27bac29bbbf39601b2aab474daf99dbc8e7176ca3389237a23944b17f8913d97 \ + --hash=sha256:299a245537e697f28a7511d01038c310ac74e8ea213c0019e1fc65f52c0dcb23 \ + --hash=sha256:2cff9bdd6c7b906cc562a505c04a57d92e82d37200027e8d362518df427f96cd \ + --hash=sha256:2e307cb5f66c59ede95c00e93cd84190a5b7f3533d7953690b2036780622ba81 \ + --hash=sha256:2e39169ac6aae06dd79c07c8a69d9da867cef6a6d7883a0186b46bb46ccfb0c3 \ + --hash=sha256:2fe6e18e5c8581f0361b35ae575043c7029d0a92cb3429e6e596c2cdde251432 \ + --hash=sha256:3001013dae10f806380ba739d40dee11db1ecb91684febb8406a87c2ded23dae \ + --hash=sha256:32196b5a99821476537b3f7732432d64d93a58d680a52c5e12a190ee0135d8b5 \ + --hash=sha256:33ba649a6e55ae3808e4c39e01580dc9a9b0d5b02e77b66bb86ef117922b1264 \ + --hash=sha256:341d8acb6724c0c17bdf714319c393bb27f6d23d39bc74f94221b3e59fc31828 \ + --hash=sha256:343cf24de9ed6c728abefc5d5c851d5de06497caa7ac37e5e65dd572921ed1b5 \ + --hash=sha256:36184b44bf60a480863e51021c26aca3dfe8dd2f5eeabb33622b132b9d8b8b54 \ + --hash=sha256:3841f66c1ffdc6cebce8aed64e36db71466f1dc23c0d9a5592e2a782a3042c79 \ + --hash=sha256:4045e2fc4b37ec4b48e8907a5819bdd3380708c139d7cc358f03a3653abedb89 \ + --hash=sha256:419dd9c98bcc9fb0242be89e0c6e922df333b975d4268faa90d58499fd9c9ebe \ + --hash=sha256:42894616da0fc0dcb2ec08a77896c3f56e9cb2f4b66acd76fc8992c3557ceb1c \ + --hash=sha256:42ccc57ff99166a55a59d8c7d14f1a357b7749f9ed3584df74053fd098243451 \ + --hash=sha256:4300e15e7d03660f04be84a125d1bdd0e6b2f674bc0723bc0fd0122f1a4585dc \ + --hash=sha256:443d239d02d9ae55b74015234f2cd8eb09e59fbba30bf60baeb3123ad4c6d5ff \ + --hash=sha256:44524b96481a4c9b8e6c46d6afe43fa1fb485c261e359fbe32b63ff60e3884d8 \ + --hash=sha256:45d04a73c54b6a5fd2bab91a4b5bc8b426949586e61340e212a8484919183859 \ + --hash=sha256:46f48482c1a4748ab2773f75fffbdd1951eb59794e32788834b945da857c47a8 \ + --hash=sha256:4790c9d5dd565ddb3e9f656092f57268951398cef52e364c405ed3112dc7c7c1 \ + --hash=sha256:4bc262ace5a1a7dc3e2eac2fa97b8257ae795389f688b5adf22c5db1e2431c43 \ + --hash=sha256:4c3f8a0d4802df34fcdbeb3dfe3a4d8c9a530baea8fafdf80816fcaac5379d83 \ + --hash=sha256:5355527adaa713ab693cbce7c1e0ec71682f599f61b128cf19d07e5c13c9b1f1 \ + --hash=sha256:555ed147cbe8c8f76e72a4c6cd3b7b761cbf9987891b9448808148204aed74a5 \ + --hash=sha256:55d42a0ef2bdf6bc81e1cc2d49d12460f63c6ae1423c4f4851b828e454ccf6f1 \ + --hash=sha256:59195dc244fc183209cf8a93406889cadde47dfd2f0a6b137783aa9c56d67c85 \ + --hash=sha256:59714ab0a5af25d723d8e9816638faf7f4254234decb7d212715c1aa71eee7be \ + --hash=sha256:5b3a5c8089eed498a3af23ce87a80805ff98f6ef8f7bdb70bd1b7dae5105f6ac \ + --hash=sha256:5d6790ff400254137b81b8053b34417e2c46921e302d655181d55ea46df58cf7 \ + --hash=sha256:5df559e9e7644d9042f626f2c3997b555f347d7a855a15f170b253f6c5bfe358 \ + --hash=sha256:5fa01b3d5e3b7d97efab65bd3d88f164e289ec323a8c033c5c38e53ee25c007e \ + --hash=sha256:61490d57e82e23b45c66f96184237994bfafa914433b8cd1a9bb57fecfced59d \ + --hash=sha256:6168af0be75bba990a39f9431cdfae5f0ad501f4af32ae62e8856307200517b8 \ + --hash=sha256:64a0fe3f334a40b989812de70160de6b0ec7e3c9e4a04c0bbc48d97c5d3600ae \ + --hash=sha256:64f689ab822f9b5eb6dfc69893b4b9366db1d2420f7db1f6a2adf2a9ca15ad64 \ + --hash=sha256:699c346abc73993962cac7bb4f02f58e438840fa5458a048d3a178a7a670ba86 \ + --hash=sha256:6b96b0b784fe5fd03beffff2b1533dc0d85e92bab8d1b2c24ef3a5dc8fac5669 \ + --hash=sha256:6bde37765564cd22a676dd8101b657839a1854cfaa9c382c5abf6ff7accfd4ae \ + --hash=sha256:6c135708e987f46053e0a1246a206f53717f9fadfba27174a9769ad4befba5c3 \ + --hash=sha256:6c27a7054b5224710fcfb1a626ec3ff4f28bcb89b899148c72873b18210e446b \ + --hash=sha256:6de6a7f622860af0146cb9ee148682ff4d0cea0b8fd3ad51ce4d40efb2f061d0 \ + --hash=sha256:737005088449ddd3b3df5a95476ee1c2c5c669f5c30eed909548a92939c0e12d \ + --hash=sha256:7451ede3560086abe1aa27dcdcf55cd15c96b56f543fb12e5826eee6f721f858 \ + --hash=sha256:7873b65686a6471c0037139aa000d23fe94628e0daaa27b6e40607c90e3f5ec4 \ + --hash=sha256:79af163a4b40bbd8cfd7ca86ec8b54b81121d3b213b4435ea27d6568bcba3e9d \ + --hash=sha256:7aed8118ae20515974650d08eb724150dc2e20c2814bcc307089569995e88a14 \ + --hash=sha256:7cf9bc4508efb18d8dff6934b602324eb9f8c6644749627ce001d6f38a490889 \ + --hash=sha256:7e57906e38583a2cba67046a09c2637e23297618dc1f3caddbc493f2be97c93f \ + --hash=sha256:7ec85994f96a58cf7ed288caa344b7fe31fd1d503bdf13d7331ead5f70ab60d5 \ + --hash=sha256:81f81bbd7cdb4bdc418c09a73809abeda8f263a6bf8f9c7f93ed98b5597af39d \ + --hash=sha256:86aca1616922b40d8ac1b3073a1ead4255a2f13405e5700c01f7c8d29a03972d \ + --hash=sha256:88051c3b7d5325409f433c5a40328fcb0685fc04e5db49ff936e910901d10114 \ + --hash=sha256:887ab1f12b0d227e9260558a4a2320024b20102207ada65c43e1ffc4546df72e \ + --hash=sha256:8a06aa1197ec0281eb1d7daf6073e199eb832fe591ffa329b88bae28f25f5fe5 \ + --hash=sha256:8a1dca5507fa1337f75dcd5070218b20bc68cf8844271c923c1b79dfcbc20391 \ + --hash=sha256:8b23cf252f180cda89220b378d917180f29d313cd6a07b2431c0d3b776aae86f \ + --hash=sha256:8d0e09cf4863c74106b5265c2c310f36146e2b445ff7b3018a56799f28f39f6f \ + --hash=sha256:8de567dec6d451649a781633d36f5c7501711adee329d76c095be2178855b042 \ + --hash=sha256:90fb790138c1a89a2e58c9282fe1089638401f2f3b8dddd758499041bc6e0774 \ + --hash=sha256:92f3b3ec3e6008a1fe00b7c0946a170f161ac00645cde35e3c9a68c2475e8156 \ + --hash=sha256:935afcdea4751b0ac918047a2df3f720212892347767aea28f5b3bf7be4f27c0 \ + --hash=sha256:9a0ff7ee28583ab30a52f371b40f54e7138c52ca67f8ca17ccb7ccf0b383cb5f \ + --hash=sha256:9ad08547995a57e74fea6abaf5940d399447935faebbd2612b3b0ca6f987946b \ + --hash=sha256:9b2a4e17bfd68536c3b801800941c95a1d4a06e3cada11c146093ba939d9638d \ + --hash=sha256:9b78430703cfcf5f5e86eb74027a1ed03a93509273d7c705babb547f03e60016 \ + --hash=sha256:9d0f92b78cfc3b74a42239fdd8c1266f4715b573204c234d2f9fc3fc7a24f185 \ + --hash=sha256:9da162b718b12c4219eeeeb68a5b7552fbc7aadedf2efee440f88b9c0e54b45d \ + --hash=sha256:a00c91104c173c9043bc46f7b30ee5e6d2f6b1149f11f545580f5d6fdff42c0b \ + --hash=sha256:a029be818059870664157194e46ce0e995082ac49926f1423c1f058534d2aaa9 \ + --hash=sha256:a1b3db5fae5cbce2131b7420a3f83553d4d89514c03d67804ced36161fe8b6b2 \ + --hash=sha256:a4cf32a26fa744101b67bfd28c55d992cd19438aff611a46cac7f066afca8fd4 \ + --hash=sha256:aa0bf113d15e8abdfee92aa4db86761b709a09954083afcb5bf0f952d6065fdb \ + --hash=sha256:ab47fe727c13c09d0e6f508e3a49e545008e23bf762a245b020391b621f5b726 \ + --hash=sha256:af22763a0a1eff106426a6e1f13c4582e0d0ad89c1493ab6c058236174cd6c6a \ + --hash=sha256:af9d4fd79ee1cc8e7caf693ee02737daabfc0fcf2773ca0a4735b356c8ad6f7c \ + --hash=sha256:b1fef1f13c842a39a03409e30ca0bf87b39a1e2a305a9924deadb75a43105d23 \ + --hash=sha256:b2eff8ee57c5996b0d2a07c3601fb4ce5fbc37547344a26945dd9e5cbd1ed27a \ + --hash=sha256:b4c4fbbcff474e1e5f38be1bf04511c03d492d42eec0babda5d03af3b5589374 \ + --hash=sha256:b8a4131698b6992b2a56015f51646711ec5d893a0b314a4b985477868e240c87 \ + --hash=sha256:b8a7acf04fda1f30f1007f3cc96d29d8cf0a53e626e4e1655fdf4eabc082d367 \ + --hash=sha256:ba783541be46f27c8faea5a6645e193943c17ea2f0ffe593639d906a327a9bcc \ + --hash=sha256:be0744661afbc4099fef7f4e604e7f1ea1be1dd7284f357924af12a705cc7d5c \ + --hash=sha256:be3964f7312ea05ed283b20f87cb533fdc555b2e428cc7be64612c0b2124f08c \ + --hash=sha256:be806e2961cd390a89d6c3ce8c2ae34271cfcd05660f716257838bb560f1c3b6 \ + --hash=sha256:bec77545d188f8bdd29d42bccb9191682a46fb2e655e3d1fb446d47c55ac3b8d \ + --hash=sha256:c10d92fb6d7fd827e44055fcd932ad93dac6a11e832d51534d77b97d1d85400f \ + --hash=sha256:c3782fb753aa825b4ccabc04292e07897e2fd941448eabf666856c5530277626 \ + --hash=sha256:c9ce7a9e967afc0a2af7caa0d15a3e9c1054815f73d6a8cb9225b61921b419bd \ + --hash=sha256:cb0702c12983be3b2fab98ead349ac63a98216d28dda6f518f52da5498a27a1b \ + --hash=sha256:cbc619e84a5e3ab2d452de831c88bdcad824414e9c2d28cd101f94dbdf26329c \ + --hash=sha256:ce4ed8e0c7dbc5b19352b9c2c6131dd23b95fa8698b5cdd076307a33626b72dc \ + --hash=sha256:ce96ab0bdfcef1b8c371ada2100767ace6804ea35aacce0aef3aeb4f3f499ca8 \ + --hash=sha256:cf824aceaeffff029ccfba0da637d432ca71ab21f13e7f6f5179cd88ebc77a8a \ + --hash=sha256:d2a81bdcfde4245468f7030a75a37d50400ac2455c3a4819d9d550c937f90ab5 \ + --hash=sha256:d2cc2b34f9e1d31ce255174da82902ad75bd7c0d88a33df54a77a22f2ef421ee \ + --hash=sha256:d2f184336bc1d6abfaaa1262ed42739c3789b1e3a65a29916a615307d22ffd2e \ + --hash=sha256:d3c622c39f04d5751408f5b801ecb527e6e0a471b367f420a877f7a660d583f6 \ + --hash=sha256:d7cf5e726b6fa977e428a61880fb108a62f28b6d0c7ef675b117eaff7076df49 \ + --hash=sha256:d85d784c619370d9329bbd670f41ff5f2ae62ea4519761b679d0f57f0f0ee267 \ + --hash=sha256:d93ebdb82363d2e7bec64eecdc3632b59e84bd270d74fe5be1659f7787052f9b \ + --hash=sha256:db8a6313dbac934193fc17fe7610f70cd8181c542a91382531bef5ed785e5615 \ + --hash=sha256:dbc2ab5d10544eb485baa76c63c501303b716a5c405ff2469a1d8ceffaabf622 \ + --hash=sha256:dbd749cff1defbde270ca346b69b3baf5f1297213ef322254bf2a28537f0b046 \ + --hash=sha256:dc662bc9375a6a394b62dfd331874c434819f10ee3902123200dbcf116963f89 \ + --hash=sha256:dc6b0d5a1ea0318ef2def2b6a55dccf1dcaf77d605672347271ed7b829860765 \ + --hash=sha256:dc79d192fb76fc0c84f2c58672c17bbbc383fd26c3cdc29daae16ce3d927e8b2 \ + --hash=sha256:dd2c1d27ebfe6a015cfa2005b7fe8c52d5019f7bbdd801bc6f7499aab9ae739e \ + --hash=sha256:dea0808153f1fbbad772669d906cddd92100277533a03845de6893cadeffc8be \ + --hash=sha256:e0d7151a1bd5d0a203a5008fc4ae51a159a610cb82ab0a9b2c4d80241745582e \ + --hash=sha256:e14aab02258cb776a108107bd15f5b5e4a1bbaa61ef33b36693dfab6f89d54f9 \ + --hash=sha256:e24d8031a2c62f34853756d9208eeafa6b940a1efcbfe36e8f57d99d52bb7261 \ + --hash=sha256:e36c80c49853b3ffda7aa1831bf175c13356b210c73128c861f3aa93c3cc4015 \ + --hash=sha256:e377e4cf8795cdbdff75b8f0223d7b6c68ff4fef36799d88ccf3a995a91c0112 \ + --hash=sha256:e3acb9c16530362aeaef4e84d57db357002dc5cbfac9a23414c3e73c08301ab2 \ + --hash=sha256:e3dc8d4ede2dbae6c0fc2b6c958bf51ce9fd7e9b40c0f5b8835c3fde44f5807d \ + --hash=sha256:e6491658dd2569f05860bad645569145c8626ac231877b0fb2d5f9bcb7054089 \ + --hash=sha256:eb91d252b35004a84670dfeafadb042528b19842a0080d8b53e5ec1128e8f433 \ + --hash=sha256:f0396e894bd1e66c74ecbc08b4f6a03dc331140942c4b1d345dd131b68574a60 \ + --hash=sha256:f09c9d4c26fa79c1bad927efb05aca2391350b8e61c38cbc0d7d3c814e463124 \ + --hash=sha256:f3cd110e02c5bf17d8fb562f6c9df5c20e73029d587cf8602a2da6c5ef1e32cb \ + --hash=sha256:f7a37dd208f0d658e0487522078b1ed68cd6bce20ef4b5a915d2809b9094b410 \ + --hash=sha256:fae4a01ef8c4cb2bbe92ef2063149596907dc4a881a8d26743b3f6b304713171 \ + --hash=sha256:fc327f4497b7087d06204235199daf208fd01c82d80465dc5efa4ec9df1c5b4e \ + --hash=sha256:fcc01c57ce6e70b728af02b2401c5bc853a9e14eb07deda30624374f0aebfe42 \ + --hash=sha256:fde355b02934cc6b07200cc3b27ab0c15870a757d1a72fd401aa92e2ea3c6bfe + # via referencing +ruff==0.5.5 \ + --hash=sha256:00817603822a3e42b80f7c3298c8269e09f889ee94640cd1fc7f9329788d7bf8 \ + --hash=sha256:187a60f555e9f865a2ff2c6984b9afeffa7158ba6e1eab56cb830404c942b0f3 \ + --hash=sha256:3191317d967af701f1b73a31ed5788795936e423b7acce82a2b63e26eb3e89d6 \ + --hash=sha256:3687d002f911e8a5faf977e619a034d159a8373514a587249cc00f211c67a091 \ + --hash=sha256:4ad25dd9c5faac95c8e9efb13e15803cd8bbf7f4600645a60ffe17c73f60779b \ + --hash=sha256:50f36d77f52d4c9c2f1361ccbfbd09099a1b2ea5d2b2222c586ab08885cf3445 \ + --hash=sha256:605d589ec35d1da9213a9d4d7e7a9c761d90bba78fc8790d1c5e65026c1b9eaf \ + --hash=sha256:696f18463b47a94575db635ebb4c178188645636f05e934fdf361b74edf1bb2d \ + --hash=sha256:a09b43e02f76ac0145f86a08e045e2ea452066f7ba064fd6b0cdccb486f7c3e7 \ + --hash=sha256:ac9dc814e510436e30d0ba535f435a7f3dc97f895f844f5b3f347ec8c228a523 \ + --hash=sha256:af9bdf6c389b5add40d89b201425b531e0a5cceb3cfdcc69f04d3d531c6be74f \ + --hash=sha256:cab904683bf9e2ecbbe9ff235bfe056f0eba754d0168ad5407832928d579e7ab \ + --hash=sha256:cc5516bdb4858d972fbc31d246bdb390eab8df1a26e2353be2dbc0c2d7f5421a \ + --hash=sha256:cfd7de17cef6ab559e9f5ab859f0d3296393bc78f69030967ca4d87a541b97a0 \ + --hash=sha256:d0b856cb19c60cd40198be5d8d4b556228e3dcd545b4f423d1ad812bfdca5884 \ + --hash=sha256:d40a8533ed545390ef8315b8e25c4bb85739b90bd0f3fe1280a29ae364cc55d8 \ + --hash=sha256:f70737c157d7edf749bcb952d13854e8f745cec695a01bdc6e29c29c288fc36e \ + --hash=sha256:fe26fc46fa8c6e0ae3f47ddccfbb136253c831c3289bba044befe68f467bfb16 + # via -r third_party/pip/requirements.in +six==1.17.0 \ + --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ + --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 + # via + # html5lib + # parse-type +sortedcontainers==2.4.0 \ + --hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \ + --hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0 + # via cyclonedx-python-lib +termcolor==2.0.0 \ + --hash=sha256:4889f2243b1da3934fc6cf4b57ee50a0ce98065ec06bfddac3fa6db24a9fcde2 \ + --hash=sha256:6e1a4b8e9c064ad8f9dfe2f9b35a7871d6ea5b6bb4f9da55bc0fd494b2302204 + # via -r third_party/pip/requirements.in +toml==0.10.2 \ + --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ + --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f + # via pip-audit +tomlkit==0.13.3 \ + --hash=sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1 \ + --hash=sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0 + # via pylint +types-certifi==2021.10.8.3 \ + --hash=sha256:72cf7798d165bc0b76e1c10dd1ea3097c7063c42c21d664523b928e88b554a4f \ + --hash=sha256:b2d1e325e69f71f7c78e5943d410e650b4707bb0ef32e4ddf3da37f54176e88a + # via -r third_party/pip/requirements.in +types-chardet==5.0.4.6 \ + --hash=sha256:caf4c74cd13ccfd8b3313c314aba943b159de562a2573ed03137402b2bb37818 \ + --hash=sha256:ea832d87e798abf1e4dfc73767807c2b7fee35d0003ae90348aea4ae00fb004d + # via -r third_party/pip/requirements.in +types-html5lib==1.1.11.20240806 \ + --hash=sha256:575c4fd84ba8eeeaa8520c7e4c7042b7791f5ec3e9c0a5d5c418124c42d9e7e4 \ + --hash=sha256:8060dc98baf63d6796a765bbbc809fff9f7a383f6e3a9add526f814c086545ef + # via -r third_party/pip/requirements.in +types-jsonschema==4.23.0.20240712 \ + --hash=sha256:8c33177ce95336241c1d61ccb56a9964d4361b99d5f1cd81a1ab4909b0dd7cf4 \ + --hash=sha256:b20db728dcf7ea3e80e9bdeb55e8b8420c6c040cda14e8cf284465adee71d217 + # via -r third_party/pip/requirements.in +types-python-dateutil==2.9.0.20240821 \ + --hash=sha256:9649d1dcb6fef1046fb18bebe9ea2aa0028b160918518c34589a46045f6ebd98 \ + --hash=sha256:f5889fcb4e63ed4aaa379b44f93c32593d50b9a94c9a60a0c854d8cc3511cd57 + # via -r third_party/pip/requirements.in +types-pyyaml==6.0.12.20240724 \ + --hash=sha256:cf7b31ae67e0c5b2919c703d2affc415485099d3fe6666a6912f040fd05cb67f \ + --hash=sha256:e5becec598f3aa3a2ddf671de4a75fa1c6856fbf73b2840286c9d50fae2d5d48 + # via -r third_party/pip/requirements.in +types-requests==2.32.0.20240712 \ + --hash=sha256:90c079ff05e549f6bf50e02e910210b98b8ff1ebdd18e19c873cd237737c1358 \ + --hash=sha256:f754283e152c752e46e70942fa2a146b5bc70393522257bb85bd1ef7e019dcc3 + # via -r third_party/pip/requirements.in +types-setuptools==72.2.0.20240821 \ + --hash=sha256:260e89d6d3b42cc35f9f0f382d030713b7b547344a664c05c9175e6ba124fac7 \ + --hash=sha256:e349b8015608879939f27ee370672f801287c46f5caa2d188d416336172c4965 + # via -r third_party/pip/requirements.in +types-six==1.16.21.20240513 \ + --hash=sha256:af2a105be6d504339bfed81319cc8e8697865f0ee5c6baa63658f127b33b9e63 \ + --hash=sha256:cdf445b5161bf17753500713a475ab79a45bd0d87728b8bfcecd86e2fbf66402 + # via -r third_party/pip/requirements.in +types-typed-ast==1.5.8.7 \ + --hash=sha256:97bdd9b4228f96c6904a76e10a050305ddadb529bd35e4d8234711e09c41b543 \ + --hash=sha256:f7795f6f9d597b35212314040b993f6613b51d81738edce3c1e3a3e9ef655124 + # via -r third_party/pip/requirements.in +types-urllib3==1.26.25.14 \ + --hash=sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f \ + --hash=sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e + # via -r third_party/pip/requirements.in +typing-extensions==4.14.1 \ + --hash=sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36 \ + --hash=sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76 + # via + # mypy + # pytest-bdd + # referencing +urllib3==2.5.0 \ + --hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \ + --hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc + # via + # requests + # types-requests +webencodings==0.5.1 \ + --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ + --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 + # via html5lib + +# The following packages are considered to be unsafe in a requirements file: +pip==25.2 \ + --hash=sha256:578283f006390f85bb6282dffb876454593d637f5d1be494b5202ce4877e71f2 \ + --hash=sha256:6d67a2b4e7f14d8b31b8b52648866fa717f45a1eb70e83002f4331d07e953717 + # via pip-api diff --git a/third_party/pip/requirements_lock_3_12.txt b/third_party/pip/requirements_lock_3_12.txt new file mode 100644 index 0000000..53664da --- /dev/null +++ b/third_party/pip/requirements_lock_3_12.txt @@ -0,0 +1,788 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# bazel run //third_party/pip:requirements_3_12.update +# +astroid==3.1.0 \ + --hash=sha256:951798f922990137ac090c53af473db7ab4e70c770e6d7fae0cec59f74411819 \ + --hash=sha256:ac248253bfa4bd924a0de213707e7ebeeb3138abeb48d798784ead1e56d419d4 + # via pylint +attrs==25.3.0 \ + --hash=sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3 \ + --hash=sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b + # via referencing +black==24.4.2 \ + --hash=sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474 \ + --hash=sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1 \ + --hash=sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0 \ + --hash=sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8 \ + --hash=sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96 \ + --hash=sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1 \ + --hash=sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04 \ + --hash=sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021 \ + --hash=sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94 \ + --hash=sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d \ + --hash=sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c \ + --hash=sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7 \ + --hash=sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c \ + --hash=sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc \ + --hash=sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7 \ + --hash=sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d \ + --hash=sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c \ + --hash=sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741 \ + --hash=sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce \ + --hash=sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb \ + --hash=sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063 \ + --hash=sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e + # via -r third_party/pip/requirements.in +boolean-py==5.0 \ + --hash=sha256:60cbc4bad079753721d32649545505362c754e121570ada4658b852a3a318d95 \ + --hash=sha256:ef28a70bd43115208441b53a045d1549e2f0ec6e3d08a9d142cbc41c1938e8d9 + # via license-expression +cachecontrol[filecache]==0.14.3 \ + --hash=sha256:73e7efec4b06b20d9267b441c1f733664f989fb8688391b670ca812d70795d11 \ + --hash=sha256:b35e44a3113f17d2a31c1e6b27b9de6d4405f84ae51baa8c1d3cc5b633010cae + # via + # cachecontrol + # pip-audit +certifi==2025.8.3 \ + --hash=sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407 \ + --hash=sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5 + # via requests +charset-normalizer==3.4.3 \ + --hash=sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91 \ + --hash=sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0 \ + --hash=sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154 \ + --hash=sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601 \ + --hash=sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884 \ + --hash=sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07 \ + --hash=sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c \ + --hash=sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64 \ + --hash=sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe \ + --hash=sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f \ + --hash=sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432 \ + --hash=sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc \ + --hash=sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa \ + --hash=sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9 \ + --hash=sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae \ + --hash=sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19 \ + --hash=sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d \ + --hash=sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e \ + --hash=sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4 \ + --hash=sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7 \ + --hash=sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312 \ + --hash=sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92 \ + --hash=sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31 \ + --hash=sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c \ + --hash=sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f \ + --hash=sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99 \ + --hash=sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b \ + --hash=sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15 \ + --hash=sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392 \ + --hash=sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f \ + --hash=sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8 \ + --hash=sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491 \ + --hash=sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0 \ + --hash=sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc \ + --hash=sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0 \ + --hash=sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f \ + --hash=sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a \ + --hash=sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40 \ + --hash=sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927 \ + --hash=sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849 \ + --hash=sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce \ + --hash=sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14 \ + --hash=sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05 \ + --hash=sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c \ + --hash=sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c \ + --hash=sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a \ + --hash=sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc \ + --hash=sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34 \ + --hash=sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9 \ + --hash=sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096 \ + --hash=sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14 \ + --hash=sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30 \ + --hash=sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b \ + --hash=sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b \ + --hash=sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942 \ + --hash=sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db \ + --hash=sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5 \ + --hash=sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b \ + --hash=sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce \ + --hash=sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669 \ + --hash=sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0 \ + --hash=sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018 \ + --hash=sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93 \ + --hash=sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe \ + --hash=sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049 \ + --hash=sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a \ + --hash=sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef \ + --hash=sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2 \ + --hash=sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca \ + --hash=sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16 \ + --hash=sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f \ + --hash=sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb \ + --hash=sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1 \ + --hash=sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557 \ + --hash=sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37 \ + --hash=sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7 \ + --hash=sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72 \ + --hash=sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c \ + --hash=sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9 + # via requests +click==8.2.1 \ + --hash=sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202 \ + --hash=sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b + # via black +colorama==0.4.6 \ + --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ + --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 + # via -r third_party/pip/requirements.in +coverage==7.5.1 \ + --hash=sha256:0646599e9b139988b63704d704af8e8df7fa4cbc4a1f33df69d97f36cb0a38de \ + --hash=sha256:0cdcbc320b14c3e5877ee79e649677cb7d89ef588852e9583e6b24c2e5072661 \ + --hash=sha256:0d0a0f5e06881ecedfe6f3dd2f56dcb057b6dbeb3327fd32d4b12854df36bf26 \ + --hash=sha256:1434e088b41594baa71188a17533083eabf5609e8e72f16ce8c186001e6b8c41 \ + --hash=sha256:16db7f26000a07efcf6aea00316f6ac57e7d9a96501e990a36f40c965ec7a95d \ + --hash=sha256:1cc0fe9b0b3a8364093c53b0b4c0c2dd4bb23acbec4c9240b5f284095ccf7981 \ + --hash=sha256:1fc81d5878cd6274ce971e0a3a18a8803c3fe25457165314271cf78e3aae3aa2 \ + --hash=sha256:2ec92012fefebee89a6b9c79bc39051a6cb3891d562b9270ab10ecfdadbc0c34 \ + --hash=sha256:39afcd3d4339329c5f58de48a52f6e4e50f6578dd6099961cf22228feb25f38f \ + --hash=sha256:4a7b0ceee8147444347da6a66be737c9d78f3353b0681715b668b72e79203e4a \ + --hash=sha256:4a9ca3f2fae0088c3c71d743d85404cec8df9be818a005ea065495bedc33da35 \ + --hash=sha256:4bf0655ab60d754491004a5efd7f9cccefcc1081a74c9ef2da4735d6ee4a6223 \ + --hash=sha256:4cc37def103a2725bc672f84bd939a6fe4522310503207aae4d56351644682f1 \ + --hash=sha256:4fc84a37bfd98db31beae3c2748811a3fa72bf2007ff7902f68746d9757f3746 \ + --hash=sha256:5037f8fcc2a95b1f0e80585bd9d1ec31068a9bcb157d9750a172836e98bc7a90 \ + --hash=sha256:54de9ef3a9da981f7af93eafde4ede199e0846cd819eb27c88e2b712aae9708c \ + --hash=sha256:556cf1a7cbc8028cb60e1ff0be806be2eded2daf8129b8811c63e2b9a6c43bca \ + --hash=sha256:57e0204b5b745594e5bc14b9b50006da722827f0b8c776949f1135677e88d0b8 \ + --hash=sha256:5a5740d1fb60ddf268a3811bcd353de34eb56dc24e8f52a7f05ee513b2d4f596 \ + --hash=sha256:5c3721c2c9e4c4953a41a26c14f4cef64330392a6d2d675c8b1db3b645e31f0e \ + --hash=sha256:5fa567e99765fe98f4e7d7394ce623e794d7cabb170f2ca2ac5a4174437e90dd \ + --hash=sha256:5fd215c0c7d7aab005221608a3c2b46f58c0285a819565887ee0b718c052aa4e \ + --hash=sha256:6175d1a0559986c6ee3f7fccfc4a90ecd12ba0a383dcc2da30c2b9918d67d8a3 \ + --hash=sha256:61c4bf1ba021817de12b813338c9be9f0ad5b1e781b9b340a6d29fc13e7c1b5e \ + --hash=sha256:6537e7c10cc47c595828b8a8be04c72144725c383c4702703ff4e42e44577312 \ + --hash=sha256:68f962d9b72ce69ea8621f57551b2fa9c70509af757ee3b8105d4f51b92b41a7 \ + --hash=sha256:7352b9161b33fd0b643ccd1f21f3a3908daaddf414f1c6cb9d3a2fd618bf2572 \ + --hash=sha256:796a79f63eca8814ca3317a1ea443645c9ff0d18b188de470ed7ccd45ae79428 \ + --hash=sha256:79afb6197e2f7f60c4824dd4b2d4c2ec5801ceb6ba9ce5d2c3080e5660d51a4f \ + --hash=sha256:7a588d39e0925f6a2bff87154752481273cdb1736270642aeb3635cb9b4cad07 \ + --hash=sha256:8748731ad392d736cc9ccac03c9845b13bb07d020a33423fa5b3a36521ac6e4e \ + --hash=sha256:8fe7502616b67b234482c3ce276ff26f39ffe88adca2acf0261df4b8454668b4 \ + --hash=sha256:9314d5678dcc665330df5b69c1e726a0e49b27df0461c08ca12674bcc19ef136 \ + --hash=sha256:9735317685ba6ec7e3754798c8871c2f49aa5e687cc794a0b1d284b2389d1bd5 \ + --hash=sha256:9981706d300c18d8b220995ad22627647be11a4276721c10911e0e9fa44c83e8 \ + --hash=sha256:9e78295f4144f9dacfed4f92935fbe1780021247c2fabf73a819b17f0ccfff8d \ + --hash=sha256:b016ea6b959d3b9556cb401c55a37547135a587db0115635a443b2ce8f1c7228 \ + --hash=sha256:b6cf3764c030e5338e7f61f95bd21147963cf6aa16e09d2f74f1fa52013c1206 \ + --hash=sha256:beccf7b8a10b09c4ae543582c1319c6df47d78fd732f854ac68d518ee1fb97fa \ + --hash=sha256:c0884920835a033b78d1c73b6d3bbcda8161a900f38a488829a83982925f6c2e \ + --hash=sha256:c3e757949f268364b96ca894b4c342b41dc6f8f8b66c37878aacef5930db61be \ + --hash=sha256:ca498687ca46a62ae590253fba634a1fe9836bc56f626852fb2720f334c9e4e5 \ + --hash=sha256:d1d0d98d95dd18fe29dc66808e1accf59f037d5716f86a501fc0256455219668 \ + --hash=sha256:d21918e9ef11edf36764b93101e2ae8cc82aa5efdc7c5a4e9c6c35a48496d601 \ + --hash=sha256:d7fed867ee50edf1a0b4a11e8e5d0895150e572af1cd6d315d557758bfa9c057 \ + --hash=sha256:db66fc317a046556a96b453a58eced5024af4582a8dbdc0c23ca4dbc0d5b3146 \ + --hash=sha256:dde0070c40ea8bb3641e811c1cfbf18e265d024deff6de52c5950677a8fb1e0f \ + --hash=sha256:df4e745a81c110e7446b1cc8131bf986157770fa405fe90e15e850aaf7619bc8 \ + --hash=sha256:e2213def81a50519d7cc56ed643c9e93e0247f5bbe0d1247d15fa520814a7cd7 \ + --hash=sha256:ef48e2707fb320c8f139424a596f5b69955a85b178f15af261bab871873bb987 \ + --hash=sha256:f152cbf5b88aaeb836127d920dd0f5e7edff5a66f10c079157306c4343d86c19 \ + --hash=sha256:fc0b4d8bfeabd25ea75e94632f5b6e047eef8adaed0c2161ada1e922e7f7cece + # via -r third_party/pip/requirements.in +cyclonedx-python-lib==6.4.4 \ + --hash=sha256:1b6f9109b6b9e91636dff822c2de90a05c0c8af120317713c1b879dbfdebdff8 \ + --hash=sha256:c366619cc4effd528675f1f7a7a00be30b6695ff03f49c64880ad15acbebc341 + # via pip-audit +defusedxml==0.7.1 \ + --hash=sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69 \ + --hash=sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61 + # via py-serializable +dill==0.4.0 \ + --hash=sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0 \ + --hash=sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049 + # via pylint +filelock==3.18.0 \ + --hash=sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2 \ + --hash=sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de + # via cachecontrol +html5lib==1.1 \ + --hash=sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d \ + --hash=sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f + # via pip-audit +idna==3.10 \ + --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ + --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 + # via requests +iniconfig==2.1.0 \ + --hash=sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7 \ + --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 + # via pytest +isort==5.13.2 \ + --hash=sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109 \ + --hash=sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6 + # via + # -r third_party/pip/requirements.in + # pylint +license-expression==30.4.4 \ + --hash=sha256:421788fdcadb41f049d2dc934ce666626265aeccefddd25e162a26f23bcbf8a4 \ + --hash=sha256:73448f0aacd8d0808895bdc4b2c8e01a8d67646e4188f887375398c761f340fd + # via cyclonedx-python-lib +mako==1.3.10 \ + --hash=sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28 \ + --hash=sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59 + # via pytest-bdd +markdown-it-py==4.0.0 \ + --hash=sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147 \ + --hash=sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3 + # via rich +markupsafe==3.0.2 \ + --hash=sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4 \ + --hash=sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30 \ + --hash=sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0 \ + --hash=sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9 \ + --hash=sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396 \ + --hash=sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13 \ + --hash=sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028 \ + --hash=sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca \ + --hash=sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557 \ + --hash=sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832 \ + --hash=sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0 \ + --hash=sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b \ + --hash=sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579 \ + --hash=sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a \ + --hash=sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c \ + --hash=sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff \ + --hash=sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c \ + --hash=sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22 \ + --hash=sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094 \ + --hash=sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb \ + --hash=sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e \ + --hash=sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5 \ + --hash=sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a \ + --hash=sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d \ + --hash=sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a \ + --hash=sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b \ + --hash=sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8 \ + --hash=sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225 \ + --hash=sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c \ + --hash=sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144 \ + --hash=sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f \ + --hash=sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87 \ + --hash=sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d \ + --hash=sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93 \ + --hash=sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf \ + --hash=sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158 \ + --hash=sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84 \ + --hash=sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb \ + --hash=sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48 \ + --hash=sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171 \ + --hash=sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c \ + --hash=sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6 \ + --hash=sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd \ + --hash=sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d \ + --hash=sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1 \ + --hash=sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d \ + --hash=sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca \ + --hash=sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a \ + --hash=sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29 \ + --hash=sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe \ + --hash=sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798 \ + --hash=sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c \ + --hash=sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8 \ + --hash=sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f \ + --hash=sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f \ + --hash=sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a \ + --hash=sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178 \ + --hash=sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0 \ + --hash=sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79 \ + --hash=sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430 \ + --hash=sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50 + # via mako +mccabe==0.7.0 \ + --hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \ + --hash=sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e + # via pylint +mdurl==0.1.2 \ + --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ + --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba + # via markdown-it-py +msgpack==1.1.1 \ + --hash=sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8 \ + --hash=sha256:1abfc6e949b352dadf4bce0eb78023212ec5ac42f6abfd469ce91d783c149c2a \ + --hash=sha256:1b13fe0fb4aac1aa5320cd693b297fe6fdef0e7bea5518cbc2dd5299f873ae90 \ + --hash=sha256:1d75f3807a9900a7d575d8d6674a3a47e9f227e8716256f35bc6f03fc597ffbf \ + --hash=sha256:2fbbc0b906a24038c9958a1ba7ae0918ad35b06cb449d398b76a7d08470b0ed9 \ + --hash=sha256:33be9ab121df9b6b461ff91baac6f2731f83d9b27ed948c5b9d1978ae28bf157 \ + --hash=sha256:353b6fc0c36fde68b661a12949d7d49f8f51ff5fa019c1e47c87c4ff34b080ed \ + --hash=sha256:36043272c6aede309d29d56851f8841ba907a1a3d04435e43e8a19928e243c1d \ + --hash=sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0 \ + --hash=sha256:3a89cd8c087ea67e64844287ea52888239cbd2940884eafd2dcd25754fb72232 \ + --hash=sha256:40eae974c873b2992fd36424a5d9407f93e97656d999f43fca9d29f820899084 \ + --hash=sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5 \ + --hash=sha256:435807eeb1bc791ceb3247d13c79868deb22184e1fc4224808750f0d7d1affc1 \ + --hash=sha256:4835d17af722609a45e16037bb1d4d78b7bdf19d6c0128116d178956618c4e88 \ + --hash=sha256:4a28e8072ae9779f20427af07f53bbb8b4aa81151054e882aee333b158da8752 \ + --hash=sha256:4d3237b224b930d58e9d83c81c0dba7aacc20fcc2f89c1e5423aa0529a4cd142 \ + --hash=sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac \ + --hash=sha256:4fd6b577e4541676e0cc9ddc1709d25014d3ad9a66caa19962c4f5de30fc09ef \ + --hash=sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323 \ + --hash=sha256:5692095123007180dca3e788bb4c399cc26626da51629a31d40207cb262e67f4 \ + --hash=sha256:5fd1b58e1431008a57247d6e7cc4faa41c3607e8e7d4aaf81f7c29ea013cb458 \ + --hash=sha256:61abccf9de335d9efd149e2fff97ed5974f2481b3353772e8e2dd3402ba2bd57 \ + --hash=sha256:61e35a55a546a1690d9d09effaa436c25ae6130573b6ee9829c37ef0f18d5e78 \ + --hash=sha256:6640fd979ca9a212e4bcdf6eb74051ade2c690b862b679bfcb60ae46e6dc4bfd \ + --hash=sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69 \ + --hash=sha256:6f64ae8fe7ffba251fecb8408540c34ee9df1c26674c50c4544d72dbf792e5ce \ + --hash=sha256:71ef05c1726884e44f8b1d1773604ab5d4d17729d8491403a705e649116c9558 \ + --hash=sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd \ + --hash=sha256:78426096939c2c7482bf31ef15ca219a9e24460289c00dd0b94411040bb73ad2 \ + --hash=sha256:79c408fcf76a958491b4e3b103d1c417044544b68e96d06432a189b43d1215c8 \ + --hash=sha256:7a17ac1ea6ec3c7687d70201cfda3b1e8061466f28f686c24f627cae4ea8efd0 \ + --hash=sha256:7da8831f9a0fdb526621ba09a281fadc58ea12701bc709e7b8cbc362feabc295 \ + --hash=sha256:870b9a626280c86cff9c576ec0d9cbcc54a1e5ebda9cd26dab12baf41fee218c \ + --hash=sha256:88d1e966c9235c1d4e2afac21ca83933ba59537e2e2727a999bf3f515ca2af26 \ + --hash=sha256:88daaf7d146e48ec71212ce21109b66e06a98e5e44dca47d853cbfe171d6c8d2 \ + --hash=sha256:8a8b10fdb84a43e50d38057b06901ec9da52baac6983d3f709d8507f3889d43f \ + --hash=sha256:8b17ba27727a36cb73aabacaa44b13090feb88a01d012c0f4be70c00f75048b4 \ + --hash=sha256:8b65b53204fe1bd037c40c4148d00ef918eb2108d24c9aaa20bc31f9810ce0a8 \ + --hash=sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9 \ + --hash=sha256:96decdfc4adcbc087f5ea7ebdcfd3dee9a13358cae6e81d54be962efc38f6338 \ + --hash=sha256:996f2609ddf0142daba4cefd767d6db26958aac8439ee41db9cc0db9f4c4c3a6 \ + --hash=sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a \ + --hash=sha256:a32747b1b39c3ac27d0670122b57e6e57f28eefb725e0b625618d1b59bf9d1e0 \ + --hash=sha256:a494554874691720ba5891c9b0b39474ba43ffb1aaf32a5dac874effb1619e1a \ + --hash=sha256:a8ef6e342c137888ebbfb233e02b8fbd689bb5b5fcc59b34711ac47ebd504478 \ + --hash=sha256:ae497b11f4c21558d95de9f64fff7053544f4d1a17731c866143ed6bb4591238 \ + --hash=sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7 \ + --hash=sha256:b8f93dcddb243159c9e4109c9750ba5b335ab8d48d9522c5308cd05d7e3ce600 \ + --hash=sha256:ba0c325c3f485dc54ec298d8b024e134acf07c10d494ffa24373bea729acf704 \ + --hash=sha256:bb29aaa613c0a1c40d1af111abf025f1732cab333f96f285d6a93b934738a68a \ + --hash=sha256:bba1be28247e68994355e028dcd668316db30c1f758d3241a7b903ac78dcd285 \ + --hash=sha256:cb643284ab0ed26f6957d969fe0dd8bb17beb567beb8998140b5e38a90974f6c \ + --hash=sha256:d182dac0221eb8faef2e6f44701812b467c02674a322c739355c39e94730cdbf \ + --hash=sha256:d275a9e3c81b1093c060c3837e580c37f47c51eca031f7b5fb76f7b8470f5f9b \ + --hash=sha256:d8b55ea20dc59b181d3f47103f113e6f28a5e1c89fd5b67b9140edb442ab67f2 \ + --hash=sha256:da8f41e602574ece93dbbda1fab24650d6bf2a24089f9e9dbb4f5730ec1e58ad \ + --hash=sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b \ + --hash=sha256:f5be6b6bc52fad84d010cb45433720327ce886009d862f46b26d4d154001994b \ + --hash=sha256:f6d58656842e1b2ddbe07f43f56b10a60f2ba5826164910968f5933e5178af75 + # via cachecontrol +mypy==1.11.1 \ + --hash=sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54 \ + --hash=sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a \ + --hash=sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72 \ + --hash=sha256:11965c2f571ded6239977b14deebd3f4c3abd9a92398712d6da3a772974fad69 \ + --hash=sha256:1a81cf05975fd61aec5ae16501a091cfb9f605dc3e3c878c0da32f250b74760b \ + --hash=sha256:2684d3f693073ab89d76da8e3921883019ea8a3ec20fa5d8ecca6a2db4c54bbe \ + --hash=sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4 \ + --hash=sha256:45df906e8b6804ef4b666af29a87ad9f5921aad091c79cc38e12198e220beabd \ + --hash=sha256:4c956b49c5d865394d62941b109728c5c596a415e9c5b2be663dd26a1ff07bc0 \ + --hash=sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525 \ + --hash=sha256:749fd3213916f1751fff995fccf20c6195cae941dc968f3aaadf9bb4e430e5a2 \ + --hash=sha256:79c07eb282cb457473add5052b63925e5cc97dfab9812ee65a7c7ab5e3cb551c \ + --hash=sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5 \ + --hash=sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de \ + --hash=sha256:a2b43895a0f8154df6519706d9bca8280cda52d3d9d1514b2d9c3e26792a0b74 \ + --hash=sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c \ + --hash=sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e \ + --hash=sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58 \ + --hash=sha256:b639dce63a0b19085213ec5fdd8cffd1d81988f47a2dec7100e93564f3e8fb3b \ + --hash=sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417 \ + --hash=sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411 \ + --hash=sha256:d44be7551689d9d47b7abc27c71257adfdb53f03880841a5db15ddb22dc63edb \ + --hash=sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03 \ + --hash=sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca \ + --hash=sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8 \ + --hash=sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08 \ + --hash=sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809 + # via -r third_party/pip/requirements.in +mypy-extensions==1.1.0 \ + --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \ + --hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558 + # via + # black + # mypy +packageurl-python==0.17.5 \ + --hash=sha256:a7be3f3ba70d705f738ace9bf6124f31920245a49fa69d4b416da7037dd2de61 \ + --hash=sha256:f0e55452ab37b5c192c443de1458e3f3b4d8ac27f747df6e8c48adeab081d321 + # via cyclonedx-python-lib +packaging==25.0 \ + --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ + --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f + # via + # black + # pip-audit + # pip-requirements-parser + # pytest + # pytest-bdd + # pytest-rerunfailures +parse==1.20.2 \ + --hash=sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558 \ + --hash=sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce + # via + # parse-type + # pytest-bdd +parse-type==0.6.6 \ + --hash=sha256:3ca79bbe71e170dfccc8ec6c341edfd1c2a0fc1e5cfd18330f93af938de2348c \ + --hash=sha256:513a3784104839770d690e04339a8b4d33439fcd5dd99f2e4580f9fc1097bfb2 + # via pytest-bdd +pathspec==0.12.1 \ + --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ + --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712 + # via black +pip-api==0.0.34 \ + --hash=sha256:8b2d7d7c37f2447373aa2cf8b1f60a2f2b27a84e1e9e0294a3f6ef10eb3ba6bb \ + --hash=sha256:9b75e958f14c5a2614bae415f2adf7eeb54d50a2cfbe7e24fd4826471bac3625 + # via pip-audit +pip-audit==2.7.0 \ + --hash=sha256:67740c5b1d5d967a258c3dfefc46f9713a2819c48062505ddf4b29de101c2b75 \ + --hash=sha256:83e039740653eb9ef1a78b1540ed441600cd88a560588ba2c0a169180685a522 + # via -r third_party/pip/requirements.in +pip-requirements-parser==32.0.1 \ + --hash=sha256:4659bc2a667783e7a15d190f6fccf8b2486685b6dba4c19c3876314769c57526 \ + --hash=sha256:b4fa3a7a0be38243123cf9d1f3518da10c51bdb165a2b2985566247f9155a7d3 + # via pip-audit +platformdirs==4.3.8 \ + --hash=sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc \ + --hash=sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4 + # via + # black + # pylint +pluggy==1.6.0 \ + --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ + --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + # via pytest +py-cpuinfo==9.0.0 \ + --hash=sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690 \ + --hash=sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5 + # via pytest-benchmark +py-serializable==1.1.2 \ + --hash=sha256:801be61b0a1ba64c3861f7c624f1de5cfbbabf8b458acc9cdda91e8f7e5effa1 \ + --hash=sha256:89af30bc319047d4aa0d8708af412f6ce73835e18bacf1a080028bb9e2f42bdb + # via cyclonedx-python-lib +pygments==2.19.2 \ + --hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \ + --hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b + # via rich +pylint==3.1.0 \ + --hash=sha256:507a5b60953874766d8a366e8e8c7af63e058b26345cfcb5f91f89d987fd6b74 \ + --hash=sha256:6a69beb4a6f63debebaab0a3477ecd0f559aa726af4954fc948c51f7a2549e23 + # via -r third_party/pip/requirements.in +pyparsing==3.2.3 \ + --hash=sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf \ + --hash=sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be + # via pip-requirements-parser +pytest==8.1.1 \ + --hash=sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7 \ + --hash=sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044 + # via + # -r third_party/pip/requirements.in + # pytest-bdd + # pytest-benchmark + # pytest-mock + # pytest-rerunfailures + # pytest-timeout +pytest-bdd==7.1.1 \ + --hash=sha256:1b41a10a8391f1849f0a1524d77b2991d0d8042d2253ace9b616b89b4a9b97a7 \ + --hash=sha256:331621122a46f9881110ae0dd43fedb29e96e8202de29256be3d76c53b57c1d2 + # via -r third_party/pip/requirements.in +pytest-benchmark==4.0.0 \ + --hash=sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1 \ + --hash=sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6 + # via -r third_party/pip/requirements.in +pytest-mock==3.12.0 \ + --hash=sha256:0972719a7263072da3a21c7f4773069bcc7486027d7e8e1f81d98a47e701bc4f \ + --hash=sha256:31a40f038c22cad32287bb43932054451ff5583ff094bca6f675df2f8bc1a6e9 + # via -r third_party/pip/requirements.in +pytest-rerunfailures==14.0 \ + --hash=sha256:4197bdd2eaeffdbf50b5ea6e7236f47ff0e44d1def8dae08e409f536d84e7b32 \ + --hash=sha256:4a400bcbcd3c7a4ad151ab8afac123d90eca3abe27f98725dc4d9702887d2e92 + # via -r third_party/pip/requirements.in +pytest-timeout==2.3.1 \ + --hash=sha256:12397729125c6ecbdaca01035b9e5239d4db97352320af155b3f5de1ba5165d9 \ + --hash=sha256:68188cb703edfc6a18fad98dc25a3c61e9f24d644b0b70f33af545219fc7813e + # via -r third_party/pip/requirements.in +referencing==0.36.2 \ + --hash=sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa \ + --hash=sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0 + # via types-jsonschema +requests==2.32.4 \ + --hash=sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c \ + --hash=sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422 + # via + # cachecontrol + # pip-audit +rich==14.1.0 \ + --hash=sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f \ + --hash=sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8 + # via pip-audit +rpds-py==0.27.0 \ + --hash=sha256:010c4843a3b92b54373e3d2291a7447d6c3fc29f591772cc2ea0e9f5c1da434b \ + --hash=sha256:05284439ebe7d9f5f5a668d4d8a0a1d851d16f7d47c78e1fab968c8ad30cab04 \ + --hash=sha256:0665be515767dc727ffa5f74bd2ef60b0ff85dad6bb8f50d91eaa6b5fb226f51 \ + --hash=sha256:069e0384a54f427bd65d7fda83b68a90606a3835901aaff42185fcd94f5a9295 \ + --hash=sha256:08680820d23df1df0a0260f714d12966bc6c42d02e8055a91d61e03f0c47dda0 \ + --hash=sha256:0954e3a92e1d62e83a54ea7b3fdc9efa5d61acef8488a8a3d31fdafbfb00460d \ + --hash=sha256:09965b314091829b378b60607022048953e25f0b396c2b70e7c4c81bcecf932e \ + --hash=sha256:0c431bfb91478d7cbe368d0a699978050d3b112d7f1d440a41e90faa325557fd \ + --hash=sha256:0f401c369186a5743694dd9fc08cba66cf70908757552e1f714bfc5219c655b5 \ + --hash=sha256:0f4f69d7a4300fbf91efb1fb4916421bd57804c01ab938ab50ac9c4aa2212f03 \ + --hash=sha256:11e8e28c0ba0373d052818b600474cfee2fafa6c9f36c8587d217b13ee28ca7d \ + --hash=sha256:130c1ffa5039a333f5926b09e346ab335f0d4ec393b030a18549a7c7e7c2cea4 \ + --hash=sha256:1321bce595ad70e80f97f998db37356b2e22cf98094eba6fe91782e626da2f71 \ + --hash=sha256:13bbc4846ae4c993f07c93feb21a24d8ec637573d567a924b1001e81c8ae80f9 \ + --hash=sha256:14f028eb47f59e9169bfdf9f7ceafd29dd64902141840633683d0bad5b04ff34 \ + --hash=sha256:15ea4d2e182345dd1b4286593601d766411b43f868924afe297570658c31a62b \ + --hash=sha256:181bc29e59e5e5e6e9d63b143ff4d5191224d355e246b5a48c88ce6b35c4e466 \ + --hash=sha256:183f5e221ba3e283cd36fdfbe311d95cd87699a083330b4f792543987167eff1 \ + --hash=sha256:184f0d7b342967f6cda94a07d0e1fae177d11d0b8f17d73e06e36ac02889f303 \ + --hash=sha256:190d7285cd3bb6d31d37a0534d7359c1ee191eb194c511c301f32a4afa5a1dd4 \ + --hash=sha256:19c990fdf5acecbf0623e906ae2e09ce1c58947197f9bced6bbd7482662231c4 \ + --hash=sha256:1d66f45b9399036e890fb9c04e9f70c33857fd8f58ac8db9f3278cfa835440c3 \ + --hash=sha256:203f581accef67300a942e49a37d74c12ceeef4514874c7cede21b012613ca2c \ + --hash=sha256:20e222a44ae9f507d0f2678ee3dd0c45ec1e930f6875d99b8459631c24058aec \ + --hash=sha256:2406d034635d1497c596c40c85f86ecf2bf9611c1df73d14078af8444fe48031 \ + --hash=sha256:249ab91ceaa6b41abc5f19513cb95b45c6f956f6b89f1fe3d99c81255a849f9e \ + --hash=sha256:25a4aebf8ca02bbb90a9b3e7a463bbf3bee02ab1c446840ca07b1695a68ce424 \ + --hash=sha256:27bac29bbbf39601b2aab474daf99dbc8e7176ca3389237a23944b17f8913d97 \ + --hash=sha256:299a245537e697f28a7511d01038c310ac74e8ea213c0019e1fc65f52c0dcb23 \ + --hash=sha256:2cff9bdd6c7b906cc562a505c04a57d92e82d37200027e8d362518df427f96cd \ + --hash=sha256:2e307cb5f66c59ede95c00e93cd84190a5b7f3533d7953690b2036780622ba81 \ + --hash=sha256:2e39169ac6aae06dd79c07c8a69d9da867cef6a6d7883a0186b46bb46ccfb0c3 \ + --hash=sha256:2fe6e18e5c8581f0361b35ae575043c7029d0a92cb3429e6e596c2cdde251432 \ + --hash=sha256:3001013dae10f806380ba739d40dee11db1ecb91684febb8406a87c2ded23dae \ + --hash=sha256:32196b5a99821476537b3f7732432d64d93a58d680a52c5e12a190ee0135d8b5 \ + --hash=sha256:33ba649a6e55ae3808e4c39e01580dc9a9b0d5b02e77b66bb86ef117922b1264 \ + --hash=sha256:341d8acb6724c0c17bdf714319c393bb27f6d23d39bc74f94221b3e59fc31828 \ + --hash=sha256:343cf24de9ed6c728abefc5d5c851d5de06497caa7ac37e5e65dd572921ed1b5 \ + --hash=sha256:36184b44bf60a480863e51021c26aca3dfe8dd2f5eeabb33622b132b9d8b8b54 \ + --hash=sha256:3841f66c1ffdc6cebce8aed64e36db71466f1dc23c0d9a5592e2a782a3042c79 \ + --hash=sha256:4045e2fc4b37ec4b48e8907a5819bdd3380708c139d7cc358f03a3653abedb89 \ + --hash=sha256:419dd9c98bcc9fb0242be89e0c6e922df333b975d4268faa90d58499fd9c9ebe \ + --hash=sha256:42894616da0fc0dcb2ec08a77896c3f56e9cb2f4b66acd76fc8992c3557ceb1c \ + --hash=sha256:42ccc57ff99166a55a59d8c7d14f1a357b7749f9ed3584df74053fd098243451 \ + --hash=sha256:4300e15e7d03660f04be84a125d1bdd0e6b2f674bc0723bc0fd0122f1a4585dc \ + --hash=sha256:443d239d02d9ae55b74015234f2cd8eb09e59fbba30bf60baeb3123ad4c6d5ff \ + --hash=sha256:44524b96481a4c9b8e6c46d6afe43fa1fb485c261e359fbe32b63ff60e3884d8 \ + --hash=sha256:45d04a73c54b6a5fd2bab91a4b5bc8b426949586e61340e212a8484919183859 \ + --hash=sha256:46f48482c1a4748ab2773f75fffbdd1951eb59794e32788834b945da857c47a8 \ + --hash=sha256:4790c9d5dd565ddb3e9f656092f57268951398cef52e364c405ed3112dc7c7c1 \ + --hash=sha256:4bc262ace5a1a7dc3e2eac2fa97b8257ae795389f688b5adf22c5db1e2431c43 \ + --hash=sha256:4c3f8a0d4802df34fcdbeb3dfe3a4d8c9a530baea8fafdf80816fcaac5379d83 \ + --hash=sha256:5355527adaa713ab693cbce7c1e0ec71682f599f61b128cf19d07e5c13c9b1f1 \ + --hash=sha256:555ed147cbe8c8f76e72a4c6cd3b7b761cbf9987891b9448808148204aed74a5 \ + --hash=sha256:55d42a0ef2bdf6bc81e1cc2d49d12460f63c6ae1423c4f4851b828e454ccf6f1 \ + --hash=sha256:59195dc244fc183209cf8a93406889cadde47dfd2f0a6b137783aa9c56d67c85 \ + --hash=sha256:59714ab0a5af25d723d8e9816638faf7f4254234decb7d212715c1aa71eee7be \ + --hash=sha256:5b3a5c8089eed498a3af23ce87a80805ff98f6ef8f7bdb70bd1b7dae5105f6ac \ + --hash=sha256:5d6790ff400254137b81b8053b34417e2c46921e302d655181d55ea46df58cf7 \ + --hash=sha256:5df559e9e7644d9042f626f2c3997b555f347d7a855a15f170b253f6c5bfe358 \ + --hash=sha256:5fa01b3d5e3b7d97efab65bd3d88f164e289ec323a8c033c5c38e53ee25c007e \ + --hash=sha256:61490d57e82e23b45c66f96184237994bfafa914433b8cd1a9bb57fecfced59d \ + --hash=sha256:6168af0be75bba990a39f9431cdfae5f0ad501f4af32ae62e8856307200517b8 \ + --hash=sha256:64a0fe3f334a40b989812de70160de6b0ec7e3c9e4a04c0bbc48d97c5d3600ae \ + --hash=sha256:64f689ab822f9b5eb6dfc69893b4b9366db1d2420f7db1f6a2adf2a9ca15ad64 \ + --hash=sha256:699c346abc73993962cac7bb4f02f58e438840fa5458a048d3a178a7a670ba86 \ + --hash=sha256:6b96b0b784fe5fd03beffff2b1533dc0d85e92bab8d1b2c24ef3a5dc8fac5669 \ + --hash=sha256:6bde37765564cd22a676dd8101b657839a1854cfaa9c382c5abf6ff7accfd4ae \ + --hash=sha256:6c135708e987f46053e0a1246a206f53717f9fadfba27174a9769ad4befba5c3 \ + --hash=sha256:6c27a7054b5224710fcfb1a626ec3ff4f28bcb89b899148c72873b18210e446b \ + --hash=sha256:6de6a7f622860af0146cb9ee148682ff4d0cea0b8fd3ad51ce4d40efb2f061d0 \ + --hash=sha256:737005088449ddd3b3df5a95476ee1c2c5c669f5c30eed909548a92939c0e12d \ + --hash=sha256:7451ede3560086abe1aa27dcdcf55cd15c96b56f543fb12e5826eee6f721f858 \ + --hash=sha256:7873b65686a6471c0037139aa000d23fe94628e0daaa27b6e40607c90e3f5ec4 \ + --hash=sha256:79af163a4b40bbd8cfd7ca86ec8b54b81121d3b213b4435ea27d6568bcba3e9d \ + --hash=sha256:7aed8118ae20515974650d08eb724150dc2e20c2814bcc307089569995e88a14 \ + --hash=sha256:7cf9bc4508efb18d8dff6934b602324eb9f8c6644749627ce001d6f38a490889 \ + --hash=sha256:7e57906e38583a2cba67046a09c2637e23297618dc1f3caddbc493f2be97c93f \ + --hash=sha256:7ec85994f96a58cf7ed288caa344b7fe31fd1d503bdf13d7331ead5f70ab60d5 \ + --hash=sha256:81f81bbd7cdb4bdc418c09a73809abeda8f263a6bf8f9c7f93ed98b5597af39d \ + --hash=sha256:86aca1616922b40d8ac1b3073a1ead4255a2f13405e5700c01f7c8d29a03972d \ + --hash=sha256:88051c3b7d5325409f433c5a40328fcb0685fc04e5db49ff936e910901d10114 \ + --hash=sha256:887ab1f12b0d227e9260558a4a2320024b20102207ada65c43e1ffc4546df72e \ + --hash=sha256:8a06aa1197ec0281eb1d7daf6073e199eb832fe591ffa329b88bae28f25f5fe5 \ + --hash=sha256:8a1dca5507fa1337f75dcd5070218b20bc68cf8844271c923c1b79dfcbc20391 \ + --hash=sha256:8b23cf252f180cda89220b378d917180f29d313cd6a07b2431c0d3b776aae86f \ + --hash=sha256:8d0e09cf4863c74106b5265c2c310f36146e2b445ff7b3018a56799f28f39f6f \ + --hash=sha256:8de567dec6d451649a781633d36f5c7501711adee329d76c095be2178855b042 \ + --hash=sha256:90fb790138c1a89a2e58c9282fe1089638401f2f3b8dddd758499041bc6e0774 \ + --hash=sha256:92f3b3ec3e6008a1fe00b7c0946a170f161ac00645cde35e3c9a68c2475e8156 \ + --hash=sha256:935afcdea4751b0ac918047a2df3f720212892347767aea28f5b3bf7be4f27c0 \ + --hash=sha256:9a0ff7ee28583ab30a52f371b40f54e7138c52ca67f8ca17ccb7ccf0b383cb5f \ + --hash=sha256:9ad08547995a57e74fea6abaf5940d399447935faebbd2612b3b0ca6f987946b \ + --hash=sha256:9b2a4e17bfd68536c3b801800941c95a1d4a06e3cada11c146093ba939d9638d \ + --hash=sha256:9b78430703cfcf5f5e86eb74027a1ed03a93509273d7c705babb547f03e60016 \ + --hash=sha256:9d0f92b78cfc3b74a42239fdd8c1266f4715b573204c234d2f9fc3fc7a24f185 \ + --hash=sha256:9da162b718b12c4219eeeeb68a5b7552fbc7aadedf2efee440f88b9c0e54b45d \ + --hash=sha256:a00c91104c173c9043bc46f7b30ee5e6d2f6b1149f11f545580f5d6fdff42c0b \ + --hash=sha256:a029be818059870664157194e46ce0e995082ac49926f1423c1f058534d2aaa9 \ + --hash=sha256:a1b3db5fae5cbce2131b7420a3f83553d4d89514c03d67804ced36161fe8b6b2 \ + --hash=sha256:a4cf32a26fa744101b67bfd28c55d992cd19438aff611a46cac7f066afca8fd4 \ + --hash=sha256:aa0bf113d15e8abdfee92aa4db86761b709a09954083afcb5bf0f952d6065fdb \ + --hash=sha256:ab47fe727c13c09d0e6f508e3a49e545008e23bf762a245b020391b621f5b726 \ + --hash=sha256:af22763a0a1eff106426a6e1f13c4582e0d0ad89c1493ab6c058236174cd6c6a \ + --hash=sha256:af9d4fd79ee1cc8e7caf693ee02737daabfc0fcf2773ca0a4735b356c8ad6f7c \ + --hash=sha256:b1fef1f13c842a39a03409e30ca0bf87b39a1e2a305a9924deadb75a43105d23 \ + --hash=sha256:b2eff8ee57c5996b0d2a07c3601fb4ce5fbc37547344a26945dd9e5cbd1ed27a \ + --hash=sha256:b4c4fbbcff474e1e5f38be1bf04511c03d492d42eec0babda5d03af3b5589374 \ + --hash=sha256:b8a4131698b6992b2a56015f51646711ec5d893a0b314a4b985477868e240c87 \ + --hash=sha256:b8a7acf04fda1f30f1007f3cc96d29d8cf0a53e626e4e1655fdf4eabc082d367 \ + --hash=sha256:ba783541be46f27c8faea5a6645e193943c17ea2f0ffe593639d906a327a9bcc \ + --hash=sha256:be0744661afbc4099fef7f4e604e7f1ea1be1dd7284f357924af12a705cc7d5c \ + --hash=sha256:be3964f7312ea05ed283b20f87cb533fdc555b2e428cc7be64612c0b2124f08c \ + --hash=sha256:be806e2961cd390a89d6c3ce8c2ae34271cfcd05660f716257838bb560f1c3b6 \ + --hash=sha256:bec77545d188f8bdd29d42bccb9191682a46fb2e655e3d1fb446d47c55ac3b8d \ + --hash=sha256:c10d92fb6d7fd827e44055fcd932ad93dac6a11e832d51534d77b97d1d85400f \ + --hash=sha256:c3782fb753aa825b4ccabc04292e07897e2fd941448eabf666856c5530277626 \ + --hash=sha256:c9ce7a9e967afc0a2af7caa0d15a3e9c1054815f73d6a8cb9225b61921b419bd \ + --hash=sha256:cb0702c12983be3b2fab98ead349ac63a98216d28dda6f518f52da5498a27a1b \ + --hash=sha256:cbc619e84a5e3ab2d452de831c88bdcad824414e9c2d28cd101f94dbdf26329c \ + --hash=sha256:ce4ed8e0c7dbc5b19352b9c2c6131dd23b95fa8698b5cdd076307a33626b72dc \ + --hash=sha256:ce96ab0bdfcef1b8c371ada2100767ace6804ea35aacce0aef3aeb4f3f499ca8 \ + --hash=sha256:cf824aceaeffff029ccfba0da637d432ca71ab21f13e7f6f5179cd88ebc77a8a \ + --hash=sha256:d2a81bdcfde4245468f7030a75a37d50400ac2455c3a4819d9d550c937f90ab5 \ + --hash=sha256:d2cc2b34f9e1d31ce255174da82902ad75bd7c0d88a33df54a77a22f2ef421ee \ + --hash=sha256:d2f184336bc1d6abfaaa1262ed42739c3789b1e3a65a29916a615307d22ffd2e \ + --hash=sha256:d3c622c39f04d5751408f5b801ecb527e6e0a471b367f420a877f7a660d583f6 \ + --hash=sha256:d7cf5e726b6fa977e428a61880fb108a62f28b6d0c7ef675b117eaff7076df49 \ + --hash=sha256:d85d784c619370d9329bbd670f41ff5f2ae62ea4519761b679d0f57f0f0ee267 \ + --hash=sha256:d93ebdb82363d2e7bec64eecdc3632b59e84bd270d74fe5be1659f7787052f9b \ + --hash=sha256:db8a6313dbac934193fc17fe7610f70cd8181c542a91382531bef5ed785e5615 \ + --hash=sha256:dbc2ab5d10544eb485baa76c63c501303b716a5c405ff2469a1d8ceffaabf622 \ + --hash=sha256:dbd749cff1defbde270ca346b69b3baf5f1297213ef322254bf2a28537f0b046 \ + --hash=sha256:dc662bc9375a6a394b62dfd331874c434819f10ee3902123200dbcf116963f89 \ + --hash=sha256:dc6b0d5a1ea0318ef2def2b6a55dccf1dcaf77d605672347271ed7b829860765 \ + --hash=sha256:dc79d192fb76fc0c84f2c58672c17bbbc383fd26c3cdc29daae16ce3d927e8b2 \ + --hash=sha256:dd2c1d27ebfe6a015cfa2005b7fe8c52d5019f7bbdd801bc6f7499aab9ae739e \ + --hash=sha256:dea0808153f1fbbad772669d906cddd92100277533a03845de6893cadeffc8be \ + --hash=sha256:e0d7151a1bd5d0a203a5008fc4ae51a159a610cb82ab0a9b2c4d80241745582e \ + --hash=sha256:e14aab02258cb776a108107bd15f5b5e4a1bbaa61ef33b36693dfab6f89d54f9 \ + --hash=sha256:e24d8031a2c62f34853756d9208eeafa6b940a1efcbfe36e8f57d99d52bb7261 \ + --hash=sha256:e36c80c49853b3ffda7aa1831bf175c13356b210c73128c861f3aa93c3cc4015 \ + --hash=sha256:e377e4cf8795cdbdff75b8f0223d7b6c68ff4fef36799d88ccf3a995a91c0112 \ + --hash=sha256:e3acb9c16530362aeaef4e84d57db357002dc5cbfac9a23414c3e73c08301ab2 \ + --hash=sha256:e3dc8d4ede2dbae6c0fc2b6c958bf51ce9fd7e9b40c0f5b8835c3fde44f5807d \ + --hash=sha256:e6491658dd2569f05860bad645569145c8626ac231877b0fb2d5f9bcb7054089 \ + --hash=sha256:eb91d252b35004a84670dfeafadb042528b19842a0080d8b53e5ec1128e8f433 \ + --hash=sha256:f0396e894bd1e66c74ecbc08b4f6a03dc331140942c4b1d345dd131b68574a60 \ + --hash=sha256:f09c9d4c26fa79c1bad927efb05aca2391350b8e61c38cbc0d7d3c814e463124 \ + --hash=sha256:f3cd110e02c5bf17d8fb562f6c9df5c20e73029d587cf8602a2da6c5ef1e32cb \ + --hash=sha256:f7a37dd208f0d658e0487522078b1ed68cd6bce20ef4b5a915d2809b9094b410 \ + --hash=sha256:fae4a01ef8c4cb2bbe92ef2063149596907dc4a881a8d26743b3f6b304713171 \ + --hash=sha256:fc327f4497b7087d06204235199daf208fd01c82d80465dc5efa4ec9df1c5b4e \ + --hash=sha256:fcc01c57ce6e70b728af02b2401c5bc853a9e14eb07deda30624374f0aebfe42 \ + --hash=sha256:fde355b02934cc6b07200cc3b27ab0c15870a757d1a72fd401aa92e2ea3c6bfe + # via referencing +ruff==0.5.5 \ + --hash=sha256:00817603822a3e42b80f7c3298c8269e09f889ee94640cd1fc7f9329788d7bf8 \ + --hash=sha256:187a60f555e9f865a2ff2c6984b9afeffa7158ba6e1eab56cb830404c942b0f3 \ + --hash=sha256:3191317d967af701f1b73a31ed5788795936e423b7acce82a2b63e26eb3e89d6 \ + --hash=sha256:3687d002f911e8a5faf977e619a034d159a8373514a587249cc00f211c67a091 \ + --hash=sha256:4ad25dd9c5faac95c8e9efb13e15803cd8bbf7f4600645a60ffe17c73f60779b \ + --hash=sha256:50f36d77f52d4c9c2f1361ccbfbd09099a1b2ea5d2b2222c586ab08885cf3445 \ + --hash=sha256:605d589ec35d1da9213a9d4d7e7a9c761d90bba78fc8790d1c5e65026c1b9eaf \ + --hash=sha256:696f18463b47a94575db635ebb4c178188645636f05e934fdf361b74edf1bb2d \ + --hash=sha256:a09b43e02f76ac0145f86a08e045e2ea452066f7ba064fd6b0cdccb486f7c3e7 \ + --hash=sha256:ac9dc814e510436e30d0ba535f435a7f3dc97f895f844f5b3f347ec8c228a523 \ + --hash=sha256:af9bdf6c389b5add40d89b201425b531e0a5cceb3cfdcc69f04d3d531c6be74f \ + --hash=sha256:cab904683bf9e2ecbbe9ff235bfe056f0eba754d0168ad5407832928d579e7ab \ + --hash=sha256:cc5516bdb4858d972fbc31d246bdb390eab8df1a26e2353be2dbc0c2d7f5421a \ + --hash=sha256:cfd7de17cef6ab559e9f5ab859f0d3296393bc78f69030967ca4d87a541b97a0 \ + --hash=sha256:d0b856cb19c60cd40198be5d8d4b556228e3dcd545b4f423d1ad812bfdca5884 \ + --hash=sha256:d40a8533ed545390ef8315b8e25c4bb85739b90bd0f3fe1280a29ae364cc55d8 \ + --hash=sha256:f70737c157d7edf749bcb952d13854e8f745cec695a01bdc6e29c29c288fc36e \ + --hash=sha256:fe26fc46fa8c6e0ae3f47ddccfbb136253c831c3289bba044befe68f467bfb16 + # via -r third_party/pip/requirements.in +six==1.17.0 \ + --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ + --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 + # via + # html5lib + # parse-type +sortedcontainers==2.4.0 \ + --hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \ + --hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0 + # via cyclonedx-python-lib +termcolor==2.0.0 \ + --hash=sha256:4889f2243b1da3934fc6cf4b57ee50a0ce98065ec06bfddac3fa6db24a9fcde2 \ + --hash=sha256:6e1a4b8e9c064ad8f9dfe2f9b35a7871d6ea5b6bb4f9da55bc0fd494b2302204 + # via -r third_party/pip/requirements.in +toml==0.10.2 \ + --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ + --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f + # via pip-audit +tomlkit==0.13.3 \ + --hash=sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1 \ + --hash=sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0 + # via pylint +types-certifi==2021.10.8.3 \ + --hash=sha256:72cf7798d165bc0b76e1c10dd1ea3097c7063c42c21d664523b928e88b554a4f \ + --hash=sha256:b2d1e325e69f71f7c78e5943d410e650b4707bb0ef32e4ddf3da37f54176e88a + # via -r third_party/pip/requirements.in +types-chardet==5.0.4.6 \ + --hash=sha256:caf4c74cd13ccfd8b3313c314aba943b159de562a2573ed03137402b2bb37818 \ + --hash=sha256:ea832d87e798abf1e4dfc73767807c2b7fee35d0003ae90348aea4ae00fb004d + # via -r third_party/pip/requirements.in +types-html5lib==1.1.11.20240806 \ + --hash=sha256:575c4fd84ba8eeeaa8520c7e4c7042b7791f5ec3e9c0a5d5c418124c42d9e7e4 \ + --hash=sha256:8060dc98baf63d6796a765bbbc809fff9f7a383f6e3a9add526f814c086545ef + # via -r third_party/pip/requirements.in +types-jsonschema==4.23.0.20240712 \ + --hash=sha256:8c33177ce95336241c1d61ccb56a9964d4361b99d5f1cd81a1ab4909b0dd7cf4 \ + --hash=sha256:b20db728dcf7ea3e80e9bdeb55e8b8420c6c040cda14e8cf284465adee71d217 + # via -r third_party/pip/requirements.in +types-python-dateutil==2.9.0.20240821 \ + --hash=sha256:9649d1dcb6fef1046fb18bebe9ea2aa0028b160918518c34589a46045f6ebd98 \ + --hash=sha256:f5889fcb4e63ed4aaa379b44f93c32593d50b9a94c9a60a0c854d8cc3511cd57 + # via -r third_party/pip/requirements.in +types-pyyaml==6.0.12.20240724 \ + --hash=sha256:cf7b31ae67e0c5b2919c703d2affc415485099d3fe6666a6912f040fd05cb67f \ + --hash=sha256:e5becec598f3aa3a2ddf671de4a75fa1c6856fbf73b2840286c9d50fae2d5d48 + # via -r third_party/pip/requirements.in +types-requests==2.32.0.20240712 \ + --hash=sha256:90c079ff05e549f6bf50e02e910210b98b8ff1ebdd18e19c873cd237737c1358 \ + --hash=sha256:f754283e152c752e46e70942fa2a146b5bc70393522257bb85bd1ef7e019dcc3 + # via -r third_party/pip/requirements.in +types-setuptools==72.2.0.20240821 \ + --hash=sha256:260e89d6d3b42cc35f9f0f382d030713b7b547344a664c05c9175e6ba124fac7 \ + --hash=sha256:e349b8015608879939f27ee370672f801287c46f5caa2d188d416336172c4965 + # via -r third_party/pip/requirements.in +types-six==1.16.21.20240513 \ + --hash=sha256:af2a105be6d504339bfed81319cc8e8697865f0ee5c6baa63658f127b33b9e63 \ + --hash=sha256:cdf445b5161bf17753500713a475ab79a45bd0d87728b8bfcecd86e2fbf66402 + # via -r third_party/pip/requirements.in +types-typed-ast==1.5.8.7 \ + --hash=sha256:97bdd9b4228f96c6904a76e10a050305ddadb529bd35e4d8234711e09c41b543 \ + --hash=sha256:f7795f6f9d597b35212314040b993f6613b51d81738edce3c1e3a3e9ef655124 + # via -r third_party/pip/requirements.in +types-urllib3==1.26.25.14 \ + --hash=sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f \ + --hash=sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e + # via -r third_party/pip/requirements.in +typing-extensions==4.14.1 \ + --hash=sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36 \ + --hash=sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76 + # via + # mypy + # pytest-bdd + # referencing +urllib3==2.5.0 \ + --hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \ + --hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc + # via + # requests + # types-requests +webencodings==0.5.1 \ + --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ + --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 + # via html5lib + +# The following packages are considered to be unsafe in a requirements file: +pip==25.2 \ + --hash=sha256:578283f006390f85bb6282dffb876454593d637f5d1be494b5202ce4877e71f2 \ + --hash=sha256:6d67a2b4e7f14d8b31b8b52648866fa717f45a1eb70e83002f4331d07e953717 + # via pip-api diff --git a/third_party/pip/requirements_lock_3_8.txt b/third_party/pip/requirements_lock_3_8.txt new file mode 100644 index 0000000..34e5ef8 --- /dev/null +++ b/third_party/pip/requirements_lock_3_8.txt @@ -0,0 +1,780 @@ +# +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: +# +# bazel run //third_party/pip:requirements_3_8.update +# +astroid==3.1.0 \ + --hash=sha256:951798f922990137ac090c53af473db7ab4e70c770e6d7fae0cec59f74411819 \ + --hash=sha256:ac248253bfa4bd924a0de213707e7ebeeb3138abeb48d798784ead1e56d419d4 + # via pylint +attrs==25.3.0 \ + --hash=sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3 \ + --hash=sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b + # via referencing +black==24.4.2 \ + --hash=sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474 \ + --hash=sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1 \ + --hash=sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0 \ + --hash=sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8 \ + --hash=sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96 \ + --hash=sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1 \ + --hash=sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04 \ + --hash=sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021 \ + --hash=sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94 \ + --hash=sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d \ + --hash=sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c \ + --hash=sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7 \ + --hash=sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c \ + --hash=sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc \ + --hash=sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7 \ + --hash=sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d \ + --hash=sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c \ + --hash=sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741 \ + --hash=sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce \ + --hash=sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb \ + --hash=sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063 \ + --hash=sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e + # via -r third_party/pip/requirements.in +boolean-py==5.0 \ + --hash=sha256:60cbc4bad079753721d32649545505362c754e121570ada4658b852a3a318d95 \ + --hash=sha256:ef28a70bd43115208441b53a045d1549e2f0ec6e3d08a9d142cbc41c1938e8d9 + # via license-expression +cachecontrol[filecache]==0.14.2 \ + --hash=sha256:7d47d19f866409b98ff6025b6a0fca8e4c791fb31abbd95f622093894ce903a2 \ + --hash=sha256:ebad2091bf12d0d200dfc2464330db638c5deb41d546f6d7aca079e87290f3b0 + # via + # cachecontrol + # pip-audit +certifi==2025.8.3 \ + --hash=sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407 \ + --hash=sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5 + # via requests +charset-normalizer==3.4.3 \ + --hash=sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91 \ + --hash=sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0 \ + --hash=sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154 \ + --hash=sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601 \ + --hash=sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884 \ + --hash=sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07 \ + --hash=sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c \ + --hash=sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64 \ + --hash=sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe \ + --hash=sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f \ + --hash=sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432 \ + --hash=sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc \ + --hash=sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa \ + --hash=sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9 \ + --hash=sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae \ + --hash=sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19 \ + --hash=sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d \ + --hash=sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e \ + --hash=sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4 \ + --hash=sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7 \ + --hash=sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312 \ + --hash=sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92 \ + --hash=sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31 \ + --hash=sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c \ + --hash=sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f \ + --hash=sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99 \ + --hash=sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b \ + --hash=sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15 \ + --hash=sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392 \ + --hash=sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f \ + --hash=sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8 \ + --hash=sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491 \ + --hash=sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0 \ + --hash=sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc \ + --hash=sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0 \ + --hash=sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f \ + --hash=sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a \ + --hash=sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40 \ + --hash=sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927 \ + --hash=sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849 \ + --hash=sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce \ + --hash=sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14 \ + --hash=sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05 \ + --hash=sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c \ + --hash=sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c \ + --hash=sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a \ + --hash=sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc \ + --hash=sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34 \ + --hash=sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9 \ + --hash=sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096 \ + --hash=sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14 \ + --hash=sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30 \ + --hash=sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b \ + --hash=sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b \ + --hash=sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942 \ + --hash=sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db \ + --hash=sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5 \ + --hash=sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b \ + --hash=sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce \ + --hash=sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669 \ + --hash=sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0 \ + --hash=sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018 \ + --hash=sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93 \ + --hash=sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe \ + --hash=sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049 \ + --hash=sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a \ + --hash=sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef \ + --hash=sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2 \ + --hash=sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca \ + --hash=sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16 \ + --hash=sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f \ + --hash=sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb \ + --hash=sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1 \ + --hash=sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557 \ + --hash=sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37 \ + --hash=sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7 \ + --hash=sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72 \ + --hash=sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c \ + --hash=sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9 + # via requests +click==8.1.8 \ + --hash=sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2 \ + --hash=sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a + # via black +colorama==0.4.6 \ + --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ + --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 + # via -r third_party/pip/requirements.in +coverage==7.5.1 \ + --hash=sha256:0646599e9b139988b63704d704af8e8df7fa4cbc4a1f33df69d97f36cb0a38de \ + --hash=sha256:0cdcbc320b14c3e5877ee79e649677cb7d89ef588852e9583e6b24c2e5072661 \ + --hash=sha256:0d0a0f5e06881ecedfe6f3dd2f56dcb057b6dbeb3327fd32d4b12854df36bf26 \ + --hash=sha256:1434e088b41594baa71188a17533083eabf5609e8e72f16ce8c186001e6b8c41 \ + --hash=sha256:16db7f26000a07efcf6aea00316f6ac57e7d9a96501e990a36f40c965ec7a95d \ + --hash=sha256:1cc0fe9b0b3a8364093c53b0b4c0c2dd4bb23acbec4c9240b5f284095ccf7981 \ + --hash=sha256:1fc81d5878cd6274ce971e0a3a18a8803c3fe25457165314271cf78e3aae3aa2 \ + --hash=sha256:2ec92012fefebee89a6b9c79bc39051a6cb3891d562b9270ab10ecfdadbc0c34 \ + --hash=sha256:39afcd3d4339329c5f58de48a52f6e4e50f6578dd6099961cf22228feb25f38f \ + --hash=sha256:4a7b0ceee8147444347da6a66be737c9d78f3353b0681715b668b72e79203e4a \ + --hash=sha256:4a9ca3f2fae0088c3c71d743d85404cec8df9be818a005ea065495bedc33da35 \ + --hash=sha256:4bf0655ab60d754491004a5efd7f9cccefcc1081a74c9ef2da4735d6ee4a6223 \ + --hash=sha256:4cc37def103a2725bc672f84bd939a6fe4522310503207aae4d56351644682f1 \ + --hash=sha256:4fc84a37bfd98db31beae3c2748811a3fa72bf2007ff7902f68746d9757f3746 \ + --hash=sha256:5037f8fcc2a95b1f0e80585bd9d1ec31068a9bcb157d9750a172836e98bc7a90 \ + --hash=sha256:54de9ef3a9da981f7af93eafde4ede199e0846cd819eb27c88e2b712aae9708c \ + --hash=sha256:556cf1a7cbc8028cb60e1ff0be806be2eded2daf8129b8811c63e2b9a6c43bca \ + --hash=sha256:57e0204b5b745594e5bc14b9b50006da722827f0b8c776949f1135677e88d0b8 \ + --hash=sha256:5a5740d1fb60ddf268a3811bcd353de34eb56dc24e8f52a7f05ee513b2d4f596 \ + --hash=sha256:5c3721c2c9e4c4953a41a26c14f4cef64330392a6d2d675c8b1db3b645e31f0e \ + --hash=sha256:5fa567e99765fe98f4e7d7394ce623e794d7cabb170f2ca2ac5a4174437e90dd \ + --hash=sha256:5fd215c0c7d7aab005221608a3c2b46f58c0285a819565887ee0b718c052aa4e \ + --hash=sha256:6175d1a0559986c6ee3f7fccfc4a90ecd12ba0a383dcc2da30c2b9918d67d8a3 \ + --hash=sha256:61c4bf1ba021817de12b813338c9be9f0ad5b1e781b9b340a6d29fc13e7c1b5e \ + --hash=sha256:6537e7c10cc47c595828b8a8be04c72144725c383c4702703ff4e42e44577312 \ + --hash=sha256:68f962d9b72ce69ea8621f57551b2fa9c70509af757ee3b8105d4f51b92b41a7 \ + --hash=sha256:7352b9161b33fd0b643ccd1f21f3a3908daaddf414f1c6cb9d3a2fd618bf2572 \ + --hash=sha256:796a79f63eca8814ca3317a1ea443645c9ff0d18b188de470ed7ccd45ae79428 \ + --hash=sha256:79afb6197e2f7f60c4824dd4b2d4c2ec5801ceb6ba9ce5d2c3080e5660d51a4f \ + --hash=sha256:7a588d39e0925f6a2bff87154752481273cdb1736270642aeb3635cb9b4cad07 \ + --hash=sha256:8748731ad392d736cc9ccac03c9845b13bb07d020a33423fa5b3a36521ac6e4e \ + --hash=sha256:8fe7502616b67b234482c3ce276ff26f39ffe88adca2acf0261df4b8454668b4 \ + --hash=sha256:9314d5678dcc665330df5b69c1e726a0e49b27df0461c08ca12674bcc19ef136 \ + --hash=sha256:9735317685ba6ec7e3754798c8871c2f49aa5e687cc794a0b1d284b2389d1bd5 \ + --hash=sha256:9981706d300c18d8b220995ad22627647be11a4276721c10911e0e9fa44c83e8 \ + --hash=sha256:9e78295f4144f9dacfed4f92935fbe1780021247c2fabf73a819b17f0ccfff8d \ + --hash=sha256:b016ea6b959d3b9556cb401c55a37547135a587db0115635a443b2ce8f1c7228 \ + --hash=sha256:b6cf3764c030e5338e7f61f95bd21147963cf6aa16e09d2f74f1fa52013c1206 \ + --hash=sha256:beccf7b8a10b09c4ae543582c1319c6df47d78fd732f854ac68d518ee1fb97fa \ + --hash=sha256:c0884920835a033b78d1c73b6d3bbcda8161a900f38a488829a83982925f6c2e \ + --hash=sha256:c3e757949f268364b96ca894b4c342b41dc6f8f8b66c37878aacef5930db61be \ + --hash=sha256:ca498687ca46a62ae590253fba634a1fe9836bc56f626852fb2720f334c9e4e5 \ + --hash=sha256:d1d0d98d95dd18fe29dc66808e1accf59f037d5716f86a501fc0256455219668 \ + --hash=sha256:d21918e9ef11edf36764b93101e2ae8cc82aa5efdc7c5a4e9c6c35a48496d601 \ + --hash=sha256:d7fed867ee50edf1a0b4a11e8e5d0895150e572af1cd6d315d557758bfa9c057 \ + --hash=sha256:db66fc317a046556a96b453a58eced5024af4582a8dbdc0c23ca4dbc0d5b3146 \ + --hash=sha256:dde0070c40ea8bb3641e811c1cfbf18e265d024deff6de52c5950677a8fb1e0f \ + --hash=sha256:df4e745a81c110e7446b1cc8131bf986157770fa405fe90e15e850aaf7619bc8 \ + --hash=sha256:e2213def81a50519d7cc56ed643c9e93e0247f5bbe0d1247d15fa520814a7cd7 \ + --hash=sha256:ef48e2707fb320c8f139424a596f5b69955a85b178f15af261bab871873bb987 \ + --hash=sha256:f152cbf5b88aaeb836127d920dd0f5e7edff5a66f10c079157306c4343d86c19 \ + --hash=sha256:fc0b4d8bfeabd25ea75e94632f5b6e047eef8adaed0c2161ada1e922e7f7cece + # via -r third_party/pip/requirements.in +cyclonedx-python-lib==6.4.4 \ + --hash=sha256:1b6f9109b6b9e91636dff822c2de90a05c0c8af120317713c1b879dbfdebdff8 \ + --hash=sha256:c366619cc4effd528675f1f7a7a00be30b6695ff03f49c64880ad15acbebc341 + # via pip-audit +defusedxml==0.7.1 \ + --hash=sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69 \ + --hash=sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61 + # via py-serializable +dill==0.4.0 \ + --hash=sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0 \ + --hash=sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049 + # via pylint +exceptiongroup==1.3.0 \ + --hash=sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10 \ + --hash=sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88 + # via pytest +filelock==3.16.1 \ + --hash=sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0 \ + --hash=sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435 + # via cachecontrol +html5lib==1.1 \ + --hash=sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d \ + --hash=sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f + # via pip-audit +idna==3.10 \ + --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ + --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 + # via requests +iniconfig==2.1.0 \ + --hash=sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7 \ + --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 + # via pytest +isort==5.13.2 \ + --hash=sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109 \ + --hash=sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6 + # via + # -r third_party/pip/requirements.in + # pylint +license-expression==30.3.1 \ + --hash=sha256:60d5bec1f3364c256a92b9a08583d7ea933c7aa272c8d36d04144a89a3858c01 \ + --hash=sha256:97904b9185c7bbb1e98799606fa7424191c375e70ba63a524b6f7100e42ddc46 + # via cyclonedx-python-lib +mako==1.3.10 \ + --hash=sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28 \ + --hash=sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59 + # via pytest-bdd +markdown-it-py==3.0.0 \ + --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ + --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb + # via rich +markupsafe==2.1.5 \ + --hash=sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf \ + --hash=sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff \ + --hash=sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f \ + --hash=sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3 \ + --hash=sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532 \ + --hash=sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f \ + --hash=sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617 \ + --hash=sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df \ + --hash=sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4 \ + --hash=sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906 \ + --hash=sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f \ + --hash=sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4 \ + --hash=sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8 \ + --hash=sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371 \ + --hash=sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2 \ + --hash=sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465 \ + --hash=sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52 \ + --hash=sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6 \ + --hash=sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169 \ + --hash=sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad \ + --hash=sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2 \ + --hash=sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0 \ + --hash=sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029 \ + --hash=sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f \ + --hash=sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a \ + --hash=sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced \ + --hash=sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5 \ + --hash=sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c \ + --hash=sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf \ + --hash=sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9 \ + --hash=sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb \ + --hash=sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad \ + --hash=sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3 \ + --hash=sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1 \ + --hash=sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46 \ + --hash=sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc \ + --hash=sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a \ + --hash=sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee \ + --hash=sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900 \ + --hash=sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5 \ + --hash=sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea \ + --hash=sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f \ + --hash=sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5 \ + --hash=sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e \ + --hash=sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a \ + --hash=sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f \ + --hash=sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50 \ + --hash=sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a \ + --hash=sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b \ + --hash=sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4 \ + --hash=sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff \ + --hash=sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2 \ + --hash=sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46 \ + --hash=sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b \ + --hash=sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf \ + --hash=sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5 \ + --hash=sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5 \ + --hash=sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab \ + --hash=sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd \ + --hash=sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68 + # via mako +mccabe==0.7.0 \ + --hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \ + --hash=sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e + # via pylint +mdurl==0.1.2 \ + --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ + --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba + # via markdown-it-py +msgpack==1.1.1 \ + --hash=sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8 \ + --hash=sha256:1abfc6e949b352dadf4bce0eb78023212ec5ac42f6abfd469ce91d783c149c2a \ + --hash=sha256:1b13fe0fb4aac1aa5320cd693b297fe6fdef0e7bea5518cbc2dd5299f873ae90 \ + --hash=sha256:1d75f3807a9900a7d575d8d6674a3a47e9f227e8716256f35bc6f03fc597ffbf \ + --hash=sha256:2fbbc0b906a24038c9958a1ba7ae0918ad35b06cb449d398b76a7d08470b0ed9 \ + --hash=sha256:33be9ab121df9b6b461ff91baac6f2731f83d9b27ed948c5b9d1978ae28bf157 \ + --hash=sha256:353b6fc0c36fde68b661a12949d7d49f8f51ff5fa019c1e47c87c4ff34b080ed \ + --hash=sha256:36043272c6aede309d29d56851f8841ba907a1a3d04435e43e8a19928e243c1d \ + --hash=sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0 \ + --hash=sha256:3a89cd8c087ea67e64844287ea52888239cbd2940884eafd2dcd25754fb72232 \ + --hash=sha256:40eae974c873b2992fd36424a5d9407f93e97656d999f43fca9d29f820899084 \ + --hash=sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5 \ + --hash=sha256:435807eeb1bc791ceb3247d13c79868deb22184e1fc4224808750f0d7d1affc1 \ + --hash=sha256:4835d17af722609a45e16037bb1d4d78b7bdf19d6c0128116d178956618c4e88 \ + --hash=sha256:4a28e8072ae9779f20427af07f53bbb8b4aa81151054e882aee333b158da8752 \ + --hash=sha256:4d3237b224b930d58e9d83c81c0dba7aacc20fcc2f89c1e5423aa0529a4cd142 \ + --hash=sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac \ + --hash=sha256:4fd6b577e4541676e0cc9ddc1709d25014d3ad9a66caa19962c4f5de30fc09ef \ + --hash=sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323 \ + --hash=sha256:5692095123007180dca3e788bb4c399cc26626da51629a31d40207cb262e67f4 \ + --hash=sha256:5fd1b58e1431008a57247d6e7cc4faa41c3607e8e7d4aaf81f7c29ea013cb458 \ + --hash=sha256:61abccf9de335d9efd149e2fff97ed5974f2481b3353772e8e2dd3402ba2bd57 \ + --hash=sha256:61e35a55a546a1690d9d09effaa436c25ae6130573b6ee9829c37ef0f18d5e78 \ + --hash=sha256:6640fd979ca9a212e4bcdf6eb74051ade2c690b862b679bfcb60ae46e6dc4bfd \ + --hash=sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69 \ + --hash=sha256:6f64ae8fe7ffba251fecb8408540c34ee9df1c26674c50c4544d72dbf792e5ce \ + --hash=sha256:71ef05c1726884e44f8b1d1773604ab5d4d17729d8491403a705e649116c9558 \ + --hash=sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd \ + --hash=sha256:78426096939c2c7482bf31ef15ca219a9e24460289c00dd0b94411040bb73ad2 \ + --hash=sha256:79c408fcf76a958491b4e3b103d1c417044544b68e96d06432a189b43d1215c8 \ + --hash=sha256:7a17ac1ea6ec3c7687d70201cfda3b1e8061466f28f686c24f627cae4ea8efd0 \ + --hash=sha256:7da8831f9a0fdb526621ba09a281fadc58ea12701bc709e7b8cbc362feabc295 \ + --hash=sha256:870b9a626280c86cff9c576ec0d9cbcc54a1e5ebda9cd26dab12baf41fee218c \ + --hash=sha256:88d1e966c9235c1d4e2afac21ca83933ba59537e2e2727a999bf3f515ca2af26 \ + --hash=sha256:88daaf7d146e48ec71212ce21109b66e06a98e5e44dca47d853cbfe171d6c8d2 \ + --hash=sha256:8a8b10fdb84a43e50d38057b06901ec9da52baac6983d3f709d8507f3889d43f \ + --hash=sha256:8b17ba27727a36cb73aabacaa44b13090feb88a01d012c0f4be70c00f75048b4 \ + --hash=sha256:8b65b53204fe1bd037c40c4148d00ef918eb2108d24c9aaa20bc31f9810ce0a8 \ + --hash=sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9 \ + --hash=sha256:96decdfc4adcbc087f5ea7ebdcfd3dee9a13358cae6e81d54be962efc38f6338 \ + --hash=sha256:996f2609ddf0142daba4cefd767d6db26958aac8439ee41db9cc0db9f4c4c3a6 \ + --hash=sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a \ + --hash=sha256:a32747b1b39c3ac27d0670122b57e6e57f28eefb725e0b625618d1b59bf9d1e0 \ + --hash=sha256:a494554874691720ba5891c9b0b39474ba43ffb1aaf32a5dac874effb1619e1a \ + --hash=sha256:a8ef6e342c137888ebbfb233e02b8fbd689bb5b5fcc59b34711ac47ebd504478 \ + --hash=sha256:ae497b11f4c21558d95de9f64fff7053544f4d1a17731c866143ed6bb4591238 \ + --hash=sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7 \ + --hash=sha256:b8f93dcddb243159c9e4109c9750ba5b335ab8d48d9522c5308cd05d7e3ce600 \ + --hash=sha256:ba0c325c3f485dc54ec298d8b024e134acf07c10d494ffa24373bea729acf704 \ + --hash=sha256:bb29aaa613c0a1c40d1af111abf025f1732cab333f96f285d6a93b934738a68a \ + --hash=sha256:bba1be28247e68994355e028dcd668316db30c1f758d3241a7b903ac78dcd285 \ + --hash=sha256:cb643284ab0ed26f6957d969fe0dd8bb17beb567beb8998140b5e38a90974f6c \ + --hash=sha256:d182dac0221eb8faef2e6f44701812b467c02674a322c739355c39e94730cdbf \ + --hash=sha256:d275a9e3c81b1093c060c3837e580c37f47c51eca031f7b5fb76f7b8470f5f9b \ + --hash=sha256:d8b55ea20dc59b181d3f47103f113e6f28a5e1c89fd5b67b9140edb442ab67f2 \ + --hash=sha256:da8f41e602574ece93dbbda1fab24650d6bf2a24089f9e9dbb4f5730ec1e58ad \ + --hash=sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b \ + --hash=sha256:f5be6b6bc52fad84d010cb45433720327ce886009d862f46b26d4d154001994b \ + --hash=sha256:f6d58656842e1b2ddbe07f43f56b10a60f2ba5826164910968f5933e5178af75 + # via cachecontrol +mypy==1.11.1 \ + --hash=sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54 \ + --hash=sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a \ + --hash=sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72 \ + --hash=sha256:11965c2f571ded6239977b14deebd3f4c3abd9a92398712d6da3a772974fad69 \ + --hash=sha256:1a81cf05975fd61aec5ae16501a091cfb9f605dc3e3c878c0da32f250b74760b \ + --hash=sha256:2684d3f693073ab89d76da8e3921883019ea8a3ec20fa5d8ecca6a2db4c54bbe \ + --hash=sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4 \ + --hash=sha256:45df906e8b6804ef4b666af29a87ad9f5921aad091c79cc38e12198e220beabd \ + --hash=sha256:4c956b49c5d865394d62941b109728c5c596a415e9c5b2be663dd26a1ff07bc0 \ + --hash=sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525 \ + --hash=sha256:749fd3213916f1751fff995fccf20c6195cae941dc968f3aaadf9bb4e430e5a2 \ + --hash=sha256:79c07eb282cb457473add5052b63925e5cc97dfab9812ee65a7c7ab5e3cb551c \ + --hash=sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5 \ + --hash=sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de \ + --hash=sha256:a2b43895a0f8154df6519706d9bca8280cda52d3d9d1514b2d9c3e26792a0b74 \ + --hash=sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c \ + --hash=sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e \ + --hash=sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58 \ + --hash=sha256:b639dce63a0b19085213ec5fdd8cffd1d81988f47a2dec7100e93564f3e8fb3b \ + --hash=sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417 \ + --hash=sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411 \ + --hash=sha256:d44be7551689d9d47b7abc27c71257adfdb53f03880841a5db15ddb22dc63edb \ + --hash=sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03 \ + --hash=sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca \ + --hash=sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8 \ + --hash=sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08 \ + --hash=sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809 + # via -r third_party/pip/requirements.in +mypy-extensions==1.1.0 \ + --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \ + --hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558 + # via + # black + # mypy +packageurl-python==0.17.5 \ + --hash=sha256:a7be3f3ba70d705f738ace9bf6124f31920245a49fa69d4b416da7037dd2de61 \ + --hash=sha256:f0e55452ab37b5c192c443de1458e3f3b4d8ac27f747df6e8c48adeab081d321 + # via cyclonedx-python-lib +packaging==25.0 \ + --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ + --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f + # via + # black + # pip-audit + # pip-requirements-parser + # pytest + # pytest-bdd + # pytest-rerunfailures +parse==1.20.2 \ + --hash=sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558 \ + --hash=sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce + # via + # parse-type + # pytest-bdd +parse-type==0.6.6 \ + --hash=sha256:3ca79bbe71e170dfccc8ec6c341edfd1c2a0fc1e5cfd18330f93af938de2348c \ + --hash=sha256:513a3784104839770d690e04339a8b4d33439fcd5dd99f2e4580f9fc1097bfb2 + # via pytest-bdd +pathspec==0.12.1 \ + --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ + --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712 + # via black +pip-api==0.0.34 \ + --hash=sha256:8b2d7d7c37f2447373aa2cf8b1f60a2f2b27a84e1e9e0294a3f6ef10eb3ba6bb \ + --hash=sha256:9b75e958f14c5a2614bae415f2adf7eeb54d50a2cfbe7e24fd4826471bac3625 + # via pip-audit +pip-audit==2.7.0 \ + --hash=sha256:67740c5b1d5d967a258c3dfefc46f9713a2819c48062505ddf4b29de101c2b75 \ + --hash=sha256:83e039740653eb9ef1a78b1540ed441600cd88a560588ba2c0a169180685a522 + # via -r third_party/pip/requirements.in +pip-requirements-parser==32.0.1 \ + --hash=sha256:4659bc2a667783e7a15d190f6fccf8b2486685b6dba4c19c3876314769c57526 \ + --hash=sha256:b4fa3a7a0be38243123cf9d1f3518da10c51bdb165a2b2985566247f9155a7d3 + # via pip-audit +platformdirs==4.3.6 \ + --hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \ + --hash=sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb + # via + # black + # pylint +pluggy==1.5.0 \ + --hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \ + --hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669 + # via pytest +py-cpuinfo==9.0.0 \ + --hash=sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690 \ + --hash=sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5 + # via pytest-benchmark +py-serializable==1.1.2 \ + --hash=sha256:801be61b0a1ba64c3861f7c624f1de5cfbbabf8b458acc9cdda91e8f7e5effa1 \ + --hash=sha256:89af30bc319047d4aa0d8708af412f6ce73835e18bacf1a080028bb9e2f42bdb + # via cyclonedx-python-lib +pygments==2.19.2 \ + --hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \ + --hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b + # via rich +pylint==3.1.0 \ + --hash=sha256:507a5b60953874766d8a366e8e8c7af63e058b26345cfcb5f91f89d987fd6b74 \ + --hash=sha256:6a69beb4a6f63debebaab0a3477ecd0f559aa726af4954fc948c51f7a2549e23 + # via -r third_party/pip/requirements.in +pyparsing==3.1.4 \ + --hash=sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c \ + --hash=sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032 + # via pip-requirements-parser +pytest==8.1.1 \ + --hash=sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7 \ + --hash=sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044 + # via + # -r third_party/pip/requirements.in + # pytest-bdd + # pytest-benchmark + # pytest-mock + # pytest-rerunfailures + # pytest-timeout +pytest-bdd==7.1.1 \ + --hash=sha256:1b41a10a8391f1849f0a1524d77b2991d0d8042d2253ace9b616b89b4a9b97a7 \ + --hash=sha256:331621122a46f9881110ae0dd43fedb29e96e8202de29256be3d76c53b57c1d2 + # via -r third_party/pip/requirements.in +pytest-benchmark==4.0.0 \ + --hash=sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1 \ + --hash=sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6 + # via -r third_party/pip/requirements.in +pytest-mock==3.12.0 \ + --hash=sha256:0972719a7263072da3a21c7f4773069bcc7486027d7e8e1f81d98a47e701bc4f \ + --hash=sha256:31a40f038c22cad32287bb43932054451ff5583ff094bca6f675df2f8bc1a6e9 + # via -r third_party/pip/requirements.in +pytest-rerunfailures==14.0 \ + --hash=sha256:4197bdd2eaeffdbf50b5ea6e7236f47ff0e44d1def8dae08e409f536d84e7b32 \ + --hash=sha256:4a400bcbcd3c7a4ad151ab8afac123d90eca3abe27f98725dc4d9702887d2e92 + # via -r third_party/pip/requirements.in +pytest-timeout==2.3.1 \ + --hash=sha256:12397729125c6ecbdaca01035b9e5239d4db97352320af155b3f5de1ba5165d9 \ + --hash=sha256:68188cb703edfc6a18fad98dc25a3c61e9f24d644b0b70f33af545219fc7813e + # via -r third_party/pip/requirements.in +referencing==0.35.1 \ + --hash=sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c \ + --hash=sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de + # via types-jsonschema +requests==2.32.4 \ + --hash=sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c \ + --hash=sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422 + # via + # cachecontrol + # pip-audit +rich==14.1.0 \ + --hash=sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f \ + --hash=sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8 + # via pip-audit +rpds-py==0.20.1 \ + --hash=sha256:02a0629ec053fc013808a85178524e3cb63a61dbc35b22499870194a63578fb9 \ + --hash=sha256:07924c1b938798797d60c6308fa8ad3b3f0201802f82e4a2c41bb3fafb44cc28 \ + --hash=sha256:07f59760ef99f31422c49038964b31c4dfcfeb5d2384ebfc71058a7c9adae2d2 \ + --hash=sha256:0a3a1e9ee9728b2c1734f65d6a1d376c6f2f6fdcc13bb007a08cc4b1ff576dc5 \ + --hash=sha256:0a90c373ea2975519b58dece25853dbcb9779b05cc46b4819cb1917e3b3215b6 \ + --hash=sha256:0ad56edabcdb428c2e33bbf24f255fe2b43253b7d13a2cdbf05de955217313e6 \ + --hash=sha256:0b581f47257a9fce535c4567782a8976002d6b8afa2c39ff616edf87cbeff712 \ + --hash=sha256:0f8f741b6292c86059ed175d80eefa80997125b7c478fb8769fd9ac8943a16c0 \ + --hash=sha256:0fc212779bf8411667234b3cdd34d53de6c2b8b8b958e1e12cb473a5f367c338 \ + --hash=sha256:13c56de6518e14b9bf6edde23c4c39dac5b48dcf04160ea7bce8fca8397cdf86 \ + --hash=sha256:142c0a5124d9bd0e2976089484af5c74f47bd3298f2ed651ef54ea728d2ea42c \ + --hash=sha256:14511a539afee6f9ab492b543060c7491c99924314977a55c98bfa2ee29ce78c \ + --hash=sha256:15a842bb369e00295392e7ce192de9dcbf136954614124a667f9f9f17d6a216f \ + --hash=sha256:16d4477bcb9fbbd7b5b0e4a5d9b493e42026c0bf1f06f723a9353f5153e75d30 \ + --hash=sha256:1791ff70bc975b098fe6ecf04356a10e9e2bd7dc21fa7351c1742fdeb9b4966f \ + --hash=sha256:19b73643c802f4eaf13d97f7855d0fb527fbc92ab7013c4ad0e13a6ae0ed23bd \ + --hash=sha256:200a23239781f46149e6a415f1e870c5ef1e712939fe8fa63035cd053ac2638e \ + --hash=sha256:2249280b870e6a42c0d972339e9cc22ee98730a99cd7f2f727549af80dd5a963 \ + --hash=sha256:2b431c777c9653e569986ecf69ff4a5dba281cded16043d348bf9ba505486f36 \ + --hash=sha256:2cc3712a4b0b76a1d45a9302dd2f53ff339614b1c29603a911318f2357b04dd2 \ + --hash=sha256:2fbb0ffc754490aff6dabbf28064be47f0f9ca0b9755976f945214965b3ace7e \ + --hash=sha256:32b922e13d4c0080d03e7b62991ad7f5007d9cd74e239c4b16bc85ae8b70252d \ + --hash=sha256:36785be22066966a27348444b40389f8444671630063edfb1a2eb04318721e17 \ + --hash=sha256:37fe0f12aebb6a0e3e17bb4cd356b1286d2d18d2e93b2d39fe647138458b4bcb \ + --hash=sha256:3aea7eed3e55119635a74bbeb80b35e776bafccb70d97e8ff838816c124539f1 \ + --hash=sha256:3c6afcf2338e7f374e8edc765c79fbcb4061d02b15dd5f8f314a4af2bdc7feb5 \ + --hash=sha256:3ccb8ac2d3c71cda472b75af42818981bdacf48d2e21c36331b50b4f16930163 \ + --hash=sha256:3d089d0b88996df627693639d123c8158cff41c0651f646cd8fd292c7da90eaf \ + --hash=sha256:3dd645e2b0dcb0fd05bf58e2e54c13875847687d0b71941ad2e757e5d89d4356 \ + --hash=sha256:3e310838a5801795207c66c73ea903deda321e6146d6f282e85fa7e3e4854804 \ + --hash=sha256:42cbde7789f5c0bcd6816cb29808e36c01b960fb5d29f11e052215aa85497c93 \ + --hash=sha256:483b29f6f7ffa6af845107d4efe2e3fa8fb2693de8657bc1849f674296ff6a5a \ + --hash=sha256:4888e117dd41b9d34194d9e31631af70d3d526efc363085e3089ab1a62c32ed1 \ + --hash=sha256:49fe9b04b6fa685bd39237d45fad89ba19e9163a1ccaa16611a812e682913496 \ + --hash=sha256:4a5a844f68776a7715ecb30843b453f07ac89bad393431efbf7accca3ef599c1 \ + --hash=sha256:4a916087371afd9648e1962e67403c53f9c49ca47b9680adbeef79da3a7811b0 \ + --hash=sha256:4f676e21db2f8c72ff0936f895271e7a700aa1f8d31b40e4e43442ba94973899 \ + --hash=sha256:518d2ca43c358929bf08f9079b617f1c2ca6e8848f83c1225c88caeac46e6cbc \ + --hash=sha256:5265505b3d61a0f56618c9b941dc54dc334dc6e660f1592d112cd103d914a6db \ + --hash=sha256:55cd1fa4ecfa6d9f14fbd97ac24803e6f73e897c738f771a9fe038f2f11ff07c \ + --hash=sha256:58b1d5dd591973d426cbb2da5e27ba0339209832b2f3315928c9790e13f159e8 \ + --hash=sha256:59240685e7da61fb78f65a9f07f8108e36a83317c53f7b276b4175dc44151684 \ + --hash=sha256:5b48e790e0355865197ad0aca8cde3d8ede347831e1959e158369eb3493d2191 \ + --hash=sha256:5d4eea0761e37485c9b81400437adb11c40e13ef513375bbd6973e34100aeb06 \ + --hash=sha256:648386ddd1e19b4a6abab69139b002bc49ebf065b596119f8f37c38e9ecee8ff \ + --hash=sha256:653647b8838cf83b2e7e6a0364f49af96deec64d2a6578324db58380cff82aca \ + --hash=sha256:6740a3e8d43a32629bb9b009017ea5b9e713b7210ba48ac8d4cb6d99d86c8ee8 \ + --hash=sha256:6889469bfdc1eddf489729b471303739bf04555bb151fe8875931f8564309afc \ + --hash=sha256:68cb0a499f2c4a088fd2f521453e22ed3527154136a855c62e148b7883b99f9a \ + --hash=sha256:6aa97af1558a9bef4025f8f5d8c60d712e0a3b13a2fe875511defc6ee77a1ab7 \ + --hash=sha256:6b73c67850ca7cae0f6c56f71e356d7e9fa25958d3e18a64927c2d930859b8e4 \ + --hash=sha256:6c8e9340ce5a52f95fa7d3b552b35c7e8f3874d74a03a8a69279fd5fca5dc751 \ + --hash=sha256:6ca91093a4a8da4afae7fe6a222c3b53ee4eef433ebfee4d54978a103435159e \ + --hash=sha256:754bbed1a4ca48479e9d4182a561d001bbf81543876cdded6f695ec3d465846b \ + --hash=sha256:762703bdd2b30983c1d9e62b4c88664df4a8a4d5ec0e9253b0231171f18f6d75 \ + --hash=sha256:78f0b6877bfce7a3d1ff150391354a410c55d3cdce386f862926a4958ad5ab7e \ + --hash=sha256:7a07ced2b22f0cf0b55a6a510078174c31b6d8544f3bc00c2bcee52b3d613f74 \ + --hash=sha256:7dca7081e9a0c3b6490a145593f6fe3173a94197f2cb9891183ef75e9d64c425 \ + --hash=sha256:7e21b7031e17c6b0e445f42ccc77f79a97e2687023c5746bfb7a9e45e0921b84 \ + --hash=sha256:7f5179583d7a6cdb981151dd349786cbc318bab54963a192692d945dd3f6435d \ + --hash=sha256:83cba698cfb3c2c5a7c3c6bac12fe6c6a51aae69513726be6411076185a8b24a \ + --hash=sha256:842c19a6ce894493563c3bd00d81d5100e8e57d70209e84d5491940fdb8b9e3a \ + --hash=sha256:84b8382a90539910b53a6307f7c35697bc7e6ffb25d9c1d4e998a13e842a5e83 \ + --hash=sha256:8ba6f89cac95c0900d932c9efb7f0fb6ca47f6687feec41abcb1bd5e2bd45535 \ + --hash=sha256:8bbe951244a838a51289ee53a6bae3a07f26d4e179b96fc7ddd3301caf0518eb \ + --hash=sha256:925d176a549f4832c6f69fa6026071294ab5910e82a0fe6c6228fce17b0706bd \ + --hash=sha256:92b68b79c0da2a980b1c4197e56ac3dd0c8a149b4603747c4378914a68706979 \ + --hash=sha256:93da1d3db08a827eda74356f9f58884adb254e59b6664f64cc04cdff2cc19b0d \ + --hash=sha256:95f3b65d2392e1c5cec27cff08fdc0080270d5a1a4b2ea1d51d5f4a2620ff08d \ + --hash=sha256:9c4cb04a16b0f199a8c9bf807269b2f63b7b5b11425e4a6bd44bd6961d28282c \ + --hash=sha256:a624cc00ef2158e04188df5e3016385b9353638139a06fb77057b3498f794782 \ + --hash=sha256:a649dfd735fff086e8a9d0503a9f0c7d01b7912a333c7ae77e1515c08c146dad \ + --hash=sha256:a94e52537a0e0a85429eda9e49f272ada715506d3b2431f64b8a3e34eb5f3e75 \ + --hash=sha256:aa7ac11e294304e615b43f8c441fee5d40094275ed7311f3420d805fde9b07b4 \ + --hash=sha256:b41b6321805c472f66990c2849e152aff7bc359eb92f781e3f606609eac877ad \ + --hash=sha256:b71b8666eeea69d6363248822078c075bac6ed135faa9216aa85f295ff009b1e \ + --hash=sha256:b9c2fe36d1f758b28121bef29ed1dee9b7a2453e997528e7d1ac99b94892527c \ + --hash=sha256:bb63804105143c7e24cee7db89e37cb3f3941f8e80c4379a0b355c52a52b6780 \ + --hash=sha256:be5ef2f1fc586a7372bfc355986226484e06d1dc4f9402539872c8bb99e34b01 \ + --hash=sha256:c142b88039b92e7e0cb2552e8967077e3179b22359e945574f5e2764c3953dcf \ + --hash=sha256:c14937af98c4cc362a1d4374806204dd51b1e12dded1ae30645c298e5a5c4cb1 \ + --hash=sha256:ca449520e7484534a2a44faf629362cae62b660601432d04c482283c47eaebab \ + --hash=sha256:cd945871335a639275eee904caef90041568ce3b42f402c6959b460d25ae8732 \ + --hash=sha256:d0b937b2a1988f184a3e9e577adaa8aede21ec0b38320d6009e02bd026db04fa \ + --hash=sha256:d126b52e4a473d40232ec2052a8b232270ed1f8c9571aaf33f73a14cc298c24f \ + --hash=sha256:d8761c3c891cc51e90bc9926d6d2f59b27beaf86c74622c8979380a29cc23ac3 \ + --hash=sha256:d9ecb51120de61e4604650666d1f2b68444d46ae18fd492245a08f53ad2b7711 \ + --hash=sha256:da584ff96ec95e97925174eb8237e32f626e7a1a97888cdd27ee2f1f24dd0ad8 \ + --hash=sha256:dbcf360c9e3399b056a238523146ea77eeb2a596ce263b8814c900263e46031a \ + --hash=sha256:dbddc10776ca7ebf2a299c41a4dde8ea0d8e3547bfd731cb87af2e8f5bf8962d \ + --hash=sha256:dc73505153798c6f74854aba69cc75953888cf9866465196889c7cdd351e720c \ + --hash=sha256:e13de156137b7095442b288e72f33503a469aa1980ed856b43c353ac86390519 \ + --hash=sha256:e1791c4aabd117653530dccd24108fa03cc6baf21f58b950d0a73c3b3b29a350 \ + --hash=sha256:e75ba609dba23f2c95b776efb9dd3f0b78a76a151e96f96cc5b6b1b0004de66f \ + --hash=sha256:e79059d67bea28b53d255c1437b25391653263f0e69cd7dec170d778fdbca95e \ + --hash=sha256:ecd27a66740ffd621d20b9a2f2b5ee4129a56e27bfb9458a3bcc2e45794c96cb \ + --hash=sha256:f009c69bc8c53db5dfab72ac760895dc1f2bc1b62ab7408b253c8d1ec52459fc \ + --hash=sha256:f16bc1334853e91ddaaa1217045dd7be166170beec337576818461268a3de67f \ + --hash=sha256:f19169781dddae7478a32301b499b2858bc52fc45a112955e798ee307e294977 \ + --hash=sha256:fa3060d885657abc549b2a0f8e1b79699290e5d83845141717c6c90c2df38311 \ + --hash=sha256:fa41a64ac5b08b292906e248549ab48b69c5428f3987b09689ab2441f267d04d \ + --hash=sha256:fbf15aff64a163db29a91ed0868af181d6f68ec1a3a7d5afcfe4501252840bad \ + --hash=sha256:fe00a9057d100e69b4ae4a094203a708d65b0f345ed546fdef86498bf5390982 + # via referencing +ruff==0.5.5 \ + --hash=sha256:00817603822a3e42b80f7c3298c8269e09f889ee94640cd1fc7f9329788d7bf8 \ + --hash=sha256:187a60f555e9f865a2ff2c6984b9afeffa7158ba6e1eab56cb830404c942b0f3 \ + --hash=sha256:3191317d967af701f1b73a31ed5788795936e423b7acce82a2b63e26eb3e89d6 \ + --hash=sha256:3687d002f911e8a5faf977e619a034d159a8373514a587249cc00f211c67a091 \ + --hash=sha256:4ad25dd9c5faac95c8e9efb13e15803cd8bbf7f4600645a60ffe17c73f60779b \ + --hash=sha256:50f36d77f52d4c9c2f1361ccbfbd09099a1b2ea5d2b2222c586ab08885cf3445 \ + --hash=sha256:605d589ec35d1da9213a9d4d7e7a9c761d90bba78fc8790d1c5e65026c1b9eaf \ + --hash=sha256:696f18463b47a94575db635ebb4c178188645636f05e934fdf361b74edf1bb2d \ + --hash=sha256:a09b43e02f76ac0145f86a08e045e2ea452066f7ba064fd6b0cdccb486f7c3e7 \ + --hash=sha256:ac9dc814e510436e30d0ba535f435a7f3dc97f895f844f5b3f347ec8c228a523 \ + --hash=sha256:af9bdf6c389b5add40d89b201425b531e0a5cceb3cfdcc69f04d3d531c6be74f \ + --hash=sha256:cab904683bf9e2ecbbe9ff235bfe056f0eba754d0168ad5407832928d579e7ab \ + --hash=sha256:cc5516bdb4858d972fbc31d246bdb390eab8df1a26e2353be2dbc0c2d7f5421a \ + --hash=sha256:cfd7de17cef6ab559e9f5ab859f0d3296393bc78f69030967ca4d87a541b97a0 \ + --hash=sha256:d0b856cb19c60cd40198be5d8d4b556228e3dcd545b4f423d1ad812bfdca5884 \ + --hash=sha256:d40a8533ed545390ef8315b8e25c4bb85739b90bd0f3fe1280a29ae364cc55d8 \ + --hash=sha256:f70737c157d7edf749bcb952d13854e8f745cec695a01bdc6e29c29c288fc36e \ + --hash=sha256:fe26fc46fa8c6e0ae3f47ddccfbb136253c831c3289bba044befe68f467bfb16 + # via -r third_party/pip/requirements.in +six==1.17.0 \ + --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ + --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 + # via + # html5lib + # parse-type +sortedcontainers==2.4.0 \ + --hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \ + --hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0 + # via cyclonedx-python-lib +termcolor==2.0.0 \ + --hash=sha256:4889f2243b1da3934fc6cf4b57ee50a0ce98065ec06bfddac3fa6db24a9fcde2 \ + --hash=sha256:6e1a4b8e9c064ad8f9dfe2f9b35a7871d6ea5b6bb4f9da55bc0fd494b2302204 + # via -r third_party/pip/requirements.in +toml==0.10.2 \ + --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ + --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f + # via pip-audit +tomli==2.2.1 \ + --hash=sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6 \ + --hash=sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd \ + --hash=sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c \ + --hash=sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b \ + --hash=sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8 \ + --hash=sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6 \ + --hash=sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77 \ + --hash=sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff \ + --hash=sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea \ + --hash=sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192 \ + --hash=sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249 \ + --hash=sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee \ + --hash=sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4 \ + --hash=sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98 \ + --hash=sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8 \ + --hash=sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4 \ + --hash=sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281 \ + --hash=sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744 \ + --hash=sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69 \ + --hash=sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13 \ + --hash=sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140 \ + --hash=sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e \ + --hash=sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e \ + --hash=sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc \ + --hash=sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff \ + --hash=sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec \ + --hash=sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2 \ + --hash=sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222 \ + --hash=sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106 \ + --hash=sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272 \ + --hash=sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a \ + --hash=sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7 + # via + # black + # mypy + # pylint + # pytest +tomlkit==0.13.3 \ + --hash=sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1 \ + --hash=sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0 + # via pylint +types-certifi==2021.10.8.3 \ + --hash=sha256:72cf7798d165bc0b76e1c10dd1ea3097c7063c42c21d664523b928e88b554a4f \ + --hash=sha256:b2d1e325e69f71f7c78e5943d410e650b4707bb0ef32e4ddf3da37f54176e88a + # via -r third_party/pip/requirements.in +types-chardet==5.0.4.6 \ + --hash=sha256:caf4c74cd13ccfd8b3313c314aba943b159de562a2573ed03137402b2bb37818 \ + --hash=sha256:ea832d87e798abf1e4dfc73767807c2b7fee35d0003ae90348aea4ae00fb004d + # via -r third_party/pip/requirements.in +types-html5lib==1.1.11.20240806 \ + --hash=sha256:575c4fd84ba8eeeaa8520c7e4c7042b7791f5ec3e9c0a5d5c418124c42d9e7e4 \ + --hash=sha256:8060dc98baf63d6796a765bbbc809fff9f7a383f6e3a9add526f814c086545ef + # via -r third_party/pip/requirements.in +types-jsonschema==4.23.0.20240712 \ + --hash=sha256:8c33177ce95336241c1d61ccb56a9964d4361b99d5f1cd81a1ab4909b0dd7cf4 \ + --hash=sha256:b20db728dcf7ea3e80e9bdeb55e8b8420c6c040cda14e8cf284465adee71d217 + # via -r third_party/pip/requirements.in +types-python-dateutil==2.9.0.20240821 \ + --hash=sha256:9649d1dcb6fef1046fb18bebe9ea2aa0028b160918518c34589a46045f6ebd98 \ + --hash=sha256:f5889fcb4e63ed4aaa379b44f93c32593d50b9a94c9a60a0c854d8cc3511cd57 + # via -r third_party/pip/requirements.in +types-pyyaml==6.0.12.20240724 \ + --hash=sha256:cf7b31ae67e0c5b2919c703d2affc415485099d3fe6666a6912f040fd05cb67f \ + --hash=sha256:e5becec598f3aa3a2ddf671de4a75fa1c6856fbf73b2840286c9d50fae2d5d48 + # via -r third_party/pip/requirements.in +types-requests==2.32.0.20240712 \ + --hash=sha256:90c079ff05e549f6bf50e02e910210b98b8ff1ebdd18e19c873cd237737c1358 \ + --hash=sha256:f754283e152c752e46e70942fa2a146b5bc70393522257bb85bd1ef7e019dcc3 + # via -r third_party/pip/requirements.in +types-setuptools==72.2.0.20240821 \ + --hash=sha256:260e89d6d3b42cc35f9f0f382d030713b7b547344a664c05c9175e6ba124fac7 \ + --hash=sha256:e349b8015608879939f27ee370672f801287c46f5caa2d188d416336172c4965 + # via -r third_party/pip/requirements.in +types-six==1.16.21.20240513 \ + --hash=sha256:af2a105be6d504339bfed81319cc8e8697865f0ee5c6baa63658f127b33b9e63 \ + --hash=sha256:cdf445b5161bf17753500713a475ab79a45bd0d87728b8bfcecd86e2fbf66402 + # via -r third_party/pip/requirements.in +types-typed-ast==1.5.8.7 \ + --hash=sha256:97bdd9b4228f96c6904a76e10a050305ddadb529bd35e4d8234711e09c41b543 \ + --hash=sha256:f7795f6f9d597b35212314040b993f6613b51d81738edce3c1e3a3e9ef655124 + # via -r third_party/pip/requirements.in +types-urllib3==1.26.25.14 \ + --hash=sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f \ + --hash=sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e + # via -r third_party/pip/requirements.in +typing-extensions==4.13.2 \ + --hash=sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c \ + --hash=sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef + # via + # astroid + # black + # exceptiongroup + # mypy + # pylint + # pytest-bdd +urllib3==2.2.3 \ + --hash=sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac \ + --hash=sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9 + # via + # requests + # types-requests +webencodings==0.5.1 \ + --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ + --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 + # via html5lib + +# The following packages are considered to be unsafe in a requirements file: +pip==25.0.1 \ + --hash=sha256:88f96547ea48b940a3a385494e181e29fb8637898f88d88737c5049780f196ea \ + --hash=sha256:c46efd13b6aa8279f33f2864459c8ce587ea6a1a59ee20de055868d8f7688f7f + # via pip-api diff --git a/third_party/pip/requirements_lock_3_9.txt b/third_party/pip/requirements_lock_3_9.txt new file mode 100644 index 0000000..88dae35 --- /dev/null +++ b/third_party/pip/requirements_lock_3_9.txt @@ -0,0 +1,834 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# bazel run //third_party/pip:requirements_3_9.update +# +astroid==3.1.0 \ + --hash=sha256:951798f922990137ac090c53af473db7ab4e70c770e6d7fae0cec59f74411819 \ + --hash=sha256:ac248253bfa4bd924a0de213707e7ebeeb3138abeb48d798784ead1e56d419d4 + # via pylint +attrs==25.3.0 \ + --hash=sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3 \ + --hash=sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b + # via referencing +black==24.4.2 \ + --hash=sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474 \ + --hash=sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1 \ + --hash=sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0 \ + --hash=sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8 \ + --hash=sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96 \ + --hash=sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1 \ + --hash=sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04 \ + --hash=sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021 \ + --hash=sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94 \ + --hash=sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d \ + --hash=sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c \ + --hash=sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7 \ + --hash=sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c \ + --hash=sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc \ + --hash=sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7 \ + --hash=sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d \ + --hash=sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c \ + --hash=sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741 \ + --hash=sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce \ + --hash=sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb \ + --hash=sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063 \ + --hash=sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e + # via -r third_party/pip/requirements.in +boolean-py==5.0 \ + --hash=sha256:60cbc4bad079753721d32649545505362c754e121570ada4658b852a3a318d95 \ + --hash=sha256:ef28a70bd43115208441b53a045d1549e2f0ec6e3d08a9d142cbc41c1938e8d9 + # via license-expression +cachecontrol[filecache]==0.14.3 \ + --hash=sha256:73e7efec4b06b20d9267b441c1f733664f989fb8688391b670ca812d70795d11 \ + --hash=sha256:b35e44a3113f17d2a31c1e6b27b9de6d4405f84ae51baa8c1d3cc5b633010cae + # via + # cachecontrol + # pip-audit +certifi==2025.8.3 \ + --hash=sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407 \ + --hash=sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5 + # via requests +charset-normalizer==3.4.3 \ + --hash=sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91 \ + --hash=sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0 \ + --hash=sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154 \ + --hash=sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601 \ + --hash=sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884 \ + --hash=sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07 \ + --hash=sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c \ + --hash=sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64 \ + --hash=sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe \ + --hash=sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f \ + --hash=sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432 \ + --hash=sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc \ + --hash=sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa \ + --hash=sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9 \ + --hash=sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae \ + --hash=sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19 \ + --hash=sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d \ + --hash=sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e \ + --hash=sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4 \ + --hash=sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7 \ + --hash=sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312 \ + --hash=sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92 \ + --hash=sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31 \ + --hash=sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c \ + --hash=sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f \ + --hash=sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99 \ + --hash=sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b \ + --hash=sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15 \ + --hash=sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392 \ + --hash=sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f \ + --hash=sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8 \ + --hash=sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491 \ + --hash=sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0 \ + --hash=sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc \ + --hash=sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0 \ + --hash=sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f \ + --hash=sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a \ + --hash=sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40 \ + --hash=sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927 \ + --hash=sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849 \ + --hash=sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce \ + --hash=sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14 \ + --hash=sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05 \ + --hash=sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c \ + --hash=sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c \ + --hash=sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a \ + --hash=sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc \ + --hash=sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34 \ + --hash=sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9 \ + --hash=sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096 \ + --hash=sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14 \ + --hash=sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30 \ + --hash=sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b \ + --hash=sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b \ + --hash=sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942 \ + --hash=sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db \ + --hash=sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5 \ + --hash=sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b \ + --hash=sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce \ + --hash=sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669 \ + --hash=sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0 \ + --hash=sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018 \ + --hash=sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93 \ + --hash=sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe \ + --hash=sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049 \ + --hash=sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a \ + --hash=sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef \ + --hash=sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2 \ + --hash=sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca \ + --hash=sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16 \ + --hash=sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f \ + --hash=sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb \ + --hash=sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1 \ + --hash=sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557 \ + --hash=sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37 \ + --hash=sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7 \ + --hash=sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72 \ + --hash=sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c \ + --hash=sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9 + # via requests +click==8.1.8 \ + --hash=sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2 \ + --hash=sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a + # via black +colorama==0.4.6 \ + --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ + --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 + # via -r third_party/pip/requirements.in +coverage==7.5.1 \ + --hash=sha256:0646599e9b139988b63704d704af8e8df7fa4cbc4a1f33df69d97f36cb0a38de \ + --hash=sha256:0cdcbc320b14c3e5877ee79e649677cb7d89ef588852e9583e6b24c2e5072661 \ + --hash=sha256:0d0a0f5e06881ecedfe6f3dd2f56dcb057b6dbeb3327fd32d4b12854df36bf26 \ + --hash=sha256:1434e088b41594baa71188a17533083eabf5609e8e72f16ce8c186001e6b8c41 \ + --hash=sha256:16db7f26000a07efcf6aea00316f6ac57e7d9a96501e990a36f40c965ec7a95d \ + --hash=sha256:1cc0fe9b0b3a8364093c53b0b4c0c2dd4bb23acbec4c9240b5f284095ccf7981 \ + --hash=sha256:1fc81d5878cd6274ce971e0a3a18a8803c3fe25457165314271cf78e3aae3aa2 \ + --hash=sha256:2ec92012fefebee89a6b9c79bc39051a6cb3891d562b9270ab10ecfdadbc0c34 \ + --hash=sha256:39afcd3d4339329c5f58de48a52f6e4e50f6578dd6099961cf22228feb25f38f \ + --hash=sha256:4a7b0ceee8147444347da6a66be737c9d78f3353b0681715b668b72e79203e4a \ + --hash=sha256:4a9ca3f2fae0088c3c71d743d85404cec8df9be818a005ea065495bedc33da35 \ + --hash=sha256:4bf0655ab60d754491004a5efd7f9cccefcc1081a74c9ef2da4735d6ee4a6223 \ + --hash=sha256:4cc37def103a2725bc672f84bd939a6fe4522310503207aae4d56351644682f1 \ + --hash=sha256:4fc84a37bfd98db31beae3c2748811a3fa72bf2007ff7902f68746d9757f3746 \ + --hash=sha256:5037f8fcc2a95b1f0e80585bd9d1ec31068a9bcb157d9750a172836e98bc7a90 \ + --hash=sha256:54de9ef3a9da981f7af93eafde4ede199e0846cd819eb27c88e2b712aae9708c \ + --hash=sha256:556cf1a7cbc8028cb60e1ff0be806be2eded2daf8129b8811c63e2b9a6c43bca \ + --hash=sha256:57e0204b5b745594e5bc14b9b50006da722827f0b8c776949f1135677e88d0b8 \ + --hash=sha256:5a5740d1fb60ddf268a3811bcd353de34eb56dc24e8f52a7f05ee513b2d4f596 \ + --hash=sha256:5c3721c2c9e4c4953a41a26c14f4cef64330392a6d2d675c8b1db3b645e31f0e \ + --hash=sha256:5fa567e99765fe98f4e7d7394ce623e794d7cabb170f2ca2ac5a4174437e90dd \ + --hash=sha256:5fd215c0c7d7aab005221608a3c2b46f58c0285a819565887ee0b718c052aa4e \ + --hash=sha256:6175d1a0559986c6ee3f7fccfc4a90ecd12ba0a383dcc2da30c2b9918d67d8a3 \ + --hash=sha256:61c4bf1ba021817de12b813338c9be9f0ad5b1e781b9b340a6d29fc13e7c1b5e \ + --hash=sha256:6537e7c10cc47c595828b8a8be04c72144725c383c4702703ff4e42e44577312 \ + --hash=sha256:68f962d9b72ce69ea8621f57551b2fa9c70509af757ee3b8105d4f51b92b41a7 \ + --hash=sha256:7352b9161b33fd0b643ccd1f21f3a3908daaddf414f1c6cb9d3a2fd618bf2572 \ + --hash=sha256:796a79f63eca8814ca3317a1ea443645c9ff0d18b188de470ed7ccd45ae79428 \ + --hash=sha256:79afb6197e2f7f60c4824dd4b2d4c2ec5801ceb6ba9ce5d2c3080e5660d51a4f \ + --hash=sha256:7a588d39e0925f6a2bff87154752481273cdb1736270642aeb3635cb9b4cad07 \ + --hash=sha256:8748731ad392d736cc9ccac03c9845b13bb07d020a33423fa5b3a36521ac6e4e \ + --hash=sha256:8fe7502616b67b234482c3ce276ff26f39ffe88adca2acf0261df4b8454668b4 \ + --hash=sha256:9314d5678dcc665330df5b69c1e726a0e49b27df0461c08ca12674bcc19ef136 \ + --hash=sha256:9735317685ba6ec7e3754798c8871c2f49aa5e687cc794a0b1d284b2389d1bd5 \ + --hash=sha256:9981706d300c18d8b220995ad22627647be11a4276721c10911e0e9fa44c83e8 \ + --hash=sha256:9e78295f4144f9dacfed4f92935fbe1780021247c2fabf73a819b17f0ccfff8d \ + --hash=sha256:b016ea6b959d3b9556cb401c55a37547135a587db0115635a443b2ce8f1c7228 \ + --hash=sha256:b6cf3764c030e5338e7f61f95bd21147963cf6aa16e09d2f74f1fa52013c1206 \ + --hash=sha256:beccf7b8a10b09c4ae543582c1319c6df47d78fd732f854ac68d518ee1fb97fa \ + --hash=sha256:c0884920835a033b78d1c73b6d3bbcda8161a900f38a488829a83982925f6c2e \ + --hash=sha256:c3e757949f268364b96ca894b4c342b41dc6f8f8b66c37878aacef5930db61be \ + --hash=sha256:ca498687ca46a62ae590253fba634a1fe9836bc56f626852fb2720f334c9e4e5 \ + --hash=sha256:d1d0d98d95dd18fe29dc66808e1accf59f037d5716f86a501fc0256455219668 \ + --hash=sha256:d21918e9ef11edf36764b93101e2ae8cc82aa5efdc7c5a4e9c6c35a48496d601 \ + --hash=sha256:d7fed867ee50edf1a0b4a11e8e5d0895150e572af1cd6d315d557758bfa9c057 \ + --hash=sha256:db66fc317a046556a96b453a58eced5024af4582a8dbdc0c23ca4dbc0d5b3146 \ + --hash=sha256:dde0070c40ea8bb3641e811c1cfbf18e265d024deff6de52c5950677a8fb1e0f \ + --hash=sha256:df4e745a81c110e7446b1cc8131bf986157770fa405fe90e15e850aaf7619bc8 \ + --hash=sha256:e2213def81a50519d7cc56ed643c9e93e0247f5bbe0d1247d15fa520814a7cd7 \ + --hash=sha256:ef48e2707fb320c8f139424a596f5b69955a85b178f15af261bab871873bb987 \ + --hash=sha256:f152cbf5b88aaeb836127d920dd0f5e7edff5a66f10c079157306c4343d86c19 \ + --hash=sha256:fc0b4d8bfeabd25ea75e94632f5b6e047eef8adaed0c2161ada1e922e7f7cece + # via -r third_party/pip/requirements.in +cyclonedx-python-lib==6.4.4 \ + --hash=sha256:1b6f9109b6b9e91636dff822c2de90a05c0c8af120317713c1b879dbfdebdff8 \ + --hash=sha256:c366619cc4effd528675f1f7a7a00be30b6695ff03f49c64880ad15acbebc341 + # via pip-audit +defusedxml==0.7.1 \ + --hash=sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69 \ + --hash=sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61 + # via py-serializable +dill==0.4.0 \ + --hash=sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0 \ + --hash=sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049 + # via pylint +exceptiongroup==1.3.0 \ + --hash=sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10 \ + --hash=sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88 + # via pytest +filelock==3.18.0 \ + --hash=sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2 \ + --hash=sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de + # via cachecontrol +html5lib==1.1 \ + --hash=sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d \ + --hash=sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f + # via pip-audit +idna==3.10 \ + --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ + --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 + # via requests +iniconfig==2.1.0 \ + --hash=sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7 \ + --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 + # via pytest +isort==5.13.2 \ + --hash=sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109 \ + --hash=sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6 + # via + # -r third_party/pip/requirements.in + # pylint +license-expression==30.4.4 \ + --hash=sha256:421788fdcadb41f049d2dc934ce666626265aeccefddd25e162a26f23bcbf8a4 \ + --hash=sha256:73448f0aacd8d0808895bdc4b2c8e01a8d67646e4188f887375398c761f340fd + # via cyclonedx-python-lib +mako==1.3.10 \ + --hash=sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28 \ + --hash=sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59 + # via pytest-bdd +markdown-it-py==3.0.0 \ + --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ + --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb + # via rich +markupsafe==3.0.2 \ + --hash=sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4 \ + --hash=sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30 \ + --hash=sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0 \ + --hash=sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9 \ + --hash=sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396 \ + --hash=sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13 \ + --hash=sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028 \ + --hash=sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca \ + --hash=sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557 \ + --hash=sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832 \ + --hash=sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0 \ + --hash=sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b \ + --hash=sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579 \ + --hash=sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a \ + --hash=sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c \ + --hash=sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff \ + --hash=sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c \ + --hash=sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22 \ + --hash=sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094 \ + --hash=sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb \ + --hash=sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e \ + --hash=sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5 \ + --hash=sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a \ + --hash=sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d \ + --hash=sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a \ + --hash=sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b \ + --hash=sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8 \ + --hash=sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225 \ + --hash=sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c \ + --hash=sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144 \ + --hash=sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f \ + --hash=sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87 \ + --hash=sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d \ + --hash=sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93 \ + --hash=sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf \ + --hash=sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158 \ + --hash=sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84 \ + --hash=sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb \ + --hash=sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48 \ + --hash=sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171 \ + --hash=sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c \ + --hash=sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6 \ + --hash=sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd \ + --hash=sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d \ + --hash=sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1 \ + --hash=sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d \ + --hash=sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca \ + --hash=sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a \ + --hash=sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29 \ + --hash=sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe \ + --hash=sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798 \ + --hash=sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c \ + --hash=sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8 \ + --hash=sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f \ + --hash=sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f \ + --hash=sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a \ + --hash=sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178 \ + --hash=sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0 \ + --hash=sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79 \ + --hash=sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430 \ + --hash=sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50 + # via mako +mccabe==0.7.0 \ + --hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \ + --hash=sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e + # via pylint +mdurl==0.1.2 \ + --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ + --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba + # via markdown-it-py +msgpack==1.1.1 \ + --hash=sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8 \ + --hash=sha256:1abfc6e949b352dadf4bce0eb78023212ec5ac42f6abfd469ce91d783c149c2a \ + --hash=sha256:1b13fe0fb4aac1aa5320cd693b297fe6fdef0e7bea5518cbc2dd5299f873ae90 \ + --hash=sha256:1d75f3807a9900a7d575d8d6674a3a47e9f227e8716256f35bc6f03fc597ffbf \ + --hash=sha256:2fbbc0b906a24038c9958a1ba7ae0918ad35b06cb449d398b76a7d08470b0ed9 \ + --hash=sha256:33be9ab121df9b6b461ff91baac6f2731f83d9b27ed948c5b9d1978ae28bf157 \ + --hash=sha256:353b6fc0c36fde68b661a12949d7d49f8f51ff5fa019c1e47c87c4ff34b080ed \ + --hash=sha256:36043272c6aede309d29d56851f8841ba907a1a3d04435e43e8a19928e243c1d \ + --hash=sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0 \ + --hash=sha256:3a89cd8c087ea67e64844287ea52888239cbd2940884eafd2dcd25754fb72232 \ + --hash=sha256:40eae974c873b2992fd36424a5d9407f93e97656d999f43fca9d29f820899084 \ + --hash=sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5 \ + --hash=sha256:435807eeb1bc791ceb3247d13c79868deb22184e1fc4224808750f0d7d1affc1 \ + --hash=sha256:4835d17af722609a45e16037bb1d4d78b7bdf19d6c0128116d178956618c4e88 \ + --hash=sha256:4a28e8072ae9779f20427af07f53bbb8b4aa81151054e882aee333b158da8752 \ + --hash=sha256:4d3237b224b930d58e9d83c81c0dba7aacc20fcc2f89c1e5423aa0529a4cd142 \ + --hash=sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac \ + --hash=sha256:4fd6b577e4541676e0cc9ddc1709d25014d3ad9a66caa19962c4f5de30fc09ef \ + --hash=sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323 \ + --hash=sha256:5692095123007180dca3e788bb4c399cc26626da51629a31d40207cb262e67f4 \ + --hash=sha256:5fd1b58e1431008a57247d6e7cc4faa41c3607e8e7d4aaf81f7c29ea013cb458 \ + --hash=sha256:61abccf9de335d9efd149e2fff97ed5974f2481b3353772e8e2dd3402ba2bd57 \ + --hash=sha256:61e35a55a546a1690d9d09effaa436c25ae6130573b6ee9829c37ef0f18d5e78 \ + --hash=sha256:6640fd979ca9a212e4bcdf6eb74051ade2c690b862b679bfcb60ae46e6dc4bfd \ + --hash=sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69 \ + --hash=sha256:6f64ae8fe7ffba251fecb8408540c34ee9df1c26674c50c4544d72dbf792e5ce \ + --hash=sha256:71ef05c1726884e44f8b1d1773604ab5d4d17729d8491403a705e649116c9558 \ + --hash=sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd \ + --hash=sha256:78426096939c2c7482bf31ef15ca219a9e24460289c00dd0b94411040bb73ad2 \ + --hash=sha256:79c408fcf76a958491b4e3b103d1c417044544b68e96d06432a189b43d1215c8 \ + --hash=sha256:7a17ac1ea6ec3c7687d70201cfda3b1e8061466f28f686c24f627cae4ea8efd0 \ + --hash=sha256:7da8831f9a0fdb526621ba09a281fadc58ea12701bc709e7b8cbc362feabc295 \ + --hash=sha256:870b9a626280c86cff9c576ec0d9cbcc54a1e5ebda9cd26dab12baf41fee218c \ + --hash=sha256:88d1e966c9235c1d4e2afac21ca83933ba59537e2e2727a999bf3f515ca2af26 \ + --hash=sha256:88daaf7d146e48ec71212ce21109b66e06a98e5e44dca47d853cbfe171d6c8d2 \ + --hash=sha256:8a8b10fdb84a43e50d38057b06901ec9da52baac6983d3f709d8507f3889d43f \ + --hash=sha256:8b17ba27727a36cb73aabacaa44b13090feb88a01d012c0f4be70c00f75048b4 \ + --hash=sha256:8b65b53204fe1bd037c40c4148d00ef918eb2108d24c9aaa20bc31f9810ce0a8 \ + --hash=sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9 \ + --hash=sha256:96decdfc4adcbc087f5ea7ebdcfd3dee9a13358cae6e81d54be962efc38f6338 \ + --hash=sha256:996f2609ddf0142daba4cefd767d6db26958aac8439ee41db9cc0db9f4c4c3a6 \ + --hash=sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a \ + --hash=sha256:a32747b1b39c3ac27d0670122b57e6e57f28eefb725e0b625618d1b59bf9d1e0 \ + --hash=sha256:a494554874691720ba5891c9b0b39474ba43ffb1aaf32a5dac874effb1619e1a \ + --hash=sha256:a8ef6e342c137888ebbfb233e02b8fbd689bb5b5fcc59b34711ac47ebd504478 \ + --hash=sha256:ae497b11f4c21558d95de9f64fff7053544f4d1a17731c866143ed6bb4591238 \ + --hash=sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7 \ + --hash=sha256:b8f93dcddb243159c9e4109c9750ba5b335ab8d48d9522c5308cd05d7e3ce600 \ + --hash=sha256:ba0c325c3f485dc54ec298d8b024e134acf07c10d494ffa24373bea729acf704 \ + --hash=sha256:bb29aaa613c0a1c40d1af111abf025f1732cab333f96f285d6a93b934738a68a \ + --hash=sha256:bba1be28247e68994355e028dcd668316db30c1f758d3241a7b903ac78dcd285 \ + --hash=sha256:cb643284ab0ed26f6957d969fe0dd8bb17beb567beb8998140b5e38a90974f6c \ + --hash=sha256:d182dac0221eb8faef2e6f44701812b467c02674a322c739355c39e94730cdbf \ + --hash=sha256:d275a9e3c81b1093c060c3837e580c37f47c51eca031f7b5fb76f7b8470f5f9b \ + --hash=sha256:d8b55ea20dc59b181d3f47103f113e6f28a5e1c89fd5b67b9140edb442ab67f2 \ + --hash=sha256:da8f41e602574ece93dbbda1fab24650d6bf2a24089f9e9dbb4f5730ec1e58ad \ + --hash=sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b \ + --hash=sha256:f5be6b6bc52fad84d010cb45433720327ce886009d862f46b26d4d154001994b \ + --hash=sha256:f6d58656842e1b2ddbe07f43f56b10a60f2ba5826164910968f5933e5178af75 + # via cachecontrol +mypy==1.11.1 \ + --hash=sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54 \ + --hash=sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a \ + --hash=sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72 \ + --hash=sha256:11965c2f571ded6239977b14deebd3f4c3abd9a92398712d6da3a772974fad69 \ + --hash=sha256:1a81cf05975fd61aec5ae16501a091cfb9f605dc3e3c878c0da32f250b74760b \ + --hash=sha256:2684d3f693073ab89d76da8e3921883019ea8a3ec20fa5d8ecca6a2db4c54bbe \ + --hash=sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4 \ + --hash=sha256:45df906e8b6804ef4b666af29a87ad9f5921aad091c79cc38e12198e220beabd \ + --hash=sha256:4c956b49c5d865394d62941b109728c5c596a415e9c5b2be663dd26a1ff07bc0 \ + --hash=sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525 \ + --hash=sha256:749fd3213916f1751fff995fccf20c6195cae941dc968f3aaadf9bb4e430e5a2 \ + --hash=sha256:79c07eb282cb457473add5052b63925e5cc97dfab9812ee65a7c7ab5e3cb551c \ + --hash=sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5 \ + --hash=sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de \ + --hash=sha256:a2b43895a0f8154df6519706d9bca8280cda52d3d9d1514b2d9c3e26792a0b74 \ + --hash=sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c \ + --hash=sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e \ + --hash=sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58 \ + --hash=sha256:b639dce63a0b19085213ec5fdd8cffd1d81988f47a2dec7100e93564f3e8fb3b \ + --hash=sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417 \ + --hash=sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411 \ + --hash=sha256:d44be7551689d9d47b7abc27c71257adfdb53f03880841a5db15ddb22dc63edb \ + --hash=sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03 \ + --hash=sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca \ + --hash=sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8 \ + --hash=sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08 \ + --hash=sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809 + # via -r third_party/pip/requirements.in +mypy-extensions==1.1.0 \ + --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \ + --hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558 + # via + # black + # mypy +packageurl-python==0.17.5 \ + --hash=sha256:a7be3f3ba70d705f738ace9bf6124f31920245a49fa69d4b416da7037dd2de61 \ + --hash=sha256:f0e55452ab37b5c192c443de1458e3f3b4d8ac27f747df6e8c48adeab081d321 + # via cyclonedx-python-lib +packaging==25.0 \ + --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ + --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f + # via + # black + # pip-audit + # pip-requirements-parser + # pytest + # pytest-bdd + # pytest-rerunfailures +parse==1.20.2 \ + --hash=sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558 \ + --hash=sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce + # via + # parse-type + # pytest-bdd +parse-type==0.6.6 \ + --hash=sha256:3ca79bbe71e170dfccc8ec6c341edfd1c2a0fc1e5cfd18330f93af938de2348c \ + --hash=sha256:513a3784104839770d690e04339a8b4d33439fcd5dd99f2e4580f9fc1097bfb2 + # via pytest-bdd +pathspec==0.12.1 \ + --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ + --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712 + # via black +pip-api==0.0.34 \ + --hash=sha256:8b2d7d7c37f2447373aa2cf8b1f60a2f2b27a84e1e9e0294a3f6ef10eb3ba6bb \ + --hash=sha256:9b75e958f14c5a2614bae415f2adf7eeb54d50a2cfbe7e24fd4826471bac3625 + # via pip-audit +pip-audit==2.7.0 \ + --hash=sha256:67740c5b1d5d967a258c3dfefc46f9713a2819c48062505ddf4b29de101c2b75 \ + --hash=sha256:83e039740653eb9ef1a78b1540ed441600cd88a560588ba2c0a169180685a522 + # via -r third_party/pip/requirements.in +pip-requirements-parser==32.0.1 \ + --hash=sha256:4659bc2a667783e7a15d190f6fccf8b2486685b6dba4c19c3876314769c57526 \ + --hash=sha256:b4fa3a7a0be38243123cf9d1f3518da10c51bdb165a2b2985566247f9155a7d3 + # via pip-audit +platformdirs==4.3.8 \ + --hash=sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc \ + --hash=sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4 + # via + # black + # pylint +pluggy==1.6.0 \ + --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ + --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + # via pytest +py-cpuinfo==9.0.0 \ + --hash=sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690 \ + --hash=sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5 + # via pytest-benchmark +py-serializable==1.1.2 \ + --hash=sha256:801be61b0a1ba64c3861f7c624f1de5cfbbabf8b458acc9cdda91e8f7e5effa1 \ + --hash=sha256:89af30bc319047d4aa0d8708af412f6ce73835e18bacf1a080028bb9e2f42bdb + # via cyclonedx-python-lib +pygments==2.19.2 \ + --hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \ + --hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b + # via rich +pylint==3.1.0 \ + --hash=sha256:507a5b60953874766d8a366e8e8c7af63e058b26345cfcb5f91f89d987fd6b74 \ + --hash=sha256:6a69beb4a6f63debebaab0a3477ecd0f559aa726af4954fc948c51f7a2549e23 + # via -r third_party/pip/requirements.in +pyparsing==3.2.3 \ + --hash=sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf \ + --hash=sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be + # via pip-requirements-parser +pytest==8.1.1 \ + --hash=sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7 \ + --hash=sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044 + # via + # -r third_party/pip/requirements.in + # pytest-bdd + # pytest-benchmark + # pytest-mock + # pytest-rerunfailures + # pytest-timeout +pytest-bdd==7.1.1 \ + --hash=sha256:1b41a10a8391f1849f0a1524d77b2991d0d8042d2253ace9b616b89b4a9b97a7 \ + --hash=sha256:331621122a46f9881110ae0dd43fedb29e96e8202de29256be3d76c53b57c1d2 + # via -r third_party/pip/requirements.in +pytest-benchmark==4.0.0 \ + --hash=sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1 \ + --hash=sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6 + # via -r third_party/pip/requirements.in +pytest-mock==3.12.0 \ + --hash=sha256:0972719a7263072da3a21c7f4773069bcc7486027d7e8e1f81d98a47e701bc4f \ + --hash=sha256:31a40f038c22cad32287bb43932054451ff5583ff094bca6f675df2f8bc1a6e9 + # via -r third_party/pip/requirements.in +pytest-rerunfailures==14.0 \ + --hash=sha256:4197bdd2eaeffdbf50b5ea6e7236f47ff0e44d1def8dae08e409f536d84e7b32 \ + --hash=sha256:4a400bcbcd3c7a4ad151ab8afac123d90eca3abe27f98725dc4d9702887d2e92 + # via -r third_party/pip/requirements.in +pytest-timeout==2.3.1 \ + --hash=sha256:12397729125c6ecbdaca01035b9e5239d4db97352320af155b3f5de1ba5165d9 \ + --hash=sha256:68188cb703edfc6a18fad98dc25a3c61e9f24d644b0b70f33af545219fc7813e + # via -r third_party/pip/requirements.in +referencing==0.36.2 \ + --hash=sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa \ + --hash=sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0 + # via types-jsonschema +requests==2.32.4 \ + --hash=sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c \ + --hash=sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422 + # via + # cachecontrol + # pip-audit +rich==14.1.0 \ + --hash=sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f \ + --hash=sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8 + # via pip-audit +rpds-py==0.27.0 \ + --hash=sha256:010c4843a3b92b54373e3d2291a7447d6c3fc29f591772cc2ea0e9f5c1da434b \ + --hash=sha256:05284439ebe7d9f5f5a668d4d8a0a1d851d16f7d47c78e1fab968c8ad30cab04 \ + --hash=sha256:0665be515767dc727ffa5f74bd2ef60b0ff85dad6bb8f50d91eaa6b5fb226f51 \ + --hash=sha256:069e0384a54f427bd65d7fda83b68a90606a3835901aaff42185fcd94f5a9295 \ + --hash=sha256:08680820d23df1df0a0260f714d12966bc6c42d02e8055a91d61e03f0c47dda0 \ + --hash=sha256:0954e3a92e1d62e83a54ea7b3fdc9efa5d61acef8488a8a3d31fdafbfb00460d \ + --hash=sha256:09965b314091829b378b60607022048953e25f0b396c2b70e7c4c81bcecf932e \ + --hash=sha256:0c431bfb91478d7cbe368d0a699978050d3b112d7f1d440a41e90faa325557fd \ + --hash=sha256:0f401c369186a5743694dd9fc08cba66cf70908757552e1f714bfc5219c655b5 \ + --hash=sha256:0f4f69d7a4300fbf91efb1fb4916421bd57804c01ab938ab50ac9c4aa2212f03 \ + --hash=sha256:11e8e28c0ba0373d052818b600474cfee2fafa6c9f36c8587d217b13ee28ca7d \ + --hash=sha256:130c1ffa5039a333f5926b09e346ab335f0d4ec393b030a18549a7c7e7c2cea4 \ + --hash=sha256:1321bce595ad70e80f97f998db37356b2e22cf98094eba6fe91782e626da2f71 \ + --hash=sha256:13bbc4846ae4c993f07c93feb21a24d8ec637573d567a924b1001e81c8ae80f9 \ + --hash=sha256:14f028eb47f59e9169bfdf9f7ceafd29dd64902141840633683d0bad5b04ff34 \ + --hash=sha256:15ea4d2e182345dd1b4286593601d766411b43f868924afe297570658c31a62b \ + --hash=sha256:181bc29e59e5e5e6e9d63b143ff4d5191224d355e246b5a48c88ce6b35c4e466 \ + --hash=sha256:183f5e221ba3e283cd36fdfbe311d95cd87699a083330b4f792543987167eff1 \ + --hash=sha256:184f0d7b342967f6cda94a07d0e1fae177d11d0b8f17d73e06e36ac02889f303 \ + --hash=sha256:190d7285cd3bb6d31d37a0534d7359c1ee191eb194c511c301f32a4afa5a1dd4 \ + --hash=sha256:19c990fdf5acecbf0623e906ae2e09ce1c58947197f9bced6bbd7482662231c4 \ + --hash=sha256:1d66f45b9399036e890fb9c04e9f70c33857fd8f58ac8db9f3278cfa835440c3 \ + --hash=sha256:203f581accef67300a942e49a37d74c12ceeef4514874c7cede21b012613ca2c \ + --hash=sha256:20e222a44ae9f507d0f2678ee3dd0c45ec1e930f6875d99b8459631c24058aec \ + --hash=sha256:2406d034635d1497c596c40c85f86ecf2bf9611c1df73d14078af8444fe48031 \ + --hash=sha256:249ab91ceaa6b41abc5f19513cb95b45c6f956f6b89f1fe3d99c81255a849f9e \ + --hash=sha256:25a4aebf8ca02bbb90a9b3e7a463bbf3bee02ab1c446840ca07b1695a68ce424 \ + --hash=sha256:27bac29bbbf39601b2aab474daf99dbc8e7176ca3389237a23944b17f8913d97 \ + --hash=sha256:299a245537e697f28a7511d01038c310ac74e8ea213c0019e1fc65f52c0dcb23 \ + --hash=sha256:2cff9bdd6c7b906cc562a505c04a57d92e82d37200027e8d362518df427f96cd \ + --hash=sha256:2e307cb5f66c59ede95c00e93cd84190a5b7f3533d7953690b2036780622ba81 \ + --hash=sha256:2e39169ac6aae06dd79c07c8a69d9da867cef6a6d7883a0186b46bb46ccfb0c3 \ + --hash=sha256:2fe6e18e5c8581f0361b35ae575043c7029d0a92cb3429e6e596c2cdde251432 \ + --hash=sha256:3001013dae10f806380ba739d40dee11db1ecb91684febb8406a87c2ded23dae \ + --hash=sha256:32196b5a99821476537b3f7732432d64d93a58d680a52c5e12a190ee0135d8b5 \ + --hash=sha256:33ba649a6e55ae3808e4c39e01580dc9a9b0d5b02e77b66bb86ef117922b1264 \ + --hash=sha256:341d8acb6724c0c17bdf714319c393bb27f6d23d39bc74f94221b3e59fc31828 \ + --hash=sha256:343cf24de9ed6c728abefc5d5c851d5de06497caa7ac37e5e65dd572921ed1b5 \ + --hash=sha256:36184b44bf60a480863e51021c26aca3dfe8dd2f5eeabb33622b132b9d8b8b54 \ + --hash=sha256:3841f66c1ffdc6cebce8aed64e36db71466f1dc23c0d9a5592e2a782a3042c79 \ + --hash=sha256:4045e2fc4b37ec4b48e8907a5819bdd3380708c139d7cc358f03a3653abedb89 \ + --hash=sha256:419dd9c98bcc9fb0242be89e0c6e922df333b975d4268faa90d58499fd9c9ebe \ + --hash=sha256:42894616da0fc0dcb2ec08a77896c3f56e9cb2f4b66acd76fc8992c3557ceb1c \ + --hash=sha256:42ccc57ff99166a55a59d8c7d14f1a357b7749f9ed3584df74053fd098243451 \ + --hash=sha256:4300e15e7d03660f04be84a125d1bdd0e6b2f674bc0723bc0fd0122f1a4585dc \ + --hash=sha256:443d239d02d9ae55b74015234f2cd8eb09e59fbba30bf60baeb3123ad4c6d5ff \ + --hash=sha256:44524b96481a4c9b8e6c46d6afe43fa1fb485c261e359fbe32b63ff60e3884d8 \ + --hash=sha256:45d04a73c54b6a5fd2bab91a4b5bc8b426949586e61340e212a8484919183859 \ + --hash=sha256:46f48482c1a4748ab2773f75fffbdd1951eb59794e32788834b945da857c47a8 \ + --hash=sha256:4790c9d5dd565ddb3e9f656092f57268951398cef52e364c405ed3112dc7c7c1 \ + --hash=sha256:4bc262ace5a1a7dc3e2eac2fa97b8257ae795389f688b5adf22c5db1e2431c43 \ + --hash=sha256:4c3f8a0d4802df34fcdbeb3dfe3a4d8c9a530baea8fafdf80816fcaac5379d83 \ + --hash=sha256:5355527adaa713ab693cbce7c1e0ec71682f599f61b128cf19d07e5c13c9b1f1 \ + --hash=sha256:555ed147cbe8c8f76e72a4c6cd3b7b761cbf9987891b9448808148204aed74a5 \ + --hash=sha256:55d42a0ef2bdf6bc81e1cc2d49d12460f63c6ae1423c4f4851b828e454ccf6f1 \ + --hash=sha256:59195dc244fc183209cf8a93406889cadde47dfd2f0a6b137783aa9c56d67c85 \ + --hash=sha256:59714ab0a5af25d723d8e9816638faf7f4254234decb7d212715c1aa71eee7be \ + --hash=sha256:5b3a5c8089eed498a3af23ce87a80805ff98f6ef8f7bdb70bd1b7dae5105f6ac \ + --hash=sha256:5d6790ff400254137b81b8053b34417e2c46921e302d655181d55ea46df58cf7 \ + --hash=sha256:5df559e9e7644d9042f626f2c3997b555f347d7a855a15f170b253f6c5bfe358 \ + --hash=sha256:5fa01b3d5e3b7d97efab65bd3d88f164e289ec323a8c033c5c38e53ee25c007e \ + --hash=sha256:61490d57e82e23b45c66f96184237994bfafa914433b8cd1a9bb57fecfced59d \ + --hash=sha256:6168af0be75bba990a39f9431cdfae5f0ad501f4af32ae62e8856307200517b8 \ + --hash=sha256:64a0fe3f334a40b989812de70160de6b0ec7e3c9e4a04c0bbc48d97c5d3600ae \ + --hash=sha256:64f689ab822f9b5eb6dfc69893b4b9366db1d2420f7db1f6a2adf2a9ca15ad64 \ + --hash=sha256:699c346abc73993962cac7bb4f02f58e438840fa5458a048d3a178a7a670ba86 \ + --hash=sha256:6b96b0b784fe5fd03beffff2b1533dc0d85e92bab8d1b2c24ef3a5dc8fac5669 \ + --hash=sha256:6bde37765564cd22a676dd8101b657839a1854cfaa9c382c5abf6ff7accfd4ae \ + --hash=sha256:6c135708e987f46053e0a1246a206f53717f9fadfba27174a9769ad4befba5c3 \ + --hash=sha256:6c27a7054b5224710fcfb1a626ec3ff4f28bcb89b899148c72873b18210e446b \ + --hash=sha256:6de6a7f622860af0146cb9ee148682ff4d0cea0b8fd3ad51ce4d40efb2f061d0 \ + --hash=sha256:737005088449ddd3b3df5a95476ee1c2c5c669f5c30eed909548a92939c0e12d \ + --hash=sha256:7451ede3560086abe1aa27dcdcf55cd15c96b56f543fb12e5826eee6f721f858 \ + --hash=sha256:7873b65686a6471c0037139aa000d23fe94628e0daaa27b6e40607c90e3f5ec4 \ + --hash=sha256:79af163a4b40bbd8cfd7ca86ec8b54b81121d3b213b4435ea27d6568bcba3e9d \ + --hash=sha256:7aed8118ae20515974650d08eb724150dc2e20c2814bcc307089569995e88a14 \ + --hash=sha256:7cf9bc4508efb18d8dff6934b602324eb9f8c6644749627ce001d6f38a490889 \ + --hash=sha256:7e57906e38583a2cba67046a09c2637e23297618dc1f3caddbc493f2be97c93f \ + --hash=sha256:7ec85994f96a58cf7ed288caa344b7fe31fd1d503bdf13d7331ead5f70ab60d5 \ + --hash=sha256:81f81bbd7cdb4bdc418c09a73809abeda8f263a6bf8f9c7f93ed98b5597af39d \ + --hash=sha256:86aca1616922b40d8ac1b3073a1ead4255a2f13405e5700c01f7c8d29a03972d \ + --hash=sha256:88051c3b7d5325409f433c5a40328fcb0685fc04e5db49ff936e910901d10114 \ + --hash=sha256:887ab1f12b0d227e9260558a4a2320024b20102207ada65c43e1ffc4546df72e \ + --hash=sha256:8a06aa1197ec0281eb1d7daf6073e199eb832fe591ffa329b88bae28f25f5fe5 \ + --hash=sha256:8a1dca5507fa1337f75dcd5070218b20bc68cf8844271c923c1b79dfcbc20391 \ + --hash=sha256:8b23cf252f180cda89220b378d917180f29d313cd6a07b2431c0d3b776aae86f \ + --hash=sha256:8d0e09cf4863c74106b5265c2c310f36146e2b445ff7b3018a56799f28f39f6f \ + --hash=sha256:8de567dec6d451649a781633d36f5c7501711adee329d76c095be2178855b042 \ + --hash=sha256:90fb790138c1a89a2e58c9282fe1089638401f2f3b8dddd758499041bc6e0774 \ + --hash=sha256:92f3b3ec3e6008a1fe00b7c0946a170f161ac00645cde35e3c9a68c2475e8156 \ + --hash=sha256:935afcdea4751b0ac918047a2df3f720212892347767aea28f5b3bf7be4f27c0 \ + --hash=sha256:9a0ff7ee28583ab30a52f371b40f54e7138c52ca67f8ca17ccb7ccf0b383cb5f \ + --hash=sha256:9ad08547995a57e74fea6abaf5940d399447935faebbd2612b3b0ca6f987946b \ + --hash=sha256:9b2a4e17bfd68536c3b801800941c95a1d4a06e3cada11c146093ba939d9638d \ + --hash=sha256:9b78430703cfcf5f5e86eb74027a1ed03a93509273d7c705babb547f03e60016 \ + --hash=sha256:9d0f92b78cfc3b74a42239fdd8c1266f4715b573204c234d2f9fc3fc7a24f185 \ + --hash=sha256:9da162b718b12c4219eeeeb68a5b7552fbc7aadedf2efee440f88b9c0e54b45d \ + --hash=sha256:a00c91104c173c9043bc46f7b30ee5e6d2f6b1149f11f545580f5d6fdff42c0b \ + --hash=sha256:a029be818059870664157194e46ce0e995082ac49926f1423c1f058534d2aaa9 \ + --hash=sha256:a1b3db5fae5cbce2131b7420a3f83553d4d89514c03d67804ced36161fe8b6b2 \ + --hash=sha256:a4cf32a26fa744101b67bfd28c55d992cd19438aff611a46cac7f066afca8fd4 \ + --hash=sha256:aa0bf113d15e8abdfee92aa4db86761b709a09954083afcb5bf0f952d6065fdb \ + --hash=sha256:ab47fe727c13c09d0e6f508e3a49e545008e23bf762a245b020391b621f5b726 \ + --hash=sha256:af22763a0a1eff106426a6e1f13c4582e0d0ad89c1493ab6c058236174cd6c6a \ + --hash=sha256:af9d4fd79ee1cc8e7caf693ee02737daabfc0fcf2773ca0a4735b356c8ad6f7c \ + --hash=sha256:b1fef1f13c842a39a03409e30ca0bf87b39a1e2a305a9924deadb75a43105d23 \ + --hash=sha256:b2eff8ee57c5996b0d2a07c3601fb4ce5fbc37547344a26945dd9e5cbd1ed27a \ + --hash=sha256:b4c4fbbcff474e1e5f38be1bf04511c03d492d42eec0babda5d03af3b5589374 \ + --hash=sha256:b8a4131698b6992b2a56015f51646711ec5d893a0b314a4b985477868e240c87 \ + --hash=sha256:b8a7acf04fda1f30f1007f3cc96d29d8cf0a53e626e4e1655fdf4eabc082d367 \ + --hash=sha256:ba783541be46f27c8faea5a6645e193943c17ea2f0ffe593639d906a327a9bcc \ + --hash=sha256:be0744661afbc4099fef7f4e604e7f1ea1be1dd7284f357924af12a705cc7d5c \ + --hash=sha256:be3964f7312ea05ed283b20f87cb533fdc555b2e428cc7be64612c0b2124f08c \ + --hash=sha256:be806e2961cd390a89d6c3ce8c2ae34271cfcd05660f716257838bb560f1c3b6 \ + --hash=sha256:bec77545d188f8bdd29d42bccb9191682a46fb2e655e3d1fb446d47c55ac3b8d \ + --hash=sha256:c10d92fb6d7fd827e44055fcd932ad93dac6a11e832d51534d77b97d1d85400f \ + --hash=sha256:c3782fb753aa825b4ccabc04292e07897e2fd941448eabf666856c5530277626 \ + --hash=sha256:c9ce7a9e967afc0a2af7caa0d15a3e9c1054815f73d6a8cb9225b61921b419bd \ + --hash=sha256:cb0702c12983be3b2fab98ead349ac63a98216d28dda6f518f52da5498a27a1b \ + --hash=sha256:cbc619e84a5e3ab2d452de831c88bdcad824414e9c2d28cd101f94dbdf26329c \ + --hash=sha256:ce4ed8e0c7dbc5b19352b9c2c6131dd23b95fa8698b5cdd076307a33626b72dc \ + --hash=sha256:ce96ab0bdfcef1b8c371ada2100767ace6804ea35aacce0aef3aeb4f3f499ca8 \ + --hash=sha256:cf824aceaeffff029ccfba0da637d432ca71ab21f13e7f6f5179cd88ebc77a8a \ + --hash=sha256:d2a81bdcfde4245468f7030a75a37d50400ac2455c3a4819d9d550c937f90ab5 \ + --hash=sha256:d2cc2b34f9e1d31ce255174da82902ad75bd7c0d88a33df54a77a22f2ef421ee \ + --hash=sha256:d2f184336bc1d6abfaaa1262ed42739c3789b1e3a65a29916a615307d22ffd2e \ + --hash=sha256:d3c622c39f04d5751408f5b801ecb527e6e0a471b367f420a877f7a660d583f6 \ + --hash=sha256:d7cf5e726b6fa977e428a61880fb108a62f28b6d0c7ef675b117eaff7076df49 \ + --hash=sha256:d85d784c619370d9329bbd670f41ff5f2ae62ea4519761b679d0f57f0f0ee267 \ + --hash=sha256:d93ebdb82363d2e7bec64eecdc3632b59e84bd270d74fe5be1659f7787052f9b \ + --hash=sha256:db8a6313dbac934193fc17fe7610f70cd8181c542a91382531bef5ed785e5615 \ + --hash=sha256:dbc2ab5d10544eb485baa76c63c501303b716a5c405ff2469a1d8ceffaabf622 \ + --hash=sha256:dbd749cff1defbde270ca346b69b3baf5f1297213ef322254bf2a28537f0b046 \ + --hash=sha256:dc662bc9375a6a394b62dfd331874c434819f10ee3902123200dbcf116963f89 \ + --hash=sha256:dc6b0d5a1ea0318ef2def2b6a55dccf1dcaf77d605672347271ed7b829860765 \ + --hash=sha256:dc79d192fb76fc0c84f2c58672c17bbbc383fd26c3cdc29daae16ce3d927e8b2 \ + --hash=sha256:dd2c1d27ebfe6a015cfa2005b7fe8c52d5019f7bbdd801bc6f7499aab9ae739e \ + --hash=sha256:dea0808153f1fbbad772669d906cddd92100277533a03845de6893cadeffc8be \ + --hash=sha256:e0d7151a1bd5d0a203a5008fc4ae51a159a610cb82ab0a9b2c4d80241745582e \ + --hash=sha256:e14aab02258cb776a108107bd15f5b5e4a1bbaa61ef33b36693dfab6f89d54f9 \ + --hash=sha256:e24d8031a2c62f34853756d9208eeafa6b940a1efcbfe36e8f57d99d52bb7261 \ + --hash=sha256:e36c80c49853b3ffda7aa1831bf175c13356b210c73128c861f3aa93c3cc4015 \ + --hash=sha256:e377e4cf8795cdbdff75b8f0223d7b6c68ff4fef36799d88ccf3a995a91c0112 \ + --hash=sha256:e3acb9c16530362aeaef4e84d57db357002dc5cbfac9a23414c3e73c08301ab2 \ + --hash=sha256:e3dc8d4ede2dbae6c0fc2b6c958bf51ce9fd7e9b40c0f5b8835c3fde44f5807d \ + --hash=sha256:e6491658dd2569f05860bad645569145c8626ac231877b0fb2d5f9bcb7054089 \ + --hash=sha256:eb91d252b35004a84670dfeafadb042528b19842a0080d8b53e5ec1128e8f433 \ + --hash=sha256:f0396e894bd1e66c74ecbc08b4f6a03dc331140942c4b1d345dd131b68574a60 \ + --hash=sha256:f09c9d4c26fa79c1bad927efb05aca2391350b8e61c38cbc0d7d3c814e463124 \ + --hash=sha256:f3cd110e02c5bf17d8fb562f6c9df5c20e73029d587cf8602a2da6c5ef1e32cb \ + --hash=sha256:f7a37dd208f0d658e0487522078b1ed68cd6bce20ef4b5a915d2809b9094b410 \ + --hash=sha256:fae4a01ef8c4cb2bbe92ef2063149596907dc4a881a8d26743b3f6b304713171 \ + --hash=sha256:fc327f4497b7087d06204235199daf208fd01c82d80465dc5efa4ec9df1c5b4e \ + --hash=sha256:fcc01c57ce6e70b728af02b2401c5bc853a9e14eb07deda30624374f0aebfe42 \ + --hash=sha256:fde355b02934cc6b07200cc3b27ab0c15870a757d1a72fd401aa92e2ea3c6bfe + # via referencing +ruff==0.5.5 \ + --hash=sha256:00817603822a3e42b80f7c3298c8269e09f889ee94640cd1fc7f9329788d7bf8 \ + --hash=sha256:187a60f555e9f865a2ff2c6984b9afeffa7158ba6e1eab56cb830404c942b0f3 \ + --hash=sha256:3191317d967af701f1b73a31ed5788795936e423b7acce82a2b63e26eb3e89d6 \ + --hash=sha256:3687d002f911e8a5faf977e619a034d159a8373514a587249cc00f211c67a091 \ + --hash=sha256:4ad25dd9c5faac95c8e9efb13e15803cd8bbf7f4600645a60ffe17c73f60779b \ + --hash=sha256:50f36d77f52d4c9c2f1361ccbfbd09099a1b2ea5d2b2222c586ab08885cf3445 \ + --hash=sha256:605d589ec35d1da9213a9d4d7e7a9c761d90bba78fc8790d1c5e65026c1b9eaf \ + --hash=sha256:696f18463b47a94575db635ebb4c178188645636f05e934fdf361b74edf1bb2d \ + --hash=sha256:a09b43e02f76ac0145f86a08e045e2ea452066f7ba064fd6b0cdccb486f7c3e7 \ + --hash=sha256:ac9dc814e510436e30d0ba535f435a7f3dc97f895f844f5b3f347ec8c228a523 \ + --hash=sha256:af9bdf6c389b5add40d89b201425b531e0a5cceb3cfdcc69f04d3d531c6be74f \ + --hash=sha256:cab904683bf9e2ecbbe9ff235bfe056f0eba754d0168ad5407832928d579e7ab \ + --hash=sha256:cc5516bdb4858d972fbc31d246bdb390eab8df1a26e2353be2dbc0c2d7f5421a \ + --hash=sha256:cfd7de17cef6ab559e9f5ab859f0d3296393bc78f69030967ca4d87a541b97a0 \ + --hash=sha256:d0b856cb19c60cd40198be5d8d4b556228e3dcd545b4f423d1ad812bfdca5884 \ + --hash=sha256:d40a8533ed545390ef8315b8e25c4bb85739b90bd0f3fe1280a29ae364cc55d8 \ + --hash=sha256:f70737c157d7edf749bcb952d13854e8f745cec695a01bdc6e29c29c288fc36e \ + --hash=sha256:fe26fc46fa8c6e0ae3f47ddccfbb136253c831c3289bba044befe68f467bfb16 + # via -r third_party/pip/requirements.in +six==1.17.0 \ + --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ + --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 + # via + # html5lib + # parse-type +sortedcontainers==2.4.0 \ + --hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \ + --hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0 + # via cyclonedx-python-lib +termcolor==2.0.0 \ + --hash=sha256:4889f2243b1da3934fc6cf4b57ee50a0ce98065ec06bfddac3fa6db24a9fcde2 \ + --hash=sha256:6e1a4b8e9c064ad8f9dfe2f9b35a7871d6ea5b6bb4f9da55bc0fd494b2302204 + # via -r third_party/pip/requirements.in +toml==0.10.2 \ + --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ + --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f + # via pip-audit +tomli==2.2.1 \ + --hash=sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6 \ + --hash=sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd \ + --hash=sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c \ + --hash=sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b \ + --hash=sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8 \ + --hash=sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6 \ + --hash=sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77 \ + --hash=sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff \ + --hash=sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea \ + --hash=sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192 \ + --hash=sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249 \ + --hash=sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee \ + --hash=sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4 \ + --hash=sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98 \ + --hash=sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8 \ + --hash=sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4 \ + --hash=sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281 \ + --hash=sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744 \ + --hash=sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69 \ + --hash=sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13 \ + --hash=sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140 \ + --hash=sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e \ + --hash=sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e \ + --hash=sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc \ + --hash=sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff \ + --hash=sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec \ + --hash=sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2 \ + --hash=sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222 \ + --hash=sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106 \ + --hash=sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272 \ + --hash=sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a \ + --hash=sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7 + # via + # black + # mypy + # pylint + # pytest +tomlkit==0.13.3 \ + --hash=sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1 \ + --hash=sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0 + # via pylint +types-certifi==2021.10.8.3 \ + --hash=sha256:72cf7798d165bc0b76e1c10dd1ea3097c7063c42c21d664523b928e88b554a4f \ + --hash=sha256:b2d1e325e69f71f7c78e5943d410e650b4707bb0ef32e4ddf3da37f54176e88a + # via -r third_party/pip/requirements.in +types-chardet==5.0.4.6 \ + --hash=sha256:caf4c74cd13ccfd8b3313c314aba943b159de562a2573ed03137402b2bb37818 \ + --hash=sha256:ea832d87e798abf1e4dfc73767807c2b7fee35d0003ae90348aea4ae00fb004d + # via -r third_party/pip/requirements.in +types-html5lib==1.1.11.20240806 \ + --hash=sha256:575c4fd84ba8eeeaa8520c7e4c7042b7791f5ec3e9c0a5d5c418124c42d9e7e4 \ + --hash=sha256:8060dc98baf63d6796a765bbbc809fff9f7a383f6e3a9add526f814c086545ef + # via -r third_party/pip/requirements.in +types-jsonschema==4.23.0.20240712 \ + --hash=sha256:8c33177ce95336241c1d61ccb56a9964d4361b99d5f1cd81a1ab4909b0dd7cf4 \ + --hash=sha256:b20db728dcf7ea3e80e9bdeb55e8b8420c6c040cda14e8cf284465adee71d217 + # via -r third_party/pip/requirements.in +types-python-dateutil==2.9.0.20240821 \ + --hash=sha256:9649d1dcb6fef1046fb18bebe9ea2aa0028b160918518c34589a46045f6ebd98 \ + --hash=sha256:f5889fcb4e63ed4aaa379b44f93c32593d50b9a94c9a60a0c854d8cc3511cd57 + # via -r third_party/pip/requirements.in +types-pyyaml==6.0.12.20240724 \ + --hash=sha256:cf7b31ae67e0c5b2919c703d2affc415485099d3fe6666a6912f040fd05cb67f \ + --hash=sha256:e5becec598f3aa3a2ddf671de4a75fa1c6856fbf73b2840286c9d50fae2d5d48 + # via -r third_party/pip/requirements.in +types-requests==2.32.0.20240712 \ + --hash=sha256:90c079ff05e549f6bf50e02e910210b98b8ff1ebdd18e19c873cd237737c1358 \ + --hash=sha256:f754283e152c752e46e70942fa2a146b5bc70393522257bb85bd1ef7e019dcc3 + # via -r third_party/pip/requirements.in +types-setuptools==72.2.0.20240821 \ + --hash=sha256:260e89d6d3b42cc35f9f0f382d030713b7b547344a664c05c9175e6ba124fac7 \ + --hash=sha256:e349b8015608879939f27ee370672f801287c46f5caa2d188d416336172c4965 + # via -r third_party/pip/requirements.in +types-six==1.16.21.20240513 \ + --hash=sha256:af2a105be6d504339bfed81319cc8e8697865f0ee5c6baa63658f127b33b9e63 \ + --hash=sha256:cdf445b5161bf17753500713a475ab79a45bd0d87728b8bfcecd86e2fbf66402 + # via -r third_party/pip/requirements.in +types-typed-ast==1.5.8.7 \ + --hash=sha256:97bdd9b4228f96c6904a76e10a050305ddadb529bd35e4d8234711e09c41b543 \ + --hash=sha256:f7795f6f9d597b35212314040b993f6613b51d81738edce3c1e3a3e9ef655124 + # via -r third_party/pip/requirements.in +types-urllib3==1.26.25.14 \ + --hash=sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f \ + --hash=sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e + # via -r third_party/pip/requirements.in +typing-extensions==4.14.1 \ + --hash=sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36 \ + --hash=sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76 + # via + # astroid + # black + # exceptiongroup + # mypy + # pylint + # pytest-bdd + # referencing +urllib3==2.5.0 \ + --hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \ + --hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc + # via + # requests + # types-requests +webencodings==0.5.1 \ + --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ + --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 + # via html5lib + +# The following packages are considered to be unsafe in a requirements file: +pip==25.2 \ + --hash=sha256:578283f006390f85bb6282dffb876454593d637f5d1be494b5202ce4877e71f2 \ + --hash=sha256:6d67a2b4e7f14d8b31b8b52648866fa717f45a1eb70e83002f4331d07e953717 + # via pip-api diff --git a/third_party/pip/test.sh b/third_party/pip/test.sh new file mode 100755 index 0000000..ff5d523 --- /dev/null +++ b/third_party/pip/test.sh @@ -0,0 +1,5 @@ +bazel run //third_party/pip:requirements_3_8_test --config=python_3_8 --config=use_workspace_mode +bazel run //third_party/pip:requirements_3_9_test --config=python_3_9 --config=use_workspace_mode +bazel run //third_party/pip:requirements_3_10_test --config=python_3_10 --config=use_workspace_mode +bazel run //third_party/pip:requirements_3_11_test --config=python_3_11 --config=use_workspace_mode +bazel run //third_party/pip:requirements_3_12_test --config=python_3_12 --config=use_workspace_mode diff --git a/third_party/pip/update.sh b/third_party/pip/update.sh new file mode 100755 index 0000000..5007fa3 --- /dev/null +++ b/third_party/pip/update.sh @@ -0,0 +1,5 @@ +bazel run //third_party/pip:requirements_3_8.update --config=python_3_8 --config=use_workspace_mode +bazel run //third_party/pip:requirements_3_9.update --config=python_3_9 --config=use_workspace_mode +bazel run //third_party/pip:requirements_3_10.update --config=python_3_10 --config=use_workspace_mode +bazel run //third_party/pip:requirements_3_11.update --config=python_3_11 --config=use_workspace_mode +bazel run //third_party/pip:requirements_3_12.update --config=python_3_12 --config=use_workspace_mode diff --git a/third_party/python_pip_hub.bzl b/third_party/python_pip_hub.bzl new file mode 100644 index 0000000..da0c4b8 --- /dev/null +++ b/third_party/python_pip_hub.bzl @@ -0,0 +1,29 @@ +# Copyright 2025 The BMW Group Authors. All rights reserved. + +"""Load and configure all pip requirements.""" + +load("@bazel_tools_python//bazel/rules:rules_python_pip_hub.bzl", "rules_python_pip_hub") +load("@bazel_tools_python//bazel/toolchains/python:versions.bzl", "PYTHON_VERSIONS") +load("@bazel_tools_python_pip_3_8//:requirements.bzl", pip_install_deps_py_3_8 = "install_deps") # buildifier: disable=out-of-order-load +load("@bazel_tools_python_pip_3_9//:requirements.bzl", pip_install_deps_py_3_9 = "install_deps") # buildifier: disable=out-of-order-load +load("@bazel_tools_python_pip_3_10//:requirements.bzl", pip_install_deps_py_3_10 = "install_deps") # buildifier: disable=out-of-order-load +load("@bazel_tools_python_pip_3_11//:requirements.bzl", pip_install_deps_py_3_11 = "install_deps") # buildifier: disable=out-of-order-load +load("@bazel_tools_python_pip_3_12//:requirements.bzl", pip_install_deps_py_3_12 = "install_deps") # buildifier: disable=out-of-order-load + +def python_pip_hub(): + """Load all rules python pip hub and configure our custom pip hub.""" + + pip_install_deps_py_3_8() + pip_install_deps_py_3_9() + pip_install_deps_py_3_10() + pip_install_deps_py_3_11() + pip_install_deps_py_3_12() + + rules_python_pip_hub( + name = "bazel_tools_python_pip_hub", + deps_to_config_map = { + "@bazel_tools_python_pip_{}".format(version.replace(".", "_")): "@bazel_tools_python//bazel/toolchains/python:python_{}".format(version.replace(".", "_")) + for version in PYTHON_VERSIONS + }, + requirements_in = "@bazel_tools_python//third_party/pip:requirements.in", + ) diff --git a/third_party/python_pip_parse.bzl b/third_party/python_pip_parse.bzl new file mode 100644 index 0000000..24efa3b --- /dev/null +++ b/third_party/python_pip_parse.bzl @@ -0,0 +1,32 @@ +# Copyright 2025 The BMW Group Authors. All rights reserved. + +"""Parse pip requirements files for supported python version.""" + +load("@bazel_tools_python_python_3_10//:defs.bzl", python_3_10_interpreter = "interpreter") +load("@bazel_tools_python_python_3_11//:defs.bzl", python_3_11_interpreter = "interpreter") +load("@bazel_tools_python_python_3_12//:defs.bzl", python_3_12_interpreter = "interpreter") +load("@bazel_tools_python_python_3_8//:defs.bzl", python_3_8_interpreter = "interpreter") +load("@bazel_tools_python_python_3_9//:defs.bzl", python_3_9_interpreter = "interpreter") +load("@rules_python//python:pip.bzl", "pip_parse") + +def python_pip_parse(): + """Parse pip requirements files for supported python version.""" + + # We can't dynamically load the interpreters so the following dict versions need to match + # "@bazel_tools_python//bazel/toolchains/python:versions.bzl%PYTHON_VERSIONS" + # buildifier: disable=unsorted-dict-items + python_versions = { + "3.8": python_3_8_interpreter, + "3.9": python_3_9_interpreter, + "3.10": python_3_10_interpreter, + "3.11": python_3_11_interpreter, + "3.12": python_3_12_interpreter, + } + + for version in python_versions: + pip_parse( + name = "bazel_tools_python_pip_{}".format(version.replace(".", "_")), + requirements_lock = "@bazel_tools_python//third_party/pip:requirements_lock_{}.txt".format(version.replace(".", "_")), + python_interpreter_target = python_versions[version], + extra_pip_args = ["--no-cache-dir"], + ) diff --git a/third_party/python_toolchains.bzl b/third_party/python_toolchains.bzl new file mode 100644 index 0000000..86c9b74 --- /dev/null +++ b/third_party/python_toolchains.bzl @@ -0,0 +1,25 @@ +# Copyright 2025 The BMW Group Authors. All rights reserved. + +"""Load all python toolchains dependencies.""" + +load("@bazel_tools_python//bazel/toolchains/python:versions.bzl", "PYTHON_VERSIONS") +load("@rules_python//python:repositories.bzl", "python_register_toolchains") + +def python_toolchains(): + """Load all python toolchains dependencies.""" + + for version in PYTHON_VERSIONS: + python_register_toolchains( + name = "bazel_tools_python_python_{}".format(version.replace(".", "_")), + python_version = version, + # There are use cases in which .pyc files in hermetic toolchains can + # cause cache misses when running a build as root. However, our CI + # uses root users and thus there's the need to disable this check. + # For more details see https://github.com/bazelbuild/rules_python/pull/713 + ignore_root_user_error = True, + # We want to use the coverage tool that is shipped with the toolchain. + register_coverage_tool = True, + # We want to register the toolchains by ourselves to be able to modify + # some of its content, for example, coverage bootstrap file. + register_toolchains = False, + ) diff --git a/third_party/rules_python/BUILD b/third_party/rules_python/BUILD new file mode 100644 index 0000000..881bc14 --- /dev/null +++ b/third_party/rules_python/BUILD @@ -0,0 +1,5 @@ +alias( + name = "runfiles", + actual = "@rules_python//python/runfiles", + visibility = ["//visibility:public"], +) diff --git a/third_party/rules_python/rules_python.bzl b/third_party/rules_python/rules_python.bzl new file mode 100644 index 0000000..a2327b2 --- /dev/null +++ b/third_party/rules_python/rules_python.bzl @@ -0,0 +1,16 @@ +"""Third-party dependency definition for rules_python""" + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") + +_VERSION = "0.40.0" # Update `README.md` if you change this. + +def rules_python(): + maybe( + http_archive, + name = "rules_python", + sha256 = "690e0141724abb568267e003c7b6d9a54925df40c275a870a4d934161dc9dd53", + strip_prefix = "rules_python-{version}".format(version = _VERSION), + urls = ["https://github.com/bazel-contrib/rules_python/releases/download/{version}/rules_python-{version}.tar.gz".format(version = _VERSION)], + patches = ["@bazel_tools_python//third_party/rules_python:rules_python.patch"], + ) diff --git a/third_party/rules_python/rules_python.patch b/third_party/rules_python/rules_python.patch new file mode 100644 index 0000000..bd6d7a8 --- /dev/null +++ b/third_party/rules_python/rules_python.patch @@ -0,0 +1,17 @@ +--- python/private/coverage.patch ++++ python/private/coverage.patch +@@ -15,3 +15,14 @@ + +sys.path.append(sys.path.pop(0)) + from coverage.cmdline import main + sys.exit(main()) ++ ++--- a/coverage/config.py +++++ b/coverage/config.py ++@@ -546,6 +546,7 @@ ++ if config_file == ".coveragerc": ++ config_file = True ++ specified_file = (config_file is not True) +++ specified_file = False ++ if not specified_file: ++ # No file was specified. Check COVERAGE_RCFILE. ++ rcfile = os.getenv("COVERAGE_RCFILE") From 0310e689fa98c1ab075b6d41fbc66231b2f324f5 Mon Sep 17 00:00:00 2001 From: "jan.nowotsch" Date: Thu, 31 Jul 2025 16:12:27 +0200 Subject: [PATCH 67/74] [tests] Add test workspace Add a separate bazel workspace intended for testing the bazel provided integrations. --- test/.bazelignore | 1 + test/.bazelrc | 35 +++++++++ test/.bazelversion | 1 + test/BUILD | 60 ++++++++++++++ test/MODULE.bazel | 75 ++++++++++++++++++ test/WORKSPACE | 116 ++++++++++++++++++++++++++++ test/WORKSPACE.bzlmod | 1 + test/awesome_pyproject.toml | 14 ++++ test/dummy_bin.py | 12 +++ test/dummy_lib.py | 10 +++ test/dummy_pytest.py | 17 ++++ test/dummy_test.py | 15 ++++ test/pip/BUILD | 25 ++++++ test/pip/extensions.bzl | 18 +++++ test/pip/requirements.in | 3 + test/pip/requirements_lock_3_10.txt | 74 ++++++++++++++++++ test/pip/requirements_lock_3_11.txt | 32 ++++++++ test/pip/requirements_lock_3_12.txt | 32 ++++++++ test/pip/requirements_lock_3_8.txt | 74 ++++++++++++++++++ test/pip/requirements_lock_3_9.txt | 74 ++++++++++++++++++ test/pip/test.sh | 5 ++ test/pip/update.sh | 5 ++ 22 files changed, 699 insertions(+) create mode 100644 test/.bazelignore create mode 100644 test/.bazelrc create mode 100644 test/.bazelversion create mode 100644 test/BUILD create mode 100644 test/MODULE.bazel create mode 100644 test/WORKSPACE create mode 100644 test/WORKSPACE.bzlmod create mode 100644 test/awesome_pyproject.toml create mode 100644 test/dummy_bin.py create mode 100644 test/dummy_lib.py create mode 100644 test/dummy_pytest.py create mode 100644 test/dummy_test.py create mode 100644 test/pip/BUILD create mode 100644 test/pip/extensions.bzl create mode 100644 test/pip/requirements.in create mode 100644 test/pip/requirements_lock_3_10.txt create mode 100644 test/pip/requirements_lock_3_11.txt create mode 100644 test/pip/requirements_lock_3_12.txt create mode 100644 test/pip/requirements_lock_3_8.txt create mode 100644 test/pip/requirements_lock_3_9.txt create mode 100755 test/pip/test.sh create mode 100755 test/pip/update.sh diff --git a/test/.bazelignore b/test/.bazelignore new file mode 100644 index 0000000..9edca79 --- /dev/null +++ b/test/.bazelignore @@ -0,0 +1 @@ +bazel-test diff --git a/test/.bazelrc b/test/.bazelrc new file mode 100644 index 0000000..4454896 --- /dev/null +++ b/test/.bazelrc @@ -0,0 +1,35 @@ +# Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG). All rights reserved. + +common --lockfile_mode=off +common --enable_platform_specific_config + +common:use_workspace_mode --noenable_bzlmod + +# Python toolchain configurations +common --flag_alias=python=@rules_python//python/config_settings:python_version +build:python_3_8 --python=3.8 +build:python_3_9 --python=3.9 +build:python_3_10 --python=3.10 +build:python_3_11 --python=3.11 +build:python_3_12 --python=3.12 +build --config=python_3_8 +build --@bazel_tools_python//quality:quality_pytest_config=//:awesome_pyproject +build --@bazel_tools_python//quality/private/python:py_test_deps=//:py_custom_test_deps + +build:pylint --output_groups=python_tool_output +build:pylint --aspects=@bazel_tools_python//quality:defs.bzl%pylint_aspect + +build:black --output_groups=python_tool_output +build:black --aspects=@bazel_tools_python//quality:defs.bzl%black_aspect + +build:isort --output_groups=python_tool_output +build:isort --aspects=@bazel_tools_python//quality:defs.bzl%black_aspect + +build:mypy --output_groups=python_tool_output +build:mypy --aspects=@bazel_tools_python//quality:defs.bzl%mypy_aspect + +build:ruff_check --output_groups=python_tool_output +build:ruff_check --aspects=@bazel_tools_python//quality:defs.bzl%ruff_check_aspect + +build:ruff_format --output_groups=python_tool_output +build:ruff_format --aspects=@bazel_tools_python//quality:defs.bzl%ruff_format_aspect diff --git a/test/.bazelversion b/test/.bazelversion new file mode 100644 index 0000000..18bb418 --- /dev/null +++ b/test/.bazelversion @@ -0,0 +1 @@ +7.5.0 diff --git a/test/BUILD b/test/BUILD new file mode 100644 index 0000000..1c7b6c9 --- /dev/null +++ b/test/BUILD @@ -0,0 +1,60 @@ +load("@bazel_tools_python//quality:defs.bzl", "py_pytest", "python_tool_config") +load("@rules_python//python:defs.bzl", "py_binary", "py_library") +load("@test_pip_hub//:loaders.bzl", "pkg") + +################ +# Config pytest. +################ + +python_tool_config( + name = "quality_python_default_config", + config = "//:awesome_pyproject", + visibility = ["//visibility:public"], +) + +label_flag( + name = "quality_pytest_config", + build_setting_default = "//:quality_python_default_config", + visibility = ["//visibility:public"], +) + +py_pytest( + name = "dummy_pytest", + srcs = ["dummy_pytest.py"], +) + +py_library( + name = "py_custom_test_deps", + visibility = ["//visibility:public"], + deps = [ + pkg("pytest"), + pkg("pytest-loop"), + ], +) + +filegroup( + name = "awesome_pyproject", + srcs = ["awesome_pyproject.toml"], + visibility = ["//visibility:public"], +) + +###################### +# Python test targets. +###################### + +py_binary( + name = "dummy_bin", + srcs = ["dummy_bin.py"], + deps = [":dummy_lib"], +) + +py_library( + name = "dummy_lib", + srcs = ["dummy_lib.py"], +) + +py_pytest( + name = "dummy_test", + srcs = ["dummy_test.py"], + deps = [":dummy_bin"], +) diff --git a/test/MODULE.bazel b/test/MODULE.bazel new file mode 100644 index 0000000..4d6eecc --- /dev/null +++ b/test/MODULE.bazel @@ -0,0 +1,75 @@ +module( + name = "bazel_tools_python_test", + version = "0.0.0", + compatibility_level = 1, +) + +########################## +# Internal dependencies. # +########################## + +bazel_dep(name = "rules_python", version = "1.4.1") + +###################################### +# Python toolchain and dependencies. # +###################################### + +PYTHON_VERSIONS = [ + "3.8", + "3.9", + "3.10", + "3.11", + "3.12", +] + +python = use_extension("@rules_python//python/extensions:python.bzl", "python") + +[ + python.toolchain( + configure_coverage_tool = True, + ignore_root_user_error = True, + is_default = False, + python_version = "{}".format(version), + ) + for version in PYTHON_VERSIONS +] + +# We can't use a loop for the following `use_repo` because we change the name of each toolchain. +# buildifier: leave-alone +use_repo( + python, + test_python_3_8 = "python_3_8", + test_python_3_9 = "python_3_9", + test_python_3_10 = "python_3_10", + test_python_3_11 = "python_3_11", + test_python_3_12 = "python_3_12", +) + +pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip") + +[ + pip.parse( + hub_name = "test_pip_{}".format(version.replace(".", "_")), + python_version = "{}".format(version), + requirements_lock = "//pip:requirements_lock_{}.txt".format(version.replace(".", "_")), + ) + for version in PYTHON_VERSIONS +] + +[ + use_repo(pip, "test_pip_{}".format(version.replace(".", "_"))) + for version in PYTHON_VERSIONS +] + +rules_python_pip_hub = use_extension("//pip:extensions.bzl", "rules_python_pip_hub") +use_repo(rules_python_pip_hub, "test_pip_hub") + +########################### +# Dependencies from bazel_tools_python. # +########################### + +bazel_dep(name = "bazel_tools_python", version = "3.6.0") +local_path_override( + module_name = "bazel_tools_python", + path = "..", +) diff --git a/test/WORKSPACE b/test/WORKSPACE new file mode 100644 index 0000000..ad95299 --- /dev/null +++ b/test/WORKSPACE @@ -0,0 +1,116 @@ +workspace(name = "bazel_tools_python_test") + +# This workspace loads local dependencies from bazel_tools_python for testing purposes. +# Ideally each user workspace will have its own set of local dependencies. + +local_repository( + name = "bazel_tools_python", + path = "..", +) + +########################## +# Internal dependencies. # +########################## + +load("@bazel_tools_python//third_party/bazel_skylib:bazel_skylib.bzl", "bazel_skylib") + +bazel_skylib() + +load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") + +bazel_skylib_workspace() + +load("@bazel_tools_python//third_party/rules_python:rules_python.bzl", "rules_python") + +rules_python() + +load("@rules_python//python:repositories.bzl", "py_repositories") + +py_repositories() + +###################################### +# Python toolchain and dependencies. # +###################################### + +load("@bazel_tools_python//bazel/toolchains/python:versions.bzl", "PYTHON_VERSIONS") +load("@rules_python//python:repositories.bzl", "python_register_toolchains") # buildifier: disable=same-origin-load + +[ + python_register_toolchains( + name = "test_python_{}".format(version.replace(".", "_")), + ignore_root_user_error = True, + python_version = "{}".format(version), + register_coverage_tool = True, + register_toolchains = True, + ) + for version in PYTHON_VERSIONS +] + +load("@rules_python//python:pip.bzl", "pip_parse") +load("@test_python_3_10//:defs.bzl", python_3_10_interpreter = "interpreter") # buildifier: disable=out-of-order-load +load("@test_python_3_11//:defs.bzl", python_3_11_interpreter = "interpreter") # buildifier: disable=out-of-order-load +load("@test_python_3_12//:defs.bzl", python_3_12_interpreter = "interpreter") # buildifier: disable=out-of-order-load +load("@test_python_3_8//:defs.bzl", python_3_8_interpreter = "interpreter") # buildifier: disable=out-of-order-load +load("@test_python_3_9//:defs.bzl", python_3_9_interpreter = "interpreter") # buildifier: disable=out-of-order-load + +# buildifier: disable=unsorted-dict-items +python_interpreter_versions = { + "3.8": python_3_8_interpreter, + "3.9": python_3_9_interpreter, + "3.10": python_3_10_interpreter, + "3.11": python_3_11_interpreter, + "3.12": python_3_12_interpreter, +} + +[ + pip_parse( + name = "test_pip_{}".format(version.replace(".", "_")), + extra_pip_args = ["--no-cache-dir"], + python_interpreter_target = python_interpreter_versions[version], + requirements_lock = "@bazel_tools_python_test//pip:requirements_lock_{}.txt".format(version.replace(".", "_")), + ) + for version in python_interpreter_versions +] + +load("@test_pip_3_10//:requirements.bzl", pip_install_deps_py_3_10 = "install_deps") # buildifier: disable=out-of-order-load +load("@test_pip_3_11//:requirements.bzl", pip_install_deps_py_3_11 = "install_deps") # buildifier: disable=out-of-order-load +load("@test_pip_3_12//:requirements.bzl", pip_install_deps_py_3_12 = "install_deps") # buildifier: disable=out-of-order-load +load("@test_pip_3_8//:requirements.bzl", pip_install_deps_py_3_8 = "install_deps") # buildifier: disable=out-of-order-load +load("@test_pip_3_9//:requirements.bzl", pip_install_deps_py_3_9 = "install_deps") # buildifier: disable=out-of-order-load + +pip_install_deps_py_3_8() + +pip_install_deps_py_3_9() + +pip_install_deps_py_3_10() + +pip_install_deps_py_3_11() + +pip_install_deps_py_3_12() + +load("@bazel_tools_python//bazel/rules:rules_python_pip_hub.bzl", "rules_python_pip_hub") + +rules_python_pip_hub( + name = "test_pip_hub", + deps_to_config_map = { + "@test_pip_{}".format(version.replace(".", "_")): "@bazel_tools_python//bazel/toolchains/python:python_{}".format(version.replace(".", "_")) + for version in PYTHON_VERSIONS + }, + requirements_in = "@bazel_tools_python_test//pip:requirements.in", +) + +########################### +# Dependencies from bazel_tools_python. # +########################### + +load("@bazel_tools_python//third_party:python_toolchains.bzl", bazel_tools_python_python_toolchains = "python_toolchains") + +bazel_tools_python_python_toolchains() + +load("@bazel_tools_python//third_party:python_pip_parse.bzl", bazel_tools_python_python_pip_parse = "python_pip_parse") + +bazel_tools_python_python_pip_parse() + +load("@bazel_tools_python//third_party:python_pip_hub.bzl", bazel_tools_python_python_pip_hub = "python_pip_hub") + +bazel_tools_python_python_pip_hub() diff --git a/test/WORKSPACE.bzlmod b/test/WORKSPACE.bzlmod new file mode 100644 index 0000000..b7db254 --- /dev/null +++ b/test/WORKSPACE.bzlmod @@ -0,0 +1 @@ +# Empty diff --git a/test/awesome_pyproject.toml b/test/awesome_pyproject.toml new file mode 100644 index 0000000..0ebd603 --- /dev/null +++ b/test/awesome_pyproject.toml @@ -0,0 +1,14 @@ +[tool.pytest.ini_options] +log_level = "NOTSET" + +addopts = [ + "-vv", + "--strict-markers", + "--strict-config", +] + +testpaths = ["tests"] + +required_plugins = [ + "pytest-loop", +] diff --git a/test/dummy_bin.py b/test/dummy_bin.py new file mode 100644 index 0000000..aa4af59 --- /dev/null +++ b/test/dummy_bin.py @@ -0,0 +1,12 @@ +"""Dummy python binary that helps testing.""" + +import dummy_lib + + +def run_foo(text: str) -> str: + """Dummy python function that helps testing.""" + dummy_lib.foo_not_allowed(text) + return text + + +run_foo("foo and bar") diff --git a/test/dummy_lib.py b/test/dummy_lib.py new file mode 100644 index 0000000..146f4de --- /dev/null +++ b/test/dummy_lib.py @@ -0,0 +1,10 @@ +"""Dummy python library that helps testing.""" + + +def foo_not_allowed(bar_not_allowed: str) -> str: + """Foo function that takes bar, prints it and then returns it. + + Pylint doesn't allow foo and bar by default, therefore we appended with 'not_allowed'. + """ + print(bar_not_allowed) + return bar_not_allowed diff --git a/test/dummy_pytest.py b/test/dummy_pytest.py new file mode 100644 index 0000000..ed1c594 --- /dev/null +++ b/test/dummy_pytest.py @@ -0,0 +1,17 @@ +"""Dummy python py_pytest that helps testing.""" + +import pytest + + +def add(a, b) -> int: + """Add function""" + return a + b + + +@pytest.mark.loop(3) +def test_add_function(): + """Test add function.""" + + assert add(2, 3) == 5 + assert add(2, 2) == 4 + assert add(1, 5) == 6 diff --git a/test/dummy_test.py b/test/dummy_test.py new file mode 100644 index 0000000..212a589 --- /dev/null +++ b/test/dummy_test.py @@ -0,0 +1,15 @@ +"""Dummy python unittest that helps testing.""" + +import unittest + +import dummy_bin + + +class TestDummyBinary(unittest.TestCase): + """TestDummyBinary.""" + + def test_run_foo(self): + """Test run_foo function.""" + variables = ["foo", "bar"] + for var in variables: + assert dummy_bin.run_foo(var) == var diff --git a/test/pip/BUILD b/test/pip/BUILD new file mode 100644 index 0000000..c3ac557 --- /dev/null +++ b/test/pip/BUILD @@ -0,0 +1,25 @@ +load("@bazel_tools_python//bazel/toolchains/python:versions.bzl", "PYTHON_VERSIONS") +load("@rules_python//python:pip.bzl", "compile_pip_requirements") + +# Rule compile_pip_requirements handles pip requirements. +# It has two important targets: +# - requirements.update: A runnable target that updates the lock file +# - requirements_test: A test target that validates the lock file + +REQUIREMENTS_SOURCE = "requirements.in" + +EXTRA_ARGS = [ + "--no-strip-extras", + "--quiet", +] + +[ + compile_pip_requirements( + name = "requirements_{}".format(version.replace(".", "_")), + src = REQUIREMENTS_SOURCE, + extra_args = EXTRA_ARGS, + requirements_txt = "requirements_lock_{}.txt".format(version.replace(".", "_")), + tags = ["manual"], + ) + for version in PYTHON_VERSIONS +] diff --git a/test/pip/extensions.bzl b/test/pip/extensions.bzl new file mode 100644 index 0000000..afba00a --- /dev/null +++ b/test/pip/extensions.bzl @@ -0,0 +1,18 @@ +"""Collection of the repository thid-party dependencies""" + +load("@bazel_tools_python//bazel/rules:rules_python_pip_hub.bzl", _rules_python_pip_hub = "rules_python_pip_hub") +load("@bazel_tools_python//bazel/toolchains/python:versions.bzl", "PYTHON_VERSIONS") + +def _rules_python_pip_hub_impl(): + """Make non module rules_python_pip_hub dependencies known to bazel.""" + + _rules_python_pip_hub( + name = "test_pip_hub", + deps_to_config_map = { + "@test_pip_{}".format(version.replace(".", "_")): "@bazel_tools_python//bazel/toolchains/python:python_{}".format(version.replace(".", "_")) + for version in PYTHON_VERSIONS + }, + requirements_in = "@bazel_tools_python_test//pip:requirements.in", + ) + +rules_python_pip_hub = module_extension(lambda ctx: _rules_python_pip_hub_impl()) diff --git a/test/pip/requirements.in b/test/pip/requirements.in new file mode 100644 index 0000000..80b5ac5 --- /dev/null +++ b/test/pip/requirements.in @@ -0,0 +1,3 @@ +termcolor==2.4.0 +pytest==8.1.1 +pytest-loop==1.0.13 diff --git a/test/pip/requirements_lock_3_10.txt b/test/pip/requirements_lock_3_10.txt new file mode 100644 index 0000000..8684d1b --- /dev/null +++ b/test/pip/requirements_lock_3_10.txt @@ -0,0 +1,74 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# bazel run //pip:requirements_3_10.update +# +exceptiongroup==1.3.0 \ + --hash=sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10 \ + --hash=sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88 + # via pytest +iniconfig==2.1.0 \ + --hash=sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7 \ + --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 + # via pytest +packaging==25.0 \ + --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ + --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f + # via pytest +pluggy==1.6.0 \ + --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ + --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + # via pytest +pytest==8.1.1 \ + --hash=sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7 \ + --hash=sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044 + # via + # -r pip/requirements.in + # pytest-loop +pytest-loop==1.0.13 \ + --hash=sha256:8d902c801413c5a16ee70d618259161a38f9f188f9d7a986ccc557deabca4f84 \ + --hash=sha256:be396c0293c4a23e9c96b99f696ad0afd6ae5e4b23d4ca0724eda0660530e5a0 + # via -r pip/requirements.in +termcolor==2.4.0 \ + --hash=sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63 \ + --hash=sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a + # via -r pip/requirements.in +tomli==2.2.1 \ + --hash=sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6 \ + --hash=sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd \ + --hash=sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c \ + --hash=sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b \ + --hash=sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8 \ + --hash=sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6 \ + --hash=sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77 \ + --hash=sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff \ + --hash=sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea \ + --hash=sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192 \ + --hash=sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249 \ + --hash=sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee \ + --hash=sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4 \ + --hash=sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98 \ + --hash=sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8 \ + --hash=sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4 \ + --hash=sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281 \ + --hash=sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744 \ + --hash=sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69 \ + --hash=sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13 \ + --hash=sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140 \ + --hash=sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e \ + --hash=sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e \ + --hash=sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc \ + --hash=sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff \ + --hash=sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec \ + --hash=sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2 \ + --hash=sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222 \ + --hash=sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106 \ + --hash=sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272 \ + --hash=sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a \ + --hash=sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7 + # via pytest +typing-extensions==4.14.1 \ + --hash=sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36 \ + --hash=sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76 + # via exceptiongroup diff --git a/test/pip/requirements_lock_3_11.txt b/test/pip/requirements_lock_3_11.txt new file mode 100644 index 0000000..5e8162f --- /dev/null +++ b/test/pip/requirements_lock_3_11.txt @@ -0,0 +1,32 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# bazel run //pip:requirements_3_11.update +# +iniconfig==2.1.0 \ + --hash=sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7 \ + --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 + # via pytest +packaging==25.0 \ + --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ + --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f + # via pytest +pluggy==1.6.0 \ + --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ + --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + # via pytest +pytest==8.1.1 \ + --hash=sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7 \ + --hash=sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044 + # via + # -r pip/requirements.in + # pytest-loop +pytest-loop==1.0.13 \ + --hash=sha256:8d902c801413c5a16ee70d618259161a38f9f188f9d7a986ccc557deabca4f84 \ + --hash=sha256:be396c0293c4a23e9c96b99f696ad0afd6ae5e4b23d4ca0724eda0660530e5a0 + # via -r pip/requirements.in +termcolor==2.4.0 \ + --hash=sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63 \ + --hash=sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a + # via -r pip/requirements.in diff --git a/test/pip/requirements_lock_3_12.txt b/test/pip/requirements_lock_3_12.txt new file mode 100644 index 0000000..7517e7c --- /dev/null +++ b/test/pip/requirements_lock_3_12.txt @@ -0,0 +1,32 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# bazel run //pip:requirements_3_12.update +# +iniconfig==2.1.0 \ + --hash=sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7 \ + --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 + # via pytest +packaging==25.0 \ + --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ + --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f + # via pytest +pluggy==1.6.0 \ + --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ + --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + # via pytest +pytest==8.1.1 \ + --hash=sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7 \ + --hash=sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044 + # via + # -r pip/requirements.in + # pytest-loop +pytest-loop==1.0.13 \ + --hash=sha256:8d902c801413c5a16ee70d618259161a38f9f188f9d7a986ccc557deabca4f84 \ + --hash=sha256:be396c0293c4a23e9c96b99f696ad0afd6ae5e4b23d4ca0724eda0660530e5a0 + # via -r pip/requirements.in +termcolor==2.4.0 \ + --hash=sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63 \ + --hash=sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a + # via -r pip/requirements.in diff --git a/test/pip/requirements_lock_3_8.txt b/test/pip/requirements_lock_3_8.txt new file mode 100644 index 0000000..60d8bf0 --- /dev/null +++ b/test/pip/requirements_lock_3_8.txt @@ -0,0 +1,74 @@ +# +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: +# +# bazel run //pip:requirements_3_8.update +# +exceptiongroup==1.3.0 \ + --hash=sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10 \ + --hash=sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88 + # via pytest +iniconfig==2.1.0 \ + --hash=sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7 \ + --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 + # via pytest +packaging==25.0 \ + --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ + --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f + # via pytest +pluggy==1.5.0 \ + --hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \ + --hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669 + # via pytest +pytest==8.1.1 \ + --hash=sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7 \ + --hash=sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044 + # via + # -r pip/requirements.in + # pytest-loop +pytest-loop==1.0.13 \ + --hash=sha256:8d902c801413c5a16ee70d618259161a38f9f188f9d7a986ccc557deabca4f84 \ + --hash=sha256:be396c0293c4a23e9c96b99f696ad0afd6ae5e4b23d4ca0724eda0660530e5a0 + # via -r pip/requirements.in +termcolor==2.4.0 \ + --hash=sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63 \ + --hash=sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a + # via -r pip/requirements.in +tomli==2.2.1 \ + --hash=sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6 \ + --hash=sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd \ + --hash=sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c \ + --hash=sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b \ + --hash=sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8 \ + --hash=sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6 \ + --hash=sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77 \ + --hash=sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff \ + --hash=sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea \ + --hash=sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192 \ + --hash=sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249 \ + --hash=sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee \ + --hash=sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4 \ + --hash=sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98 \ + --hash=sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8 \ + --hash=sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4 \ + --hash=sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281 \ + --hash=sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744 \ + --hash=sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69 \ + --hash=sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13 \ + --hash=sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140 \ + --hash=sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e \ + --hash=sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e \ + --hash=sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc \ + --hash=sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff \ + --hash=sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec \ + --hash=sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2 \ + --hash=sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222 \ + --hash=sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106 \ + --hash=sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272 \ + --hash=sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a \ + --hash=sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7 + # via pytest +typing-extensions==4.13.2 \ + --hash=sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c \ + --hash=sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef + # via exceptiongroup diff --git a/test/pip/requirements_lock_3_9.txt b/test/pip/requirements_lock_3_9.txt new file mode 100644 index 0000000..1a3272a --- /dev/null +++ b/test/pip/requirements_lock_3_9.txt @@ -0,0 +1,74 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# bazel run //pip:requirements_3_9.update +# +exceptiongroup==1.3.0 \ + --hash=sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10 \ + --hash=sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88 + # via pytest +iniconfig==2.1.0 \ + --hash=sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7 \ + --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 + # via pytest +packaging==25.0 \ + --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ + --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f + # via pytest +pluggy==1.6.0 \ + --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ + --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + # via pytest +pytest==8.1.1 \ + --hash=sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7 \ + --hash=sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044 + # via + # -r pip/requirements.in + # pytest-loop +pytest-loop==1.0.13 \ + --hash=sha256:8d902c801413c5a16ee70d618259161a38f9f188f9d7a986ccc557deabca4f84 \ + --hash=sha256:be396c0293c4a23e9c96b99f696ad0afd6ae5e4b23d4ca0724eda0660530e5a0 + # via -r pip/requirements.in +termcolor==2.4.0 \ + --hash=sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63 \ + --hash=sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a + # via -r pip/requirements.in +tomli==2.2.1 \ + --hash=sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6 \ + --hash=sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd \ + --hash=sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c \ + --hash=sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b \ + --hash=sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8 \ + --hash=sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6 \ + --hash=sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77 \ + --hash=sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff \ + --hash=sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea \ + --hash=sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192 \ + --hash=sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249 \ + --hash=sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee \ + --hash=sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4 \ + --hash=sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98 \ + --hash=sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8 \ + --hash=sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4 \ + --hash=sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281 \ + --hash=sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744 \ + --hash=sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69 \ + --hash=sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13 \ + --hash=sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140 \ + --hash=sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e \ + --hash=sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e \ + --hash=sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc \ + --hash=sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff \ + --hash=sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec \ + --hash=sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2 \ + --hash=sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222 \ + --hash=sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106 \ + --hash=sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272 \ + --hash=sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a \ + --hash=sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7 + # via pytest +typing-extensions==4.14.1 \ + --hash=sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36 \ + --hash=sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76 + # via exceptiongroup diff --git a/test/pip/test.sh b/test/pip/test.sh new file mode 100755 index 0000000..20a5819 --- /dev/null +++ b/test/pip/test.sh @@ -0,0 +1,5 @@ +bazel run //pip:requirements_3_8_test --config=python_3_8 --noenable_bzlmod +bazel run //pip:requirements_3_9_test --config=python_3_9 --noenable_bzlmod +bazel run //pip:requirements_3_10_test --config=python_3_10 --noenable_bzlmod +bazel run //pip:requirements_3_11_test --config=python_3_11 --noenable_bzlmod +bazel run //pip:requirements_3_12_test --config=python_3_12 --noenable_bzlmod diff --git a/test/pip/update.sh b/test/pip/update.sh new file mode 100755 index 0000000..c875765 --- /dev/null +++ b/test/pip/update.sh @@ -0,0 +1,5 @@ +bazel run //pip:requirements_3_8.update --config=python_3_8 --noenable_bzlmod +bazel run //pip:requirements_3_9.update --config=python_3_9 --noenable_bzlmod +bazel run //pip:requirements_3_10.update --config=python_3_10 --noenable_bzlmod +bazel run //pip:requirements_3_11.update --config=python_3_11 --noenable_bzlmod +bazel run //pip:requirements_3_12.update --config=python_3_12 --noenable_bzlmod From 2ce932186092f50633ad3f47dae047f890b1cb70 Mon Sep 17 00:00:00 2001 From: "jan.nowotsch" Date: Thu, 31 Jul 2025 16:13:41 +0200 Subject: [PATCH 68/74] [tests] Add run_all_tests.sh Add scripts to run linters, formatters and tests. --- scripts/run_all_tests.sh | 85 ++++++++++++++++++++++++++++++++++++++++ test/run_all_tests.sh | 78 ++++++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100755 scripts/run_all_tests.sh create mode 100755 test/run_all_tests.sh diff --git a/scripts/run_all_tests.sh b/scripts/run_all_tests.sh new file mode 100755 index 0000000..e549b2a --- /dev/null +++ b/scripts/run_all_tests.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +set -u + +GREEN='\033[0;32m' +RED='\033[0;31m' +RESET='\033[0m' + + +declare -A command_status +declare -a command_order +exit_code=0 + + +interrupt_handler(){ + echo -e "${RED}Script interrupted by user.${RESET}" + exit 130 # Exit code for script interruption by Ctrl+C +} + +trap interrupt_handler SIGINT + +run_command(){ + local cmd=$1 + local name=$2 + + echo -e "Running $name ..." + + if eval "$cmd"; then + echo -e "${GREEN}$name SUCCEEDED${RESET}" + command_status["$name"]="SUCCEEDED" + else + echo -e "${RED}$name FAILED${RESET}" + command_status["$name"]="FAILED" + exit_code=1 + fi + + command_order+=("$name") +} + +# Run bazel test with workspace mode and python 3.9. +run_command "bazel --output_base=$HOME/.cache/bazel_tools_python/workspace_output_base test --config=use_workspace_mode --config=python_3_9 //..." "tests (workspace mode and python 3.9)" + +# Run bazel test with bzlmod mode and python 3.12. +run_command "bazel --output_base=$HOME/.cache/bazel_tools_python/python_3_12_output_base test --config=python_3_12 //..." "tests (bzlmod mode and python 3.12)" + +# Run bazel test with bzlmod mode. +run_command "bazel test //..." "tests (bzlmod mode)" + +# Run python quality tools. +run_command "bazel build --config=ruff_check --keep_going //..." "ruff_check" +run_command "bazel build --config=ruff_format --keep_going //..." "ruff_format" +run_command "bazel build --config=pylint --keep_going //..." "pylint" +run_command "bazel build --config=black --keep_going //..." "black" +run_command "bazel build --config=isort --keep_going //..." "isort" +run_command "bazel build --config=mypy --keep_going //..." "mypy" + +# Run test workspace tests. +run_command "test/run_all_tests.sh" "tests (in test workspace)" + +# Run buildifier. +run_command "bazel run bazel/buildifier:check" "buildifier" + +# Run Eclipse-specific checks. +run_command "bazel run //:copyright.check -- --fix" "eclipse copyright check" +run_command "bazel test //:format.check" "eclipse format check" + +# Run security vulnerability scan. +run_command "third_party/pip/check_vulnerabilities.sh" "security scan" + +# Print execution summary +printf '%-37s | %-10s\n' "Command Name" "Status" +printf '%-37s | %-10s\n' "-------------------------------------" "----------" + +for name in "${command_order[@]}"; do + status="${command_status[$name]}" + + if [[ "$status" == "SUCCEEDED" ]]; then + printf "%-37s | ${GREEN}%-10s${RESET}\n" "$name" "$status" + else + printf "%-37s | ${RED}%-10s${RESET}\n" "$name" "$status" + fi +done + +printf '%-37s | %-10s\n' "-------------------------------------" "----------" + +exit $exit_code diff --git a/test/run_all_tests.sh b/test/run_all_tests.sh new file mode 100755 index 0000000..c63f152 --- /dev/null +++ b/test/run_all_tests.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +set -u + +GREEN='\033[0;32m' +RED='\033[0;31m' +RESET='\033[0m' + +BZL_WORSKPACE_OUTPUT_BASE="$HOME/.cache/bazel_tools_python_tests/workspace_output_base" + + +declare -A command_status +declare -a command_order +exit_code=0 + + +interrupt_handler(){ + echo -e "${RED}Script interrupted by user.${RESET}" + exit 130 # Exit code for script interruption by Ctrl+C +} + +trap interrupt_handler SIGINT + +run_command(){ + local cmd=$1 + local name=$2 + + echo -e "Running $name ..." + + if eval "$cmd"; then + echo -e "${GREEN}$name SUCCEEDED${RESET}" + command_status["$name"]="SUCCEEDED" + else + echo -e "${RED}$name FAILED${RESET}" + command_status["$name"]="FAILED" + exit_code=1 + fi + + command_order+=("$name") +} + +# Ensure the following commands are run from the test workspace. +cd $(dirname $0) + +# Run checks with bzlmod mode. +run_command "bazel test //..." "tests (bzlmod mode)" +run_command "bazel build --config=ruff_check --keep_going //..." "ruff_check (bzlmod mode)" +run_command "bazel build --config=ruff_format --keep_going //..." "ruff_format (bzlmod mode)" +run_command "bazel build --config=pylint --keep_going //..." "pylint (bzlmod mode)" +run_command "bazel build --config=black --keep_going //..." "black (bzlmod mode)" +run_command "bazel build --config=isort --keep_going //..." "isort (bzlmod mode)" +run_command "bazel build --config=mypy --keep_going //..." "mypy (bzlmod mode)" + +# Run checks in workspace mode +run_command "bazel --output_base=${BZL_WORSKPACE_OUTPUT_BASE} test --config=use_workspace_mode //..." "tests (workspace mode)" +run_command "bazel --output_base=${BZL_WORSKPACE_OUTPUT_BASE} build --config=use_workspace_mode --config=ruff_check --keep_going //..." "ruff_check (workspace mode)" +run_command "bazel --output_base=${BZL_WORSKPACE_OUTPUT_BASE} build --config=use_workspace_mode --config=ruff_format --keep_going //..." "ruff_format (workspace mode)" +run_command "bazel --output_base=${BZL_WORSKPACE_OUTPUT_BASE} build --config=use_workspace_mode --config=pylint --keep_going //..." "pylint (workspace mode)" +run_command "bazel --output_base=${BZL_WORSKPACE_OUTPUT_BASE} build --config=use_workspace_mode --config=black --keep_going //..." "black (workspace mode)" +run_command "bazel --output_base=${BZL_WORSKPACE_OUTPUT_BASE} build --config=use_workspace_mode --config=isort --keep_going //..." "isort (workspace mode)" +run_command "bazel --output_base=${BZL_WORSKPACE_OUTPUT_BASE} build --config=use_workspace_mode --config=mypy --keep_going //..." "mypy (workspace mode)" + +# Print execution summary +printf '%-37s | %-10s\n' "Command Name" "Status" +printf '%-37s | %-10s\n' "-------------------------------------" "----------" + +for name in "${command_order[@]}"; do + status="${command_status[$name]}" + + if [[ "$status" == "SUCCEEDED" ]]; then + printf "%-37s | ${GREEN}%-10s${RESET}\n" "$name" "$status" + else + printf "%-37s | ${RED}%-10s${RESET}\n" "$name" "$status" + fi +done + +printf '%-37s | %-10s\n' "-------------------------------------" "----------" + +exit $exit_code From b2a810e926e031657686776b802dbf4a95388a10 Mon Sep 17 00:00:00 2001 From: "jan.nowotsch" Date: Thu, 31 Jul 2025 16:15:24 +0200 Subject: [PATCH 69/74] [docu] Add user and contributor docs Add user-facing documentation with the README.md and contributor-focused documentation with the CONTRIBUTING.md. --- CONTRIBUTING.md | 68 ++++++++++++++++++++++++++++++++ README.md | 101 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 CONTRIBUTING.md create mode 100644 README.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6e57cdc --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,68 @@ +# Eclipse Safe Open Vehicle Core (SCORE) +The [Eclipse Safe Open Vehicle Core](https://projects.eclipse.org/projects/automotive.score) project aims to develop an open-source core stack for Software Defined Vehicles (SDVs), specifically targeting embedded high-performance Electronic Control Units (ECUs). +Please check the [documentation](https://eclipse-score.github.io) for more information. +The source code is hosted at [GitHub](https://github.com/eclipse-score). + +The communication mainly takes place via the [`score-dev` mailing list](https://accounts.eclipse.org/mailing-list/score-dev) and GitHub issues & pull requests (PR). And we have a chatroom for community discussions here [Eclipse SCORE chatroom](https://chat.eclipse.org/#/room/#automotive.score:matrix.eclipse.org). + +Please note that for the project the [Eclipse Foundation’s Terms of Use](https://www.eclipse.org/legal/terms-of-use/) apply. +In addition, you need to sign the [ECA](https://www.eclipse.org/legal/ECA.php) and the [DCO](https://www.eclipse.org/legal/dco/) to contribute to the project. + +## Contributing + +Want to contribute? You're welcoe and we're happy to accept your pull requests! + +- [Development](#development) + - [Updating python dependencies](#updating-python-dependencies) + - [Local quality check](#local-quality-check) + +### Getting involved + +#### Setup Phase +This phase is part of the eclipse Incubation Phase and shall establish all the processes needed for a safe development of functions. Only after this phase it will be possible to contribute code to the project. As the development in this project is driven by requirements, the processes and needed infrastructure incl. tooling will be established based on non-functional Stakeholder_Requirements. During setup phase the contributions are Bug Fixes and Improvements (both on processes and infrastructure). + +#### Bug Fixes and Improvements +Improvements are adding/changing processes and infrastructure, bug fixes can be also on development work products like code. +In case you want to fix a bug or contribute an improvement, please perform the following steps: +1) Create a PR by using the corresponding template ([Bugfix PR template](.github/PULL_REQUEST_TEMPLATE/bug_fix.md) or [Improvement PR template](.github/PULL_REQUEST_TEMPLATE/improvement.md)). Please mark your PR as draft until it's ready for review by the Committers (see the [Eclipse Foundation Project Handbook](https://www.eclipse.org/projects/handbook/#contributing-committers) for more information on the role definitions). +2) Initiate content review by opening a corresponding issue for the PR when it is ready for review. Review of the PR and final merge into the project repository is in responsibility of the Committers. Use the [Bugfix Issue template](.github/ISSUE_TEMPLATE/bug_fix.md) or [Improvement Issue template](.github/ISSUE_TEMPLATE/improvement.md) for this. + +Please check here for our Git Commit Rules in the [Configuration_Tool_Guidelines](https://eclipse-score.github.io/score/process_description/guidelines/index.html). + +Please use the [Stakeholder and Tool Requirements Template](https://eclipse-score.github.io/score/process_description/templates/index.html) when defining these requirements. + +#### Additional Information +Please note, that all Git commit messages must adhere the rules described in the [Eclipse Foundation Project Handbook](https://www.eclipse.org/projects/handbook/#resources-commit). + +Please find process descriptions here: [process description](https://eclipse-score.github.io/score/process_description/). + +### Development + +#### Updating python dependencies + +This repository uses Bazel `rules_python` pip integration plus a [custom pip hub implementation](bazel/rules/rules_python_pip_hub.bzl). Therefore, to add, remove or modify python pip dependencies, one should do as follows: + +1. Update the dependency and its version under [requirements.in](third_party/pip/requirements.in); +2. Lock pip requirements by executing `bazel run //third_party/pip:update.sh`. This will update all `requirements_lock_3_*.txt` files under `third_party/pip`; +3. Test the updated requirements by executing `bazel run //third_party/pip:test.sh`. + +After this was successfully done, it is possible to load pip packges into bazel targets. To load the pip dependency package itself (common use case), one can use our pip hub `pkg` alias. + +```python +load("@bazel_tools_python_pip_hub//:loaders.bzl", "pkg") + +py_binary( + ... + deps = [ + pkg("pip_package_name"), + ], + ... +) +``` + +Other options are also available through other pip hub aliases, for example, the dependency data can be accessed using the `data` alias. + +#### Local quality check + +Ideally, one should verify code quality locally before pushing changes to the CI. This avoids unecessary CI jobs and can even speed up development. +To do that, simply run [`scripts/run_all_tests.sh`](scripts/run_all_tests.sh). diff --git a/README.md b/README.md new file mode 100644 index 0000000..b8aa97c --- /dev/null +++ b/README.md @@ -0,0 +1,101 @@ +# Bazel Rules Quality Python (bazel_tools_python) + +This repository contains bazel integrations for python quality tools. + +- [Offered Tools](#offered-tools) +- [How to use bazel_tools_python](#how-to-use-bazel-tools-python) + - [Requirements](#requirements) + - [Select python pip hub version](#select-python-pip-hub-version) + - [Using WORKSPACE](#using-workspace) + - [Using bzlmod](#using-bzlmod) +- [Contributing](#contributing) + +## Offered Tools + +Each offered tool has it own README document and can be independently activated and configured. + +| Tool | Tool Type | Bazel Type | Supported Languages | Workspace | Bzlmod | +| :---------------------------------------------: | :---------------------: | :--------: | :-----------------: | :-------: | :----: | +| [pylint](quality/private/python/README.md) | Code linter | aspect | Python | yes | no | +| [ruff-check](quality/private/python/README.md) | Code linter | aspect | Python | yes | yes | +| [mypy](quality/private/python/README.md) | Code type linter | aspect | Python | yes | yes | +| [black](quality/private/python/README.md) | Code formatter | aspect | Python | yes | yes | +| [ruff-format](quality/private/python/README.md) | Code formatter | aspect | Python | yes | yes | +| [isort](quality/private/python/README.md) | Import formatter | aspect | Python | yes | yes | +| [pytest](quality/private/python/README.md) | Test Framework | aspect | Python | yes | yes | +| [pycoverage](quality/private/python/README.md) | Code coverage tool | aspect | Python | yes | yes | +| [pip_audit](quality/private/python/README.md) | Vulnerability checker | rule | Python | yes | yes | + +## How to use bazel_tools_python + +The next sections explain the steps to achieve a proper config for each Bazel dependency manager. For a fully working setup, take a look at the [test workspace](test) as an example. + +### Requirements + +It's important to note that this repository does not supply python toolchains but only its pip dependencies. Therefore one must set up its own python toolchain. This repository support major python versions from `3.8` to `3.12`. + +Additionaly, one must have the following bazel repositories already in place: + +- bazel_skylib >= 1.7.1 +- rules_python >= 0.40.0 + +### Select python pip hub version + +To select the correct python dependency version one only needs to set a `string_flag` under one's `.bazelrc` file. + +For example if one is using python `3.10`, one should select our python `3.10` dependencies by adding the following lines to the respective `.bazelrc` file. + +```python +# .bazelrc + +common --flag_alias=python=@rules_python//python/config_settings:python_version +common --python=3.10 +``` + +### Using WORKSPACE + +Add this to your `WORKSPACE` file. + +```python +# WORKSPACE + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "bazel_tools_python", + sha256 = "", + urls = ["/bazel-tools-python//bazel-tools-python-.tar.gz"], +) + +load("@bazel_tools_python//third_party:python_toolchains.bzl", bazel_tools_python_python_toolchains = "python_toolchains") + +bazel_tools_python_python_toolchains() + +load("@bazel_tools_python//third_party:python_pip_parse.bzl", bazel_tools_python_python_pip_parse = "python_pip_parse") + +bazel_tools_python_python_pip_parse() + +load("@bazel_tools_python//third_party:python_pip_hub.bzl", bazel_tools_python_python_pip_hub = "python_pip_hub") + +bazel_tools_python_python_pip_hub() +``` + +### Using bzlmod + +Add this to your `MODULE.bazel` file. + +```python +# MODULE.bazel + +bazel_dep(name = "bazel_tools_python", version = "") +``` + +To generate Bazel integrity value, one can use the following command: + +```sh +openssl dgst -sha256 -binary bazel-tools-python-.tar.gz | openssl base64 -A | sed 's/^/sha256-/' +``` + +## Contributing + +Please check our [contributing guide](CONTRIBUTING.md). From 424c235a161e113f40fcded3d9ccd6f3926a88e5 Mon Sep 17 00:00:00 2001 From: "jan.nowotsch" Date: Sat, 9 Aug 2025 01:17:04 +0200 Subject: [PATCH 70/74] [copyright] Replace copyright headers Replace the existing copyright header with the eclipse one. --- bazel/buildifier/BUILD | 12 ++++++++++++ bazel/rules/rules_python_pip_hub.bzl | 12 ++++++++++++ bazel/toolchains/python/BUILD | 12 ++++++++++++ bazel/toolchains/python/versions.bzl | 13 ++++++++++++- bazel/toolchains/toolchains.bzl | 13 ++++++++++++- quality/BUILD | 13 ++++++++++++- quality/defs.bzl | 13 ++++++++++++- quality/private/python/BUILD | 12 ++++++++++++ quality/private/python/black_entry_point.py | 12 ++++++++++++ quality/private/python/isort_entry_point.py | 12 ++++++++++++ quality/private/python/mypy_entry_point.py | 12 ++++++++++++ quality/private/python/pip_audit_entry_point.py | 12 ++++++++++++ quality/private/python/py_pytest.bzl | 12 ++++++++++++ quality/private/python/pycoverage/BUILD | 13 ++++++++++++- .../private/python/pycoverage/output_generator.py | 13 ++++++++++++- .../private/python/pycoverage/report_generator.py | 13 ++++++++++++- quality/private/python/pycoverage/test/BUILD | 13 ++++++++++++- .../test/test_pycoverage_output_generator.py | 13 ++++++++++++- .../test/test_pycoverage_report_generator.py | 13 ++++++++++++- quality/private/python/pycoverage_entry_point.py | 12 ++++++++++++ quality/private/python/pylint_entry_point.py | 12 ++++++++++++ quality/private/python/python_collect_aspect.bzl | 12 ++++++++++++ quality/private/python/python_helper.bzl | 12 ++++++++++++ quality/private/python/python_pip_audit_rule.bzl | 12 ++++++++++++ quality/private/python/python_providers.bzl | 12 ++++++++++++ quality/private/python/python_tool_aspect.bzl | 12 ++++++++++++ quality/private/python/ruff_entry_point.py | 12 ++++++++++++ quality/private/python/support/BUILD | 12 ++++++++++++ quality/private/python/tools/BUILD | 12 ++++++++++++ quality/private/python/tools/black_runner.py | 12 ++++++++++++ quality/private/python/tools/isort_runner.py | 12 ++++++++++++ quality/private/python/tools/mypy_runner.py | 12 ++++++++++++ quality/private/python/tools/pylint_runner.py | 12 ++++++++++++ quality/private/python/tools/pytest_runner.py | 12 ++++++++++++ quality/private/python/tools/python_tool_common.py | 12 ++++++++++++ quality/private/python/tools/ruff_check_runner.py | 12 ++++++++++++ quality/private/python/tools/ruff_format_runner.py | 12 ++++++++++++ quality/private/python/tools/test/BUILD | 12 ++++++++++++ quality/private/python/tools/test/conftest.py | 12 ++++++++++++ .../private/python/tools/test/test_black_runner.py | 12 ++++++++++++ .../private/python/tools/test/test_isort_runner.py | 12 ++++++++++++ .../private/python/tools/test/test_mypy_runner.py | 12 ++++++++++++ .../private/python/tools/test/test_pylint_runner.py | 12 ++++++++++++ .../python/tools/test/test_python_tool_common.py | 12 ++++++++++++ .../python/tools/test/test_ruff_check_runner.py | 12 ++++++++++++ .../python/tools/test/test_ruff_format_runner.py | 12 ++++++++++++ scripts/run_all_tests.sh | 12 ++++++++++++ test/.bazelrc | 2 -- test/BUILD | 12 ++++++++++++ test/MODULE.bazel | 12 ++++++++++++ test/dummy_bin.py | 12 ++++++++++++ test/dummy_lib.py | 12 ++++++++++++ test/dummy_pytest.py | 12 ++++++++++++ test/dummy_test.py | 12 ++++++++++++ test/pip/BUILD | 12 ++++++++++++ test/pip/extensions.bzl | 12 ++++++++++++ test/pip/test.sh | 12 ++++++++++++ test/pip/update.sh | 12 ++++++++++++ test/run_all_tests.sh | 12 ++++++++++++ third_party/bazel_skylib/bazel_skylib.bzl | 12 ++++++++++++ third_party/buildifier/buildifier.bzl | 12 ++++++++++++ third_party/extensions.bzl | 12 ++++++++++++ third_party/internal_dependencies.bzl | 12 ++++++++++++ third_party/internal_transitive_dependencies.bzl | 12 ++++++++++++ third_party/pip/BUILD | 12 ++++++++++++ third_party/pip/check_vulnerabilities.sh | 12 ++++++++++++ third_party/pip/test.sh | 12 ++++++++++++ third_party/pip/update.sh | 12 ++++++++++++ third_party/python_pip_hub.bzl | 13 ++++++++++++- third_party/python_pip_parse.bzl | 13 ++++++++++++- third_party/python_toolchains.bzl | 13 ++++++++++++- third_party/rules_python/BUILD | 12 ++++++++++++ third_party/rules_python/rules_python.bzl | 12 ++++++++++++ 73 files changed, 864 insertions(+), 15 deletions(-) diff --git a/bazel/buildifier/BUILD b/bazel/buildifier/BUILD index ebe596d..d847be1 100644 --- a/bazel/buildifier/BUILD +++ b/bazel/buildifier/BUILD @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* load("@buildifier_prebuilt//:rules.bzl", "buildifier") BUILDIFIER_EXCLUDE_PATTERNS = [ diff --git a/bazel/rules/rules_python_pip_hub.bzl b/bazel/rules/rules_python_pip_hub.bzl index 51a8ee3..71425dd 100644 --- a/bazel/rules/rules_python_pip_hub.bzl +++ b/bazel/rules/rules_python_pip_hub.bzl @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Rule that creates an auxiliary pip hub. Using a pip hub is already a default workflow for rules_python pip integration. We create diff --git a/bazel/toolchains/python/BUILD b/bazel/toolchains/python/BUILD index 17d5d87..c3f78a6 100644 --- a/bazel/toolchains/python/BUILD +++ b/bazel/toolchains/python/BUILD @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* load("@bazel_skylib//rules:common_settings.bzl", "bool_flag", "bool_setting") load("@bazel_tools_python//bazel/toolchains/python:versions.bzl", "PYTHON_VERSIONS", "unsupported_python_configuration_error") load("@rules_python//python:defs.bzl", "py_runtime_pair") diff --git a/bazel/toolchains/python/versions.bzl b/bazel/toolchains/python/versions.bzl index 08e644f..24f7c9b 100644 --- a/bazel/toolchains/python/versions.bzl +++ b/bazel/toolchains/python/versions.bzl @@ -1,4 +1,15 @@ -# Copyright 2025 The BMW Group Authors. All rights reserved. +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """This module defines which python version are supported.""" diff --git a/bazel/toolchains/toolchains.bzl b/bazel/toolchains/toolchains.bzl index eebad97..40d2494 100644 --- a/bazel/toolchains/toolchains.bzl +++ b/bazel/toolchains/toolchains.bzl @@ -1,4 +1,15 @@ -# Copyright 2025 The BMW Group Authors. All rights reserved. +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Repository toolchain collection.""" diff --git a/quality/BUILD b/quality/BUILD index 2c62678..7409253 100644 --- a/quality/BUILD +++ b/quality/BUILD @@ -1,4 +1,15 @@ -# Copyright 2022 The BMW Group Authors. All rights reserved. +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* load("@bazel_tools_python//quality:defs.bzl", "python_tool_config") diff --git a/quality/defs.bzl b/quality/defs.bzl index 6183c8c..4f5d671 100644 --- a/quality/defs.bzl +++ b/quality/defs.bzl @@ -1,4 +1,15 @@ -# Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG). All rights reserved. +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """ Defines all public symbols for this modules to be used by the specific project. diff --git a/quality/private/python/BUILD b/quality/private/python/BUILD index 919a4c6..95cb405 100644 --- a/quality/private/python/BUILD +++ b/quality/private/python/BUILD @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* load("@bazel_tools_python_pip_hub//:loaders.bzl", "pkg") load("@rules_python//python:defs.bzl", "py_binary", "py_library") diff --git a/quality/private/python/black_entry_point.py b/quality/private/python/black_entry_point.py index c69f927..8d41428 100644 --- a/quality/private/python/black_entry_point.py +++ b/quality/private/python/black_entry_point.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Entry point for black python library. This executes black by importing, thus executing, its main entry point. diff --git a/quality/private/python/isort_entry_point.py b/quality/private/python/isort_entry_point.py index 2b2e0df..669e5eb 100644 --- a/quality/private/python/isort_entry_point.py +++ b/quality/private/python/isort_entry_point.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Entry point for isort python library. This executes isort by importing, thus executing, its main entry point. diff --git a/quality/private/python/mypy_entry_point.py b/quality/private/python/mypy_entry_point.py index 5e0624e..63889f8 100644 --- a/quality/private/python/mypy_entry_point.py +++ b/quality/private/python/mypy_entry_point.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Entry point for mypy python library. This executes mypy by importing, thus executing, its main entry point. diff --git a/quality/private/python/pip_audit_entry_point.py b/quality/private/python/pip_audit_entry_point.py index 5262de4..406b42b 100644 --- a/quality/private/python/pip_audit_entry_point.py +++ b/quality/private/python/pip_audit_entry_point.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Entry point for pip-audit python library. This executes pip-audit by importing, thus executing, its main entry point. diff --git a/quality/private/python/py_pytest.bzl b/quality/private/python/py_pytest.bzl index 533cbcd..db7fb8c 100644 --- a/quality/private/python/py_pytest.bzl +++ b/quality/private/python/py_pytest.bzl @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Custom py_test rule that is compatible with pytest.""" load("@rules_python//python:defs.bzl", "py_test") diff --git a/quality/private/python/pycoverage/BUILD b/quality/private/python/pycoverage/BUILD index d12613a..a7d9183 100644 --- a/quality/private/python/pycoverage/BUILD +++ b/quality/private/python/pycoverage/BUILD @@ -1,4 +1,15 @@ -# Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG). All rights reserved. +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* load("@bazel_tools_python_pip_hub//:loaders.bzl", "pkg") load("@rules_python//python:defs.bzl", "py_binary") diff --git a/quality/private/python/pycoverage/output_generator.py b/quality/private/python/pycoverage/output_generator.py index 110cd67..2ef172f 100755 --- a/quality/private/python/pycoverage/output_generator.py +++ b/quality/private/python/pycoverage/output_generator.py @@ -1,4 +1,15 @@ -# Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG). All rights reserved. +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Custom coverage report generator for pycoverage.""" diff --git a/quality/private/python/pycoverage/report_generator.py b/quality/private/python/pycoverage/report_generator.py index ff8da66..832c19f 100755 --- a/quality/private/python/pycoverage/report_generator.py +++ b/quality/private/python/pycoverage/report_generator.py @@ -1,4 +1,15 @@ -# Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG). All rights reserved. +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Custom coverage report generator for pycoverage.""" diff --git a/quality/private/python/pycoverage/test/BUILD b/quality/private/python/pycoverage/test/BUILD index faee4da..e41f9e1 100644 --- a/quality/private/python/pycoverage/test/BUILD +++ b/quality/private/python/pycoverage/test/BUILD @@ -1,4 +1,15 @@ -# Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG). All rights reserved. +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* load("@bazel_tools_python//quality:defs.bzl", "py_pytest") load("@bazel_tools_python_pip_hub//:loaders.bzl", "pkg") diff --git a/quality/private/python/pycoverage/test/test_pycoverage_output_generator.py b/quality/private/python/pycoverage/test/test_pycoverage_output_generator.py index 1689148..e3289af 100644 --- a/quality/private/python/pycoverage/test/test_pycoverage_output_generator.py +++ b/quality/private/python/pycoverage/test/test_pycoverage_output_generator.py @@ -1,4 +1,15 @@ -# Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG). All rights reserved. +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Tests for the pycoverage output_generator module.""" diff --git a/quality/private/python/pycoverage/test/test_pycoverage_report_generator.py b/quality/private/python/pycoverage/test/test_pycoverage_report_generator.py index 69fde5f..545bf2c 100644 --- a/quality/private/python/pycoverage/test/test_pycoverage_report_generator.py +++ b/quality/private/python/pycoverage/test/test_pycoverage_report_generator.py @@ -1,4 +1,15 @@ -# Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG). All rights reserved. +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Tests for the pycoverage report_generator module.""" diff --git a/quality/private/python/pycoverage_entry_point.py b/quality/private/python/pycoverage_entry_point.py index f30fea4..29ca049 100644 --- a/quality/private/python/pycoverage_entry_point.py +++ b/quality/private/python/pycoverage_entry_point.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Entry point for pycoverage. This executes pycoverage by importing, thus executing, its main entry point. diff --git a/quality/private/python/pylint_entry_point.py b/quality/private/python/pylint_entry_point.py index 066fa17..b4e1a51 100644 --- a/quality/private/python/pylint_entry_point.py +++ b/quality/private/python/pylint_entry_point.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Entry point for pylint python library. This executes pylint by importing, thus executing, its main entry point. diff --git a/quality/private/python/python_collect_aspect.bzl b/quality/private/python/python_collect_aspect.bzl index 2715e9a..cd41fdf 100644 --- a/quality/private/python/python_collect_aspect.bzl +++ b/quality/private/python/python_collect_aspect.bzl @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Aspect that collects python targets information and output it to a provider.""" load("@bazel_tools_python//quality/private/python:python_helper.bzl", "is_valid_label") diff --git a/quality/private/python/python_helper.bzl b/quality/private/python/python_helper.bzl index b9bb806..8add533 100644 --- a/quality/private/python/python_helper.bzl +++ b/quality/private/python/python_helper.bzl @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """A collection of helper functions for the python aspect.""" _excluded_main_label_names = ["rules_python_entry_point_"] diff --git a/quality/private/python/python_pip_audit_rule.bzl b/quality/private/python/python_pip_audit_rule.bzl index 072313b..3418b21 100644 --- a/quality/private/python/python_pip_audit_rule.bzl +++ b/quality/private/python/python_pip_audit_rule.bzl @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Executable rule that runs pip-audit on a requirements file.""" def _pip_audit_rule_impl(ctx): diff --git a/quality/private/python/python_providers.bzl b/quality/private/python/python_providers.bzl index dcee8f0..e6fdf51 100644 --- a/quality/private/python/python_providers.bzl +++ b/quality/private/python/python_providers.bzl @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """This module defines the offered providers of the python aspect.""" PythonCollectInfo = provider( diff --git a/quality/private/python/python_tool_aspect.bzl b/quality/private/python/python_tool_aspect.bzl index bf58323..275ff97 100644 --- a/quality/private/python/python_tool_aspect.bzl +++ b/quality/private/python/python_tool_aspect.bzl @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Aspect that call a tool runner on top of a python target.""" load("@bazel_tools_python//quality/private/python:python_collect_aspect.bzl", "python_collect_aspect") diff --git a/quality/private/python/ruff_entry_point.py b/quality/private/python/ruff_entry_point.py index f346f03..1671281 100644 --- a/quality/private/python/ruff_entry_point.py +++ b/quality/private/python/ruff_entry_point.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Entry point for ruff python library. This executes ruff as a subprocess by finding its binary, forwarding every diff --git a/quality/private/python/support/BUILD b/quality/private/python/support/BUILD index d7c608a..5ec3b5f 100644 --- a/quality/private/python/support/BUILD +++ b/quality/private/python/support/BUILD @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* filegroup( name = "pyproject_toml", srcs = ["pyproject.toml"], diff --git a/quality/private/python/tools/BUILD b/quality/private/python/tools/BUILD index 4dbbeca..fc93b3c 100644 --- a/quality/private/python/tools/BUILD +++ b/quality/private/python/tools/BUILD @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* load("@bazel_tools_python_pip_hub//:loaders.bzl", "pkg") load("@rules_python//python:defs.bzl", "py_binary", "py_library") diff --git a/quality/private/python/tools/black_runner.py b/quality/private/python/tools/black_runner.py index ca409c4..3469932 100644 --- a/quality/private/python/tools/black_runner.py +++ b/quality/private/python/tools/black_runner.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """A runner that interfaces python tool aspect and runs black on a list of files.""" import pathlib diff --git a/quality/private/python/tools/isort_runner.py b/quality/private/python/tools/isort_runner.py index 41edf3e..5467f1f 100644 --- a/quality/private/python/tools/isort_runner.py +++ b/quality/private/python/tools/isort_runner.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """A runner that interfaces python tool aspect and runs isort on a list of files.""" import pathlib diff --git a/quality/private/python/tools/mypy_runner.py b/quality/private/python/tools/mypy_runner.py index 108e9fd..b7ab7fb 100644 --- a/quality/private/python/tools/mypy_runner.py +++ b/quality/private/python/tools/mypy_runner.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """A runner that interfaces python tool aspect and runs mypy on a list of files.""" import pathlib diff --git a/quality/private/python/tools/pylint_runner.py b/quality/private/python/tools/pylint_runner.py index 805b678..2bbb8ff 100644 --- a/quality/private/python/tools/pylint_runner.py +++ b/quality/private/python/tools/pylint_runner.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """A runner that interfaces python tool aspect and runs pylint on a list of files.""" import pathlib diff --git a/quality/private/python/tools/pytest_runner.py b/quality/private/python/tools/pytest_runner.py index f10d05c..c0f87e2 100644 --- a/quality/private/python/tools/pytest_runner.py +++ b/quality/private/python/tools/pytest_runner.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Pytest wrapper.""" import os diff --git a/quality/private/python/tools/python_tool_common.py b/quality/private/python/tools/python_tool_common.py index 58fa6cd..9dc8e67 100644 --- a/quality/private/python/tools/python_tool_common.py +++ b/quality/private/python/tools/python_tool_common.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Common features for runners that interface the python tool aspect.""" import argparse diff --git a/quality/private/python/tools/ruff_check_runner.py b/quality/private/python/tools/ruff_check_runner.py index 9a73286..70eb59b 100644 --- a/quality/private/python/tools/ruff_check_runner.py +++ b/quality/private/python/tools/ruff_check_runner.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """A runner that interfaces python tool aspect and runs ruff on a list of files.""" import json diff --git a/quality/private/python/tools/ruff_format_runner.py b/quality/private/python/tools/ruff_format_runner.py index daf9d32..7e50435 100644 --- a/quality/private/python/tools/ruff_format_runner.py +++ b/quality/private/python/tools/ruff_format_runner.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """A runner that interfaces python tool aspect and runs ruff on a list of files.""" import pathlib diff --git a/quality/private/python/tools/test/BUILD b/quality/private/python/tools/test/BUILD index 468849c..e4cfcd4 100644 --- a/quality/private/python/tools/test/BUILD +++ b/quality/private/python/tools/test/BUILD @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* load("@bazel_tools_python//quality:defs.bzl", "py_pytest") load("@bazel_tools_python_pip_hub//:loaders.bzl", "pkg") load("@rules_python//python:defs.bzl", "py_library") diff --git a/quality/private/python/tools/test/conftest.py b/quality/private/python/tools/test/conftest.py index 3c71c6a..f978da0 100644 --- a/quality/private/python/tools/test/conftest.py +++ b/quality/private/python/tools/test/conftest.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """pytest fixtures for the config-generator test""" import pathlib diff --git a/quality/private/python/tools/test/test_black_runner.py b/quality/private/python/tools/test/test_black_runner.py index 8de759d..7e1fbb1 100644 --- a/quality/private/python/tools/test/test_black_runner.py +++ b/quality/private/python/tools/test/test_black_runner.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Tests for the black runner.""" import json diff --git a/quality/private/python/tools/test/test_isort_runner.py b/quality/private/python/tools/test/test_isort_runner.py index e0d0e97..347bf38 100644 --- a/quality/private/python/tools/test/test_isort_runner.py +++ b/quality/private/python/tools/test/test_isort_runner.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Tests for the isort runner.""" import json diff --git a/quality/private/python/tools/test/test_mypy_runner.py b/quality/private/python/tools/test/test_mypy_runner.py index da84682..d6a29b3 100644 --- a/quality/private/python/tools/test/test_mypy_runner.py +++ b/quality/private/python/tools/test/test_mypy_runner.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Tests for the mypy runner.""" import pathlib diff --git a/quality/private/python/tools/test/test_pylint_runner.py b/quality/private/python/tools/test/test_pylint_runner.py index de338ba..d7914a0 100644 --- a/quality/private/python/tools/test/test_pylint_runner.py +++ b/quality/private/python/tools/test/test_pylint_runner.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Tests for the pylint runner.""" import json diff --git a/quality/private/python/tools/test/test_python_tool_common.py b/quality/private/python/tools/test/test_python_tool_common.py index 1958bb3..0e34b9b 100644 --- a/quality/private/python/tools/test/test_python_tool_common.py +++ b/quality/private/python/tools/test/test_python_tool_common.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Tests for the python tool common.""" import filecmp diff --git a/quality/private/python/tools/test/test_ruff_check_runner.py b/quality/private/python/tools/test/test_ruff_check_runner.py index 3567738..beaab95 100644 --- a/quality/private/python/tools/test/test_ruff_check_runner.py +++ b/quality/private/python/tools/test/test_ruff_check_runner.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Tests for the ruff_check runner.""" import pathlib diff --git a/quality/private/python/tools/test/test_ruff_format_runner.py b/quality/private/python/tools/test/test_ruff_format_runner.py index d5a0cca..e113081 100644 --- a/quality/private/python/tools/test/test_ruff_format_runner.py +++ b/quality/private/python/tools/test/test_ruff_format_runner.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Tests for the ruff_format runner.""" import pathlib diff --git a/scripts/run_all_tests.sh b/scripts/run_all_tests.sh index e549b2a..9b2f00d 100755 --- a/scripts/run_all_tests.sh +++ b/scripts/run_all_tests.sh @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* #!/usr/bin/env bash set -u diff --git a/test/.bazelrc b/test/.bazelrc index 4454896..bf3095b 100644 --- a/test/.bazelrc +++ b/test/.bazelrc @@ -1,5 +1,3 @@ -# Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG). All rights reserved. - common --lockfile_mode=off common --enable_platform_specific_config diff --git a/test/BUILD b/test/BUILD index 1c7b6c9..09d7dba 100644 --- a/test/BUILD +++ b/test/BUILD @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* load("@bazel_tools_python//quality:defs.bzl", "py_pytest", "python_tool_config") load("@rules_python//python:defs.bzl", "py_binary", "py_library") load("@test_pip_hub//:loaders.bzl", "pkg") diff --git a/test/MODULE.bazel b/test/MODULE.bazel index 4d6eecc..1f9b992 100644 --- a/test/MODULE.bazel +++ b/test/MODULE.bazel @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* module( name = "bazel_tools_python_test", version = "0.0.0", diff --git a/test/dummy_bin.py b/test/dummy_bin.py index aa4af59..f14d803 100644 --- a/test/dummy_bin.py +++ b/test/dummy_bin.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Dummy python binary that helps testing.""" import dummy_lib diff --git a/test/dummy_lib.py b/test/dummy_lib.py index 146f4de..59fe559 100644 --- a/test/dummy_lib.py +++ b/test/dummy_lib.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Dummy python library that helps testing.""" diff --git a/test/dummy_pytest.py b/test/dummy_pytest.py index ed1c594..6cd20ec 100644 --- a/test/dummy_pytest.py +++ b/test/dummy_pytest.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Dummy python py_pytest that helps testing.""" import pytest diff --git a/test/dummy_test.py b/test/dummy_test.py index 212a589..856f3f1 100644 --- a/test/dummy_test.py +++ b/test/dummy_test.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Dummy python unittest that helps testing.""" import unittest diff --git a/test/pip/BUILD b/test/pip/BUILD index c3ac557..ec80edc 100644 --- a/test/pip/BUILD +++ b/test/pip/BUILD @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* load("@bazel_tools_python//bazel/toolchains/python:versions.bzl", "PYTHON_VERSIONS") load("@rules_python//python:pip.bzl", "compile_pip_requirements") diff --git a/test/pip/extensions.bzl b/test/pip/extensions.bzl index afba00a..45c48a5 100644 --- a/test/pip/extensions.bzl +++ b/test/pip/extensions.bzl @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Collection of the repository thid-party dependencies""" load("@bazel_tools_python//bazel/rules:rules_python_pip_hub.bzl", _rules_python_pip_hub = "rules_python_pip_hub") diff --git a/test/pip/test.sh b/test/pip/test.sh index 20a5819..5c56229 100755 --- a/test/pip/test.sh +++ b/test/pip/test.sh @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* bazel run //pip:requirements_3_8_test --config=python_3_8 --noenable_bzlmod bazel run //pip:requirements_3_9_test --config=python_3_9 --noenable_bzlmod bazel run //pip:requirements_3_10_test --config=python_3_10 --noenable_bzlmod diff --git a/test/pip/update.sh b/test/pip/update.sh index c875765..a833ad4 100755 --- a/test/pip/update.sh +++ b/test/pip/update.sh @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* bazel run //pip:requirements_3_8.update --config=python_3_8 --noenable_bzlmod bazel run //pip:requirements_3_9.update --config=python_3_9 --noenable_bzlmod bazel run //pip:requirements_3_10.update --config=python_3_10 --noenable_bzlmod diff --git a/test/run_all_tests.sh b/test/run_all_tests.sh index c63f152..9379b26 100755 --- a/test/run_all_tests.sh +++ b/test/run_all_tests.sh @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* #!/usr/bin/env bash set -u diff --git a/third_party/bazel_skylib/bazel_skylib.bzl b/third_party/bazel_skylib/bazel_skylib.bzl index b9c5eed..e9c9e15 100644 --- a/third_party/bazel_skylib/bazel_skylib.bzl +++ b/third_party/bazel_skylib/bazel_skylib.bzl @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Third-party dependency definition for bazel_skylib""" load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") diff --git a/third_party/buildifier/buildifier.bzl b/third_party/buildifier/buildifier.bzl index 9944721..7c76dd7 100644 --- a/third_party/buildifier/buildifier.bzl +++ b/third_party/buildifier/buildifier.bzl @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Third-party dependency definition for buildifier.""" load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") diff --git a/third_party/extensions.bzl b/third_party/extensions.bzl index c1d7ae9..6f8e81a 100644 --- a/third_party/extensions.bzl +++ b/third_party/extensions.bzl @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Collection of the repository thid-party extensions.""" load("@bazel_tools_python//bazel/rules:rules_python_pip_hub.bzl", _rules_python_pip_hub = "rules_python_pip_hub") diff --git a/third_party/internal_dependencies.bzl b/third_party/internal_dependencies.bzl index 0ad478c..6fbe5b8 100644 --- a/third_party/internal_dependencies.bzl +++ b/third_party/internal_dependencies.bzl @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Collection of the repository internal thid-party dependencies.""" load("@bazel_tools_python//third_party/bazel_skylib:bazel_skylib.bzl", "bazel_skylib") diff --git a/third_party/internal_transitive_dependencies.bzl b/third_party/internal_transitive_dependencies.bzl index 66ffd89..56ca87b 100644 --- a/third_party/internal_transitive_dependencies.bzl +++ b/third_party/internal_transitive_dependencies.bzl @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """First set of internal transitive dependencies required for this module.""" load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") diff --git a/third_party/pip/BUILD b/third_party/pip/BUILD index 6c327f7..2fdd1ec 100644 --- a/third_party/pip/BUILD +++ b/third_party/pip/BUILD @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* load("@bazel_tools_python//bazel/toolchains/python:versions.bzl", "PYTHON_VERSIONS") load("@bazel_tools_python//quality:defs.bzl", "pip_audit_rule") load("@rules_python//python:pip.bzl", "compile_pip_requirements") diff --git a/third_party/pip/check_vulnerabilities.sh b/third_party/pip/check_vulnerabilities.sh index fd18a74..32b9b47 100755 --- a/third_party/pip/check_vulnerabilities.sh +++ b/third_party/pip/check_vulnerabilities.sh @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* bazel run //third_party/pip:pip_audit_requirements_3_8 bazel run //third_party/pip:pip_audit_requirements_3_9 bazel run //third_party/pip:pip_audit_requirements_3_10 diff --git a/third_party/pip/test.sh b/third_party/pip/test.sh index ff5d523..09a77b1 100755 --- a/third_party/pip/test.sh +++ b/third_party/pip/test.sh @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* bazel run //third_party/pip:requirements_3_8_test --config=python_3_8 --config=use_workspace_mode bazel run //third_party/pip:requirements_3_9_test --config=python_3_9 --config=use_workspace_mode bazel run //third_party/pip:requirements_3_10_test --config=python_3_10 --config=use_workspace_mode diff --git a/third_party/pip/update.sh b/third_party/pip/update.sh index 5007fa3..ed6a450 100755 --- a/third_party/pip/update.sh +++ b/third_party/pip/update.sh @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* bazel run //third_party/pip:requirements_3_8.update --config=python_3_8 --config=use_workspace_mode bazel run //third_party/pip:requirements_3_9.update --config=python_3_9 --config=use_workspace_mode bazel run //third_party/pip:requirements_3_10.update --config=python_3_10 --config=use_workspace_mode diff --git a/third_party/python_pip_hub.bzl b/third_party/python_pip_hub.bzl index da0c4b8..8a733c0 100644 --- a/third_party/python_pip_hub.bzl +++ b/third_party/python_pip_hub.bzl @@ -1,4 +1,15 @@ -# Copyright 2025 The BMW Group Authors. All rights reserved. +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Load and configure all pip requirements.""" diff --git a/third_party/python_pip_parse.bzl b/third_party/python_pip_parse.bzl index 24efa3b..370029e 100644 --- a/third_party/python_pip_parse.bzl +++ b/third_party/python_pip_parse.bzl @@ -1,4 +1,15 @@ -# Copyright 2025 The BMW Group Authors. All rights reserved. +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Parse pip requirements files for supported python version.""" diff --git a/third_party/python_toolchains.bzl b/third_party/python_toolchains.bzl index 86c9b74..09dc93a 100644 --- a/third_party/python_toolchains.bzl +++ b/third_party/python_toolchains.bzl @@ -1,4 +1,15 @@ -# Copyright 2025 The BMW Group Authors. All rights reserved. +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Load all python toolchains dependencies.""" diff --git a/third_party/rules_python/BUILD b/third_party/rules_python/BUILD index 881bc14..4bec28c 100644 --- a/third_party/rules_python/BUILD +++ b/third_party/rules_python/BUILD @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* alias( name = "runfiles", actual = "@rules_python//python/runfiles", diff --git a/third_party/rules_python/rules_python.bzl b/third_party/rules_python/rules_python.bzl index a2327b2..d4023d5 100644 --- a/third_party/rules_python/rules_python.bzl +++ b/third_party/rules_python/rules_python.bzl @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """Third-party dependency definition for rules_python""" load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") From 891787cfcbdd38e425db989cf4a68c5529459d16 Mon Sep 17 00:00:00 2001 From: "jan.nowotsch" Date: Mon, 11 Aug 2025 08:15:44 +0200 Subject: [PATCH 71/74] [buildsys] Remove starpls Remove the starlark language server since the underlying rule is broken. When attempting to invoke the stetup_starpls rule bazel reports "declared output 'starpls_server_bin' was not created by genrule". Since the starpls language server is anyhow not used by the project it is fine to remove it. --- BUILD | 6 ------ 1 file changed, 6 deletions(-) diff --git a/BUILD b/BUILD index dcdc8c6..7d6d594 100644 --- a/BUILD +++ b/BUILD @@ -14,14 +14,8 @@ load("@score_cr_checker//:cr_checker.bzl", "copyright_checker") load("@score_dash_license_checker//:dash.bzl", "dash_license_checker") load("@score_docs_as_code//:docs.bzl", "docs") load("@score_format_checker//:macros.bzl", "use_format_targets") -load("@score_starpls_lsp//:starpls.bzl", "setup_starpls") load("//:project_config.bzl", "PROJECT_CONFIG") -setup_starpls( - name = "starpls_server", - visibility = ["//visibility:public"], -) - copyright_checker( name = "copyright", srcs = [ From 8d37a2beaf5dc9934fa26f22441dbb4fa9c27345 Mon Sep 17 00:00:00 2001 From: "jan.nowotsch" Date: Tue, 12 Aug 2025 16:36:29 +0200 Subject: [PATCH 72/74] [buildsys] Remove score sphinx docs targets Remove the score targets that build the sphinx documentation. Those targets only work for python 3.12. Since the project has support for python version from 3.8 up to 3.12 we also run tests for each of the versions. However, the score docs targets do not allow to restrict them to only being build for 3.12, hence they are disabled for now. On top of that those targets take way too many resources to build even though it doesn't actually have much content. With too many resources meaning over an hour and up to 40GB memory. --- BUILD | 5 ----- scripts/run_all_tests.sh | 3 ++- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/BUILD b/BUILD index 7d6d594..cf7e1e2 100644 --- a/BUILD +++ b/BUILD @@ -12,7 +12,6 @@ # ******************************************************************************* load("@score_cr_checker//:cr_checker.bzl", "copyright_checker") load("@score_dash_license_checker//:dash.bzl", "dash_license_checker") -load("@score_docs_as_code//:docs.bzl", "docs") load("@score_format_checker//:macros.bzl", "use_format_targets") load("//:project_config.bzl", "PROJECT_CONFIG") @@ -42,10 +41,6 @@ dash_license_checker( # Add target for formatting checks use_format_targets() -docs( - source_dir = "docs", -) - exports_files([ "pyproject.toml", ]) diff --git a/scripts/run_all_tests.sh b/scripts/run_all_tests.sh index 9b2f00d..e26095b 100755 --- a/scripts/run_all_tests.sh +++ b/scripts/run_all_tests.sh @@ -49,7 +49,8 @@ run_command(){ } # Run bazel test with workspace mode and python 3.9. -run_command "bazel --output_base=$HOME/.cache/bazel_tools_python/workspace_output_base test --config=use_workspace_mode --config=python_3_9 //..." "tests (workspace mode and python 3.9)" +# TODO re-enable once the score targets (like //:docs) support bazel workspace mode and more than python 3.12 +# run_command "bazel --output_base=$HOME/.cache/bazel_tools_python/workspace_output_base test --config=use_workspace_mode --config=python_3_9 //..." "tests (workspace mode and python 3.9)" # Run bazel test with bzlmod mode and python 3.12. run_command "bazel --output_base=$HOME/.cache/bazel_tools_python/python_3_12_output_base test --config=python_3_12 //..." "tests (bzlmod mode and python 3.12)" From fd9c5cf3ae4050411b4c61aaacf7e7ec38ba6b5f Mon Sep 17 00:00:00 2001 From: "jan.nowotsch" Date: Tue, 12 Aug 2025 16:41:46 +0200 Subject: [PATCH 73/74] [security] Add a SECURITY.md file It contains all information regarding secutiry and vulnerabilities for this project. --- SECURITY.md | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..2236322 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,65 @@ +# Security Policy + +## Supported Versions + +We actively support the current version and maintain the previous version with hotfixes for vulnerabilities that may be reported by our users. + +| Version | Support stage | End of support | +| ------- | ------------- | -------------- | +| 3.x.x | Active | Not planned | +| 2.x.x | Maintenance | Dec 2025 | +| 1.x.x | Deprecated | Dec 2024 | +| 0.x.x | Deprecated | Dec 2024 | + +## Reporting a Vulnerability + +If you discover a security vulnerability in this project, please report it responsibly: + +1. Create a [new discussion](https://github.com/eclipse-score/bazel-tools-python/issues) +2. Label it with either `security` or `vulnerability` tags +3. Include all necessary information following the discussion template + +## Security Best Practices + +### For Contributors + +- **Never commit secrets, tokens, or credentials** +- **Use secure coding practices** +- **Keep dependencies up to date** +- **Follow the principle of least privilege** +- **Validate all inputs** + +### For Users + +- **Always use the latest supported version** +- **Keep your environment updated** +- **Report suspicious behavior** + +## Security Features + +This project includes: + +- Automated vulnerability scanning (pip-audit) +- Static code analysis +- Code quality checks +- Hermetic builds and releases with integrity verification +- Secure build pipeline + +## Known Vulnerabilities + +Known vulnerabilities may vary according to the selected Python version. + +You can check our latest constraints against vulnerable package versions in our [requirements.in file](third_party/pip/requirements.in). + +The following table lists all known vulnerabilities that could not be fixed: + +| Package | Vulnerability ID | Vulnerable Version | Fixed Version | Python Version | Reason | +| ---------- | ------------------- | ------------------ | ------------- | -------------- | ------------------------------------ | +| urllib3 | GHSA-48p4-8xcf-vxj5 | 2.2.3 | 2.5.0 | 3.8 | Fixed package requires Python >= 3.9 | +| urllib3 | GHSA-pq67-6m6q-mj2v | 2.2.3 | 2.5.0 | 3.8 | Fixed package requires Python >= 3.9 | + +### Vulnerable Python Versions + +The [official status of Python versions](https://devguide.python.org/versions/) shows which Python versions are no longer supported and therefore are vulnerable. + +While we might support some vulnerable Python versions for backwards compatibility, we strongly advise our users to consider upgrading to a more recent version. From 5d9f19b066c60f734ffeb57fe595e3d6b9e5a133 Mon Sep 17 00:00:00 2001 From: "jan.nowotsch" Date: Tue, 12 Aug 2025 17:12:54 +0200 Subject: [PATCH 74/74] [ci] Add test github action Add the test github action that executes the run_all_tests shell script which in turn performs all tests and checks relevant for the project. --- .github/workflows/tests.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..15525df --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,10 @@ +name: test +on: push + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: bazelbuild/setup-bazelisk@v3 + - run: scripts/run_all_tests.sh