From 907e3d7f28254af5b42db50837d2a032ca877a22 Mon Sep 17 00:00:00 2001 From: Sebastiaan Huber Date: Wed, 28 Feb 2024 11:26:22 +0100 Subject: [PATCH] Devops: Add pre-commit hook that validates optional dependencies The `dev/validate_optional_dependencies.py` is added. It validates that the `all_plugins` extras specifies exactly the same dependency requirements that all other extras combined declare as well, except for the `docs`, `pre-commit`, and `tests` extras, which are only used for development. This is to ensure that the `all_plugins` extras provides the exact same dependencies as all the plugin specific extras combined. The script is called through a pre-commit hook. --- .pre-commit-config.yaml | 8 +++++ dev/validate_optional_dependencies.py | 47 +++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100755 dev/validate_optional_dependencies.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 491bb535..4d836a43 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,3 +31,11 @@ repos: args: [--autofix] - id: pretty-format-yaml args: [--autofix] + +- repo: local + hooks: + - id: optional-dependencies + name: validate optional dependencies + entry: python ./dev/validate_optional_dependencies.py + language: system + files: pyproject.toml| diff --git a/dev/validate_optional_dependencies.py b/dev/validate_optional_dependencies.py new file mode 100755 index 00000000..d6bfbeb0 --- /dev/null +++ b/dev/validate_optional_dependencies.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +"""Script to validate the optional dependencies in the `pyproject.toml`.""" + + +def main(): + """Validate the optional dependencies.""" + import pathlib + import sys + + import tomllib + + filepath_pyproject_toml = pathlib.Path(__file__).parent.parent / 'pyproject.toml' + + with filepath_pyproject_toml.open('rb') as handle: + pyproject = tomllib.load(handle) + + exclude = ['all_plugins', 'docs', 'pre-commit', 'tests'] + dependencies_all_plugins = pyproject['project']['optional-dependencies']['all_plugins'] + dependencies_separate = [] + + for key, dependencies in pyproject['project']['optional-dependencies'].items(): + if key in exclude: + continue + dependencies_separate.extend(dependencies) + + missing_all_plugins = set(dependencies_separate).difference(set(dependencies_all_plugins)) + excess_all_plugins = set(dependencies_all_plugins).difference(set(dependencies_separate)) + + if missing_all_plugins: + print( + 'ERROR: the `all_plugins` extras are inconsistent. The following plugin dependencies are missing: ' + f'{", ".join(missing_all_plugins)}', + file=sys.stderr, + ) + sys.exit(1) + + if excess_all_plugins: + print( + 'ERROR: the `all_plugins` extras are inconsistent. The following dependencies are not declared by any ' + f'plugin extras: {", ".join(excess_all_plugins)}', + file=sys.stderr, + ) + sys.exit(1) + + +if __name__ == '__main__': + main()