From ee756513a2fd97e4269b7ffd42738fea688c7bff Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Fri, 8 Nov 2024 16:13:46 +0100 Subject: [PATCH 01/15] rewrite the `min_deps_check` script --- ci/minimum_versions.py | 320 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 ci/minimum_versions.py diff --git a/ci/minimum_versions.py b/ci/minimum_versions.py new file mode 100644 index 00000000000..591b0c5e65e --- /dev/null +++ b/ci/minimum_versions.py @@ -0,0 +1,320 @@ +import asyncio +import bisect +import datetime +import pathlib +import sys +from dataclasses import dataclass, field + +import rich_click as click +import yaml +from dateutil.relativedelta import relativedelta +from rattler import Gateway, Version +from rich.console import Console +from rich.panel import Panel +from rich.table import Column, Table +from tlz.functoolz import curry, pipe +from tlz.itertoolz import concat, groupby + +click.rich_click.SHOW_ARGUMENTS = True +console = Console() + +channels = ["conda-forge"] +platforms = ["noarch", "linux-64"] +ignored_packages = [ + "coveralls", + "pip", + "pytest", + "pytest-cov", + "pytest-env", + "pytest-xdist", + "pytest-timeout", + "hypothesis", +] + + +@dataclass +class Policy: + package_months: dict + default_months: int + overrides: dict[str, Version] = field(default_factory=dict) + + def minimum_version(self, package_name, releases): + if (override := self.overrides.get(package_name)) is not None: + return override + + policy_months = self.package_months.get(package_name, self.default_months) + today = datetime.date.today() + + cutoff_date = today - relativedelta(months=policy_months) + + index = bisect.bisect_left( + releases, cutoff_date, key=lambda x: x.timestamp.date() + ) + return releases[index - 1 if index > 0 else 0] + + +@dataclass +class Spec: + name: str + version: Version | None + + @classmethod + def parse(cls, spec_text): + warnings = [] + if ">" in spec_text or "<" in spec_text: + warnings.append( + f"package should be pinned with an exact version: {spec_text!r}" + ) + + spec_text = spec_text.replace(">", "").replace("<", "") + + if "=" in spec_text: + name, version_text = spec_text.split("=", maxsplit=1) + version = Version(version_text) + segments = version.segments() + + if len(segments) != 2 or (len(segments) == 3 and segments[2] != 0): + warnings.append( + f"package should be pinned to a minor version (got {version})" + ) + else: + name = spec_text + version = None + + return cls(name, version), (name, warnings) + + +@dataclass(order=True) +class Release: + version: Version + build_number: int + timestamp: datetime.datetime = field(compare=False) + + @classmethod + def from_repodata_record(cls, repo_data): + return cls( + version=repo_data.version, + build_number=repo_data.build_number, + timestamp=repo_data.timestamp, + ) + + +def parse_environment(text): + env = yaml.safe_load(text) + + specs = [] + warnings = [] + for dep in env["dependencies"]: + spec, warnings_ = Spec.parse(dep) + + warnings.append(warnings_) + specs.append(spec) + + return specs, warnings + + +def is_preview(version): + candidates = ["rc", "beta", "alpha"] + + *_, last_segment = version.segments() + return any(candidate in last_segment for candidate in candidates) + + +def group_packages(records): + groups = groupby(lambda r: r.name.normalized, records) + return { + name: sorted(map(Release.from_repodata_record, group)) + for name, group in groups.items() + } + + +def filter_releases(predicate, releases): + return { + name: [r for r in records if predicate(r)] for name, records in releases.items() + } + + +def deduplicate_releases(package_info): + def deduplicate(releases): + return min(releases, key=lambda p: p.timestamp) + + return { + name: list(map(deduplicate, groupby(lambda p: p.version, group).values())) + for name, group in package_info.items() + } + + +def find_policy_versions(policy, releases): + return { + name: policy.minimum_version(name, package_releases) + for name, package_releases in releases.items() + } + + +def is_suitable_release(release): + if release.timestamp is None: + return False + + segments = release.version.extend_to_length(3).segments() + + return segments[2] == [0] + + +def lookup_spec_release(spec, releases): + version = spec.version.extend_to_length(3) + + return releases[spec.name][version] + + +def compare_versions(environments, policy_versions): + status = {} + for env, specs in environments.items(): + env_status = any( + spec.version > policy_versions[spec.name].version for spec in specs + ) + status[env] = env_status + return status + + +def version_comparison_symbol(required, policy): + if required < policy: + return "<" + elif required > policy: + return ">" + else: + return "=" + + +def format_bump_table(specs, policy_versions, releases, warnings): + table = Table( + Column("Package", width=20), + Column("Required", width=8), + "Required (date)", + Column("Policy", width=8), + "Policy (date)", + "Status", + ) + + for spec in specs: + policy_release = policy_versions[spec.name] + policy_version = policy_release.version.with_segments(0, 2) + policy_date = policy_release.timestamp + + required_version = spec.version + required_date = lookup_spec_release(spec, releases).timestamp + + status = version_comparison_symbol(required_version, policy_version) + styles = { + ">": "bold red on grey15", + "=": "green", + "<": "bold yellow on grey15", + } + if warnings[spec.name]: + style = "bold red on grey3" + else: + style = styles[status] + + table.add_row( + spec.name, + str(required_version), + f"{required_date:%Y-%m-%d}", + str(policy_version), + f"{policy_date:%Y-%m-%d}", + status, + style=style, + ) + + grid = Table.grid(expand=True, padding=(0, 2)) + grid.add_column(style="bold red", vertical="middle") + grid.add_column() + grid.add_row("Version summary", table) + + if any(warnings.values()): + warning_table = Table(width=table.width, expand=True) + warning_table.add_column("Package", "Warning", style="bold yellow on grey3") + + for package, messages in warnings.items(): + if not messages: + continue + warning_table.add_row(package, messages[0], style="bold yellow on grey3") + for message in messages[1:]: + warning_table.add_row("", message, style="bold yellow on grey3") + + grid.add_row("Warnings", warning_table) + + return grid + + +@click.command() +@click.argument( + "environment_paths", + type=click.Path(exists=True, readable=True, path_type=pathlib.Path), + nargs=-1, +) +def main(environment_paths): + parsed_environments = { + path.stem: parse_environment(path.read_text()) for path in environment_paths + } + + warnings = { + env: dict(warnings_) for env, (_, warnings_) in parsed_environments.items() + } + environments = { + env: [spec for spec in specs if spec.name not in ignored_packages] + for env, (specs, _) in parsed_environments.items() + } + + all_packages = list( + dict.fromkeys(spec.name for spec in concat(environments.values())) + ) + + policy_months = { + "python": 30, + "numpy": 18, + } + policy_months_default = 12 + overrides = {} + + policy = Policy( + policy_months, default_months=policy_months_default, overrides=overrides + ) + + gateway = Gateway() + query = gateway.query(channels, platforms, all_packages, recursive=False) + records = asyncio.run(query) + + package_releases = pipe( + records, + concat, + group_packages, + curry(filter_releases, lambda r: r.timestamp is not None), + deduplicate_releases, + ) + policy_versions = pipe( + package_releases, + curry(filter_releases, is_suitable_release), + curry(find_policy_versions, policy), + ) + release_lookup = { + n: {r.version: r for r in releases} for n, releases in package_releases.items() + } + + status = compare_versions(environments, policy_versions) + grids = { + env: format_bump_table(specs, policy_versions, release_lookup, warnings[env]) + for env, specs in environments.items() + } + root_grid = Table.grid() + root_grid.add_column() + + for env, grid in grids.items(): + root_grid.add_row(Panel(grid, title=env, expand=True)) + + console.print(root_grid) + + status_code = 1 if any(status.values()) else 0 + sys.exit(status_code) + + +if __name__ == "__main__": + main() From 512ee51ccb1f35c946fc96d5922d3cf3c37b3fa5 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Fri, 8 Nov 2024 16:19:58 +0100 Subject: [PATCH 02/15] call the new script --- .github/workflows/ci-additional.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-additional.yaml b/.github/workflows/ci-additional.yaml index b665d20b40a..d2e257811ba 100644 --- a/.github/workflows/ci-additional.yaml +++ b/.github/workflows/ci-additional.yaml @@ -319,12 +319,14 @@ jobs: python=3.12 pyyaml python-dateutil - conda + cytoolz + rich + py-rattler - name: All-deps minimum versions policy run: | - python ci/min_deps_check.py ci/requirements/min-all-deps.yml + python ci/minimum_versions.py ci/requirements/min-all-deps.yml - name: Bare minimum versions policy run: | - python ci/min_deps_check.py ci/requirements/bare-minimum.yml + python ci/minimum_versions.py ci/requirements/bare-minimum.yml From bd40f13a3d7cb7135cbb6e2f1890b6385c6bd305 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Fri, 8 Nov 2024 16:20:15 +0100 Subject: [PATCH 03/15] unpin `micromamba` --- .github/workflows/ci-additional.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/ci-additional.yaml b/.github/workflows/ci-additional.yaml index d2e257811ba..471f704c6dc 100644 --- a/.github/workflows/ci-additional.yaml +++ b/.github/workflows/ci-additional.yaml @@ -311,9 +311,6 @@ jobs: - name: Setup micromamba uses: mamba-org/setup-micromamba@v2 with: - # run with micromamba 1.5.10 together with conda - # conda.api is not API compatible with libmambapy - micromamba-version: "1.5.10-0" environment-name: xarray-tests create-args: >- python=3.12 From be50bac440c5393c667f67b806202237a2d13106 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Fri, 8 Nov 2024 16:22:50 +0100 Subject: [PATCH 04/15] install `rich-click` --- .github/workflows/ci-additional.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci-additional.yaml b/.github/workflows/ci-additional.yaml index 471f704c6dc..12f779925c6 100644 --- a/.github/workflows/ci-additional.yaml +++ b/.github/workflows/ci-additional.yaml @@ -318,6 +318,7 @@ jobs: python-dateutil cytoolz rich + rich-click py-rattler - name: All-deps minimum versions policy From 6c38f52dfd06d277a075ae14777785c66ff4049c Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Fri, 8 Nov 2024 17:33:13 +0100 Subject: [PATCH 05/15] enforce a minimum width of 120 --- .github/workflows/ci-additional.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci-additional.yaml b/.github/workflows/ci-additional.yaml index 12f779925c6..9ef315f505c 100644 --- a/.github/workflows/ci-additional.yaml +++ b/.github/workflows/ci-additional.yaml @@ -303,6 +303,9 @@ jobs: run: shell: bash -l {0} + env: + COLUMNS: 120 + steps: - uses: actions/checkout@v4 with: From 8cc50a83a02b5c6789bf08e3ab201a36947475ff Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Fri, 8 Nov 2024 21:13:54 +0100 Subject: [PATCH 06/15] remove the background colors --- ci/minimum_versions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ci/minimum_versions.py b/ci/minimum_versions.py index 591b0c5e65e..afb0d22aabe 100644 --- a/ci/minimum_versions.py +++ b/ci/minimum_versions.py @@ -205,12 +205,12 @@ def format_bump_table(specs, policy_versions, releases, warnings): status = version_comparison_symbol(required_version, policy_version) styles = { - ">": "bold red on grey15", + ">": "bold red", "=": "green", - "<": "bold yellow on grey15", + "<": "bold yellow", } if warnings[spec.name]: - style = "bold red on grey3" + style = "bold bright_red" else: style = styles[status] From 6d5c9cd699cff7ee6242b7467575b5274105db1c Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Fri, 8 Nov 2024 21:14:28 +0100 Subject: [PATCH 07/15] remove old min-deps script --- ci/min_deps_check.py | 218 ------------------------------------------- 1 file changed, 218 deletions(-) delete mode 100755 ci/min_deps_check.py diff --git a/ci/min_deps_check.py b/ci/min_deps_check.py deleted file mode 100755 index 443ab7d5a40..00000000000 --- a/ci/min_deps_check.py +++ /dev/null @@ -1,218 +0,0 @@ -#!/usr/bin/env python -"""Fetch from conda database all available versions of the xarray dependencies and their -publication date. Compare it against requirements/min-all-deps.yml to verify the -policy on obsolete dependencies is being followed. Print a pretty report :) -""" - -from __future__ import annotations - -import itertools -import sys -from collections.abc import Iterator -from datetime import datetime - -import conda.api # type: ignore[import] -import yaml -from dateutil.relativedelta import relativedelta - -CHANNELS = ["conda-forge", "defaults"] -IGNORE_DEPS = { - "coveralls", - "flake8", - "hypothesis", - "isort", - "mypy", - "pip", - "pytest", - "pytest-cov", - "pytest-env", - "pytest-timeout", - "pytest-xdist", - "setuptools", -} - -POLICY_MONTHS = {"python": 30, "numpy": 18} -POLICY_MONTHS_DEFAULT = 12 -POLICY_OVERRIDE: dict[str, tuple[int, int]] = {} -errors = [] - - -def error(msg: str) -> None: - global errors - errors.append(msg) - print("ERROR:", msg) - - -def warning(msg: str) -> None: - print("WARNING:", msg) - - -def parse_requirements(fname) -> Iterator[tuple[str, int, int, int | None]]: - """Load requirements/min-all-deps.yml - - Yield (package name, major version, minor version, [patch version]) - """ - global errors - - with open(fname) as fh: - contents = yaml.safe_load(fh) - for row in contents["dependencies"]: - if isinstance(row, dict) and list(row) == ["pip"]: - continue - pkg, eq, version = row.partition("=") - if pkg.rstrip("<>") in IGNORE_DEPS: - continue - if pkg.endswith(("<", ">")) or eq != "=": - error("package should be pinned with exact version: " + row) - continue - - try: - version_tup = tuple(int(x) for x in version.split(".")) - except ValueError as err: - raise ValueError("non-numerical version: " + row) from err - - if len(version_tup) == 2: - yield (pkg, *version_tup, None) # type: ignore[misc] - elif len(version_tup) == 3: - yield (pkg, *version_tup) # type: ignore[misc] - else: - raise ValueError("expected major.minor or major.minor.patch: " + row) - - -def query_conda(pkg: str) -> dict[tuple[int, int], datetime]: - """Query the conda repository for a specific package - - Return map of {(major version, minor version): publication date} - """ - - def metadata(entry): - version = entry.version - - time = datetime.fromtimestamp(entry.timestamp) - major, minor = map(int, version.split(".")[:2]) - - return (major, minor), time - - raw_data = conda.api.SubdirData.query_all(pkg, channels=CHANNELS) - data = sorted(metadata(entry) for entry in raw_data if entry.timestamp != 0) - - release_dates = { - version: [time for _, time in group if time is not None] - for version, group in itertools.groupby(data, key=lambda x: x[0]) - } - out = {version: min(dates) for version, dates in release_dates.items() if dates} - - # Hardcoded fix to work around incorrect dates in conda - if pkg == "python": - out.update( - { - (2, 7): datetime(2010, 6, 3), - (3, 5): datetime(2015, 9, 13), - (3, 6): datetime(2016, 12, 23), - (3, 7): datetime(2018, 6, 27), - (3, 8): datetime(2019, 10, 14), - (3, 9): datetime(2020, 10, 5), - (3, 10): datetime(2021, 10, 4), - (3, 11): datetime(2022, 10, 24), - } - ) - - return out - - -def process_pkg( - pkg: str, req_major: int, req_minor: int, req_patch: int | None -) -> tuple[str, str, str, str, str, str]: - """Compare package version from requirements file to available versions in conda. - Return row to build pandas dataframe: - - - package name - - major.minor.[patch] version in requirements file - - publication date of version in requirements file (YYYY-MM-DD) - - major.minor version suggested by policy - - publication date of version suggested by policy (YYYY-MM-DD) - - status ("<", "=", "> (!)") - """ - print(f"Analyzing {pkg}...") - versions = query_conda(pkg) - - try: - req_published = versions[req_major, req_minor] - except KeyError: - error("not found in conda: " + pkg) - return pkg, fmt_version(req_major, req_minor, req_patch), "-", "-", "-", "(!)" - - policy_months = POLICY_MONTHS.get(pkg, POLICY_MONTHS_DEFAULT) - policy_published = datetime.now() - relativedelta(months=policy_months) - - filtered_versions = [ - version - for version, published in versions.items() - if published < policy_published - ] - policy_major, policy_minor = max(filtered_versions, default=(req_major, req_minor)) - - try: - policy_major, policy_minor = POLICY_OVERRIDE[pkg] - except KeyError: - pass - policy_published_actual = versions[policy_major, policy_minor] - - if (req_major, req_minor) < (policy_major, policy_minor): - status = "<" - elif (req_major, req_minor) > (policy_major, policy_minor): - status = "> (!)" - delta = relativedelta(datetime.now(), req_published).normalized() - n_months = delta.years * 12 + delta.months - warning( - f"Package is too new: {pkg}={req_major}.{req_minor} was " - f"published on {req_published:%Y-%m-%d} " - f"which was {n_months} months ago (policy is {policy_months} months)" - ) - else: - status = "=" - - if req_patch is not None: - warning("patch version should not appear in requirements file: " + pkg) - status += " (w)" - - return ( - pkg, - fmt_version(req_major, req_minor, req_patch), - req_published.strftime("%Y-%m-%d"), - fmt_version(policy_major, policy_minor), - policy_published_actual.strftime("%Y-%m-%d"), - status, - ) - - -def fmt_version(major: int, minor: int, patch: int | None = None) -> str: - if patch is None: - return f"{major}.{minor}" - else: - return f"{major}.{minor}.{patch}" - - -def main() -> None: - fname = sys.argv[1] - rows = [ - process_pkg(pkg, major, minor, patch) - for pkg, major, minor, patch in parse_requirements(fname) - ] - - print("\nPackage Required Policy Status") - print("----------------- -------------------- -------------------- ------") - fmt = "{:17} {:7} ({:10}) {:7} ({:10}) {}" - for row in rows: - print(fmt.format(*row)) - - if errors: - print("\nErrors:") - print("-------") - for i, e in enumerate(errors): - print(f"{i+1}. {e}") - sys.exit(1) - - -if __name__ == "__main__": - main() From 20c59bec8c3c23ac8056980452b7f6354a861e14 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Fri, 8 Nov 2024 21:25:07 +0100 Subject: [PATCH 08/15] more changing of colors --- ci/minimum_versions.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ci/minimum_versions.py b/ci/minimum_versions.py index afb0d22aabe..2389a2549ce 100644 --- a/ci/minimum_versions.py +++ b/ci/minimum_versions.py @@ -205,12 +205,12 @@ def format_bump_table(specs, policy_versions, releases, warnings): status = version_comparison_symbol(required_version, policy_version) styles = { - ">": "bold red", - "=": "green", - "<": "bold yellow", + ">": "bold red1", + "=": "dim green3", + "<": "orange3", } - if warnings[spec.name]: - style = "bold bright_red" + if warnings[spec.name] and status != ">": + style = "bold yellow" else: style = styles[status] @@ -231,14 +231,14 @@ def format_bump_table(specs, policy_versions, releases, warnings): if any(warnings.values()): warning_table = Table(width=table.width, expand=True) - warning_table.add_column("Package", "Warning", style="bold yellow on grey3") + warning_table.add_column("Package", "Warning", style="bold yellow") for package, messages in warnings.items(): if not messages: continue - warning_table.add_row(package, messages[0], style="bold yellow on grey3") + warning_table.add_row(package, messages[0], style="bold yellow") for message in messages[1:]: - warning_table.add_row("", message, style="bold yellow on grey3") + warning_table.add_row("", message, style="bold yellow") grid.add_row("Warnings", warning_table) From 79c402512388deb130b1d09ca3ac0d94ef96dc15 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Fri, 8 Nov 2024 21:32:20 +0100 Subject: [PATCH 09/15] some more styling --- ci/minimum_versions.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ci/minimum_versions.py b/ci/minimum_versions.py index 2389a2549ce..6918d6897a4 100644 --- a/ci/minimum_versions.py +++ b/ci/minimum_versions.py @@ -204,13 +204,14 @@ def format_bump_table(specs, policy_versions, releases, warnings): required_date = lookup_spec_release(spec, releases).timestamp status = version_comparison_symbol(required_version, policy_version) + warning_style = "bold yellow1" styles = { ">": "bold red1", - "=": "dim green3", - "<": "orange3", + "=": "bold dim green3", + "<": "dark_orange", } if warnings[spec.name] and status != ">": - style = "bold yellow" + style = warning_style else: style = styles[status] @@ -231,14 +232,14 @@ def format_bump_table(specs, policy_versions, releases, warnings): if any(warnings.values()): warning_table = Table(width=table.width, expand=True) - warning_table.add_column("Package", "Warning", style="bold yellow") + warning_table.add_column("Package", "Warning", style=warning_style) for package, messages in warnings.items(): if not messages: continue - warning_table.add_row(package, messages[0], style="bold yellow") + warning_table.add_row(package, messages[0], style=warning_style) for message in messages[1:]: - warning_table.add_row("", message, style="bold yellow") + warning_table.add_row("", message, style=warning_style) grid.add_row("Warnings", warning_table) From 01841b1b85164a2f0676f5f40ae9e7087e1b68e5 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Fri, 8 Nov 2024 21:35:37 +0100 Subject: [PATCH 10/15] ... aaand some more styling --- ci/minimum_versions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/minimum_versions.py b/ci/minimum_versions.py index 6918d6897a4..321edf985c5 100644 --- a/ci/minimum_versions.py +++ b/ci/minimum_versions.py @@ -208,7 +208,7 @@ def format_bump_table(specs, policy_versions, releases, warnings): styles = { ">": "bold red1", "=": "bold dim green3", - "<": "dark_orange", + "<": "bold orange3", } if warnings[spec.name] and status != ">": style = warning_style @@ -226,7 +226,7 @@ def format_bump_table(specs, policy_versions, releases, warnings): ) grid = Table.grid(expand=True, padding=(0, 2)) - grid.add_column(style="bold red", vertical="middle") + grid.add_column(style="bold red1", vertical="middle") grid.add_column() grid.add_row("Version summary", table) From 415d4ace29402dec81a0f28cf04ca051444b6ab8 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Sat, 9 Nov 2024 21:41:53 +0100 Subject: [PATCH 11/15] move the style definition in one place --- ci/minimum_versions.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/ci/minimum_versions.py b/ci/minimum_versions.py index 321edf985c5..7f2032bbeca 100644 --- a/ci/minimum_versions.py +++ b/ci/minimum_versions.py @@ -195,6 +195,14 @@ def format_bump_table(specs, policy_versions, releases, warnings): "Status", ) + heading_style = "bold red1" + warning_style = "bold yellow1" + styles = { + ">": "bold red1", + "=": "bold dim green3", + "<": "bold orange3", + } + for spec in specs: policy_release = policy_versions[spec.name] policy_version = policy_release.version.with_segments(0, 2) @@ -204,16 +212,7 @@ def format_bump_table(specs, policy_versions, releases, warnings): required_date = lookup_spec_release(spec, releases).timestamp status = version_comparison_symbol(required_version, policy_version) - warning_style = "bold yellow1" - styles = { - ">": "bold red1", - "=": "bold dim green3", - "<": "bold orange3", - } - if warnings[spec.name] and status != ">": - style = warning_style - else: - style = styles[status] + style = styles[status] table.add_row( spec.name, @@ -226,7 +225,7 @@ def format_bump_table(specs, policy_versions, releases, warnings): ) grid = Table.grid(expand=True, padding=(0, 2)) - grid.add_column(style="bold red1", vertical="middle") + grid.add_column(style=heading_style, vertical="middle") grid.add_column() grid.add_row("Version summary", table) From 762882654ca804f5cd361ca1823b93a9d9ddb45e Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Sat, 9 Nov 2024 21:42:08 +0100 Subject: [PATCH 12/15] compare versions *before* formatting --- ci/minimum_versions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/minimum_versions.py b/ci/minimum_versions.py index 7f2032bbeca..ec9c59bf104 100644 --- a/ci/minimum_versions.py +++ b/ci/minimum_versions.py @@ -295,11 +295,11 @@ def main(environment_paths): curry(filter_releases, is_suitable_release), curry(find_policy_versions, policy), ) + status = compare_versions(environments, policy_versions) + release_lookup = { n: {r.version: r for r in releases} for n, releases in package_releases.items() } - - status = compare_versions(environments, policy_versions) grids = { env: format_bump_table(specs, policy_versions, release_lookup, warnings[env]) for env, specs in environments.items() From f5914f0d0a01605639421d514bcc51af43de30dc Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Sat, 9 Nov 2024 21:55:19 +0100 Subject: [PATCH 13/15] move the definition `console` into `main` --- ci/minimum_versions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ci/minimum_versions.py b/ci/minimum_versions.py index ec9c59bf104..1b0d3c05bff 100644 --- a/ci/minimum_versions.py +++ b/ci/minimum_versions.py @@ -16,7 +16,6 @@ from tlz.itertoolz import concat, groupby click.rich_click.SHOW_ARGUMENTS = True -console = Console() channels = ["conda-forge"] platforms = ["noarch", "linux-64"] @@ -252,6 +251,8 @@ def format_bump_table(specs, policy_versions, releases, warnings): nargs=-1, ) def main(environment_paths): + console = Console() + parsed_environments = { path.stem: parse_environment(path.read_text()) for path in environment_paths } From 830e48d20ee5ff3aec1d0ecf6b0743fb9d4f5509 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Sat, 9 Nov 2024 21:55:38 +0100 Subject: [PATCH 14/15] properly add two columns to the warnings tables --- ci/minimum_versions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ci/minimum_versions.py b/ci/minimum_versions.py index 1b0d3c05bff..bff8e1949b4 100644 --- a/ci/minimum_versions.py +++ b/ci/minimum_versions.py @@ -230,7 +230,8 @@ def format_bump_table(specs, policy_versions, releases, warnings): if any(warnings.values()): warning_table = Table(width=table.width, expand=True) - warning_table.add_column("Package", "Warning", style=warning_style) + warning_table.add_column("Package") + warning_table.add_column("Warning") for package, messages in warnings.items(): if not messages: From e6d573b6edc9835b9a7a9227836de80d86c53acb Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Sat, 9 Nov 2024 21:56:01 +0100 Subject: [PATCH 15/15] define the styles using the class and RGB values --- ci/minimum_versions.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ci/minimum_versions.py b/ci/minimum_versions.py index bff8e1949b4..c226e304769 100644 --- a/ci/minimum_versions.py +++ b/ci/minimum_versions.py @@ -11,6 +11,7 @@ from rattler import Gateway, Version from rich.console import Console from rich.panel import Panel +from rich.style import Style from rich.table import Column, Table from tlz.functoolz import curry, pipe from tlz.itertoolz import concat, groupby @@ -194,12 +195,12 @@ def format_bump_table(specs, policy_versions, releases, warnings): "Status", ) - heading_style = "bold red1" - warning_style = "bold yellow1" + heading_style = Style(color="#ff0000", bold=True) + warning_style = Style(color="#ffff00", bold=True) styles = { - ">": "bold red1", - "=": "bold dim green3", - "<": "bold orange3", + ">": Style(color="#ff0000", bold=True), + "=": Style(color="#008700", bold=True), + "<": Style(color="#d78700", bold=True), } for spec in specs: