diff --git a/minimum_versions/environments/spec.py b/minimum_versions/environments/spec.py index f93fa94..24f7c24 100644 --- a/minimum_versions/environments/spec.py +++ b/minimum_versions/environments/spec.py @@ -11,16 +11,22 @@ class Spec: def compare_versions(environments, policy_versions, ignored_violations): status = {} + warnings = {} for env, specs in environments.items(): - env_status = any( - ( - spec.name not in ignored_violations - and ( - spec.version is None - or spec.version > policy_versions[spec.name].version - ) - ) + violations = { + spec.name: spec.version is None + or spec.version > policy_versions[spec.name].version for spec in specs + } + status[env] = any( + value + for name, value in violations.items() + if name not in ignored_violations ) - status[env] = env_status - return status + warnings[env] = { + name: ["violation unnecessarily ignored"] + for name, value in violations.items() + if not value and name in ignored_violations + } + + return status, warnings diff --git a/minimum_versions/formatting.py b/minimum_versions/formatting.py index ba6fca9..13df3f1 100644 --- a/minimum_versions/formatting.py +++ b/minimum_versions/formatting.py @@ -90,12 +90,18 @@ def format_bump_table(specs, policy_versions, releases, warnings, ignored_violat warning_table.add_column("Package") warning_table.add_column("Warning") + formatters = { + "indexed": lambda index, message: f"{index: 2d}. {message}", + "bare": lambda index, message: f" {message}", + } + for package, messages in warnings.items(): - if not messages: - continue - warning_table.add_row(package, messages[0], style=warning_style) - for message in messages[1:]: - warning_table.add_row("", message, style=warning_style) + formatter = formatters["indexed" if len(messages) > 1 else "bare"] + for index, message in enumerate(messages, start=1): + key = "" if index > 1 else package + warning_table.add_row( + key, formatter(index, message), style=warning_style + ) grid.add_row("Warnings", warning_table) diff --git a/minimum_versions/main.py b/minimum_versions/main.py index e777725..1068df5 100644 --- a/minimum_versions/main.py +++ b/minimum_versions/main.py @@ -8,6 +8,7 @@ from rich.console import Console from rich.panel import Panel from rich.table import Table +from tlz.dicttoolz import merge_with from tlz.itertoolz import concat, unique from minimum_versions.environments import compare_versions, parse_environment @@ -35,6 +36,16 @@ def convert( return super().convert(value, param, ctx) +def merge_warnings(*warnings): + def merge_lists(v): + return list(concat(v)) + + def merge_env(values): + return merge_with(merge_lists, *values) + + return merge_with(merge_env, *warnings) + + @click.group() def main(): pass @@ -60,7 +71,7 @@ def validate(today, policy_file, manifest_path, environment_paths): for path in environment_paths } - warnings = { + spec_warnings = { env: {n: w for n, w in warnings_ if n not in policy.exclude} for env, (_, warnings_) in parsed_environments.items() } @@ -84,11 +95,16 @@ def validate(today, policy_file, manifest_path, environment_paths): policy_versions = find_policy_versions(policy, today, package_releases) - status = compare_versions(environments, policy_versions, policy.ignored_violations) + status, violation_warnings = compare_versions( + environments, policy_versions, policy.ignored_violations + ) + + warnings = merge_warnings(spec_warnings, violation_warnings) release_lookup = { n: {r.version: r for r in releases} for n, releases in package_releases.items() } + grids = { env: format_bump_table( specs, diff --git a/minimum_versions/tests/test_environments.py b/minimum_versions/tests/test_environments.py index 1de5cb5..b01e375 100644 --- a/minimum_versions/tests/test_environments.py +++ b/minimum_versions/tests/test_environments.py @@ -34,7 +34,7 @@ def test_parse_environment(specifier, manifest_path, key, monkeypatch): @pytest.mark.parametrize( - ["envs", "ignored_violations", "expected"], + ["envs", "ignored_violations", "expected", "expected_warnings"], ( pytest.param( { @@ -46,6 +46,7 @@ def test_parse_environment(specifier, manifest_path, key, monkeypatch): }, ["d"], {"env1": False}, + {"env1": {}}, id="single-violation-ignored", ), pytest.param( @@ -55,23 +56,33 @@ def test_parse_environment(specifier, manifest_path, key, monkeypatch): }, [], {"env1": True, "env2": False}, + {"env1": {}, "env2": {}}, id="multiple-split-not ignored", ), pytest.param( {"env1": [Spec("d", None)]}, [], {"env1": True}, + {"env1": {}}, id="single-none-not ignored", ), pytest.param( {"env1": [Spec("d", None)]}, ["d"], {"env1": False}, + {"env1": {}}, id="single-none-ignored", ), + pytest.param( + {"env1": [Spec("a", Version("1.2"))]}, + ["a"], + {"env1": False}, + {"env1": {"a": ["violation unnecessarily ignored"]}}, + id="single-none-unnecessary_ignore", + ), ), ) -def test_compare_versions(envs, ignored_violations, expected): +def test_compare_versions(envs, ignored_violations, expected, expected_warnings): policy_versions = { "a": FakeRecord(version=Version("1.2")), "b": FakeRecord(version=Version("3.1")), @@ -79,10 +90,11 @@ def test_compare_versions(envs, ignored_violations, expected): "d": FakeRecord(version=Version("0.5")), } - actual = environments.spec.compare_versions( + actual, actual_warnings = environments.spec.compare_versions( envs, policy_versions, ignored_violations ) assert actual == expected + assert actual_warnings == expected_warnings class TestCondaEnvironment: