From d679bac21c9b175dff5e78df82f761bce7598b0d Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 12 Sep 2024 00:02:41 +0530 Subject: [PATCH 01/18] Suppress urllib3 logging for JSON response --- pyodide_build/xbuildenv_releases.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/pyodide_build/xbuildenv_releases.py b/pyodide_build/xbuildenv_releases.py index b0782b3..84c64b7 100644 --- a/pyodide_build/xbuildenv_releases.py +++ b/pyodide_build/xbuildenv_releases.py @@ -1,4 +1,6 @@ +import logging import os +from contextlib import contextmanager from functools import cache from packaging.version import Version @@ -19,7 +21,7 @@ class CrossBuildEnvReleaseSpec(BaseModel): python_version: str # The version of the Emscripten SDK emscripten_version: str - # Minimum and maximum pyodide-build versions that is compatible with this release + # Minimum and maximum pyodide-build versions that are compatible with this release min_pyodide_build_version: str | None = None max_pyodide_build_version: str | None = None model_config = ConfigDict(extra="forbid", title="CrossBuildEnvReleasesSpec") @@ -184,6 +186,20 @@ def get_release( return self.releases[version] +@contextmanager +def _suppress_urllib3_logging(): + """ + Temporarily suppresses urllib3 logging for internal use. + """ + logger = logging.getLogger("urllib3") + original_level = logger.level + logger.setLevel(logging.WARNING) + try: + yield + finally: + logger.setLevel(original_level) + + def cross_build_env_metadata_url() -> str: """ Get the URL to the Pyodide cross-build environment metadata @@ -215,12 +231,14 @@ def load_cross_build_env_metadata(url_or_filename: str) -> CrossBuildEnvMetaSpec CrossBuildEnvMetaSpec The Pyodide cross-build environment metadata """ - if url_or_filename.startswith("http"): + if url_or_filename.startswith("https"): import requests - response = requests.get(url_or_filename) - response.raise_for_status() - data = response.json() + with _suppress_urllib3_logging(): + with requests.get(url_or_filename) as response: + response.raise_for_status() + data = response.json() + return CrossBuildEnvMetaSpec.model_validate(data) with open(url_or_filename) as f: From 6c3b73cb5832e8dd1c30b29fd98a05c4c6da753e Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 12 Sep 2024 00:03:24 +0530 Subject: [PATCH 02/18] Add `--json` option, use helper functions for output --- pyodide_build/cli/xbuildenv.py | 105 ++++++++++++++++++++++----------- 1 file changed, 70 insertions(+), 35 deletions(-) diff --git a/pyodide_build/cli/xbuildenv.py b/pyodide_build/cli/xbuildenv.py index db2a152..e28a431 100644 --- a/pyodide_build/cli/xbuildenv.py +++ b/pyodide_build/cli/xbuildenv.py @@ -151,7 +151,12 @@ def _search( "-a", help="search all versions, without filtering out incompatible ones", ), -) -> None: + json_output: bool = typer.Option( + False, + "--json", + help="output results in JSON format", + ), +) -> None | str: """ Search for available versions of cross-build environment. """ @@ -175,40 +180,70 @@ def _search( ) raise typer.Exit(1) - table = [] - columns = [ - # column name, width - ("Version", 10), - ("Python", 10), - ("Emscripten", 10), - ("pyodide-build", 25), - ("Compatible", 10), - ] - header = [f"{name:{width}}" for name, width in columns] - divider = ["-" * width for _, width in columns] - - table.append("\t".join(header)) - table.append("\t".join(divider)) - - for release in releases: - compatible = ( - "Yes" - if release.is_compatible( - python_version=local["python"], - pyodide_build_version=local["pyodide-build"], - ) - else "No" - ) - pyodide_build_range = f"{release.min_pyodide_build_version or ''} - {release.max_pyodide_build_version or ''}" - - row = [ - f"{release.version:{columns[0][1]}}", - f"{release.python_version:{columns[1][1]}}", - f"{release.emscripten_version:{columns[2][1]}}", - f"{pyodide_build_range:{columns[3][1]}}", - f"{compatible:{columns[4][1]}}", + def _generate_json_output(releases, local) -> str: + """A helper function to help generate JSON output""" + import json + + output = { + "environments": [ + { + "version": release.version, + "python": release.python_version, + "emscripten": release.emscripten_version, + "pyodide_build": { + "min": release.min_pyodide_build_version, + "max": release.max_pyodide_build_version, + }, + "compatible": release.is_compatible( + python_version=local["python"], + pyodide_build_version=local["pyodide-build"], + ), + } + for release in releases + ] + } + return json.dumps(output, indent=2) + + def _print_table_output(releases, local) -> None: + """A helper function to print a tabular output""" + table = [] + columns = [ + ("Version", 10), + ("Python", 10), + ("Emscripten", 10), + ("pyodide-build", 25), + ("Compatible", 10), ] + header = [f"{name:{width}}" for name, width in columns] + divider = ["-" * width for _, width in columns] + + table.append("\t".join(header)) + table.append("\t".join(divider)) + + for release in releases: + compatible = ( + "Yes" + if release.is_compatible( + python_version=local["python"], + pyodide_build_version=local["pyodide-build"], + ) + else "No" + ) + pyodide_build_range = f"{release.min_pyodide_build_version or ''} - {release.max_pyodide_build_version or ''}" + + row = [ + f"{release.version:{columns[0][1]}}", + f"{release.python_version:{columns[1][1]}}", + f"{release.emscripten_version:{columns[2][1]}}", + f"{pyodide_build_range:{columns[3][1]}}", + f"{compatible:{columns[4][1]}}", + ] - table.append("\t".join(row)) + table.append("\t".join(row)) - print("\n".join(table)) + print("\n".join(table)) + + if json_output: + print(_generate_json_output(releases, local)) + else: + _print_table_output(releases, local) From a1a3a43fcadab25120476a41cbd11543bf47d1ea Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 12 Sep 2024 00:59:02 +0530 Subject: [PATCH 03/18] Add fixture to test JSON validity (non-schema) --- pyodide_build/tests/test_cli_xbuildenv.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pyodide_build/tests/test_cli_xbuildenv.py b/pyodide_build/tests/test_cli_xbuildenv.py index 4da912f..aa1cddb 100644 --- a/pyodide_build/tests/test_cli_xbuildenv.py +++ b/pyodide_build/tests/test_cli_xbuildenv.py @@ -25,6 +25,20 @@ def mock_pyodide_lock() -> PyodideLockSpec: ) +@pytest.fixture() +def is_valid_json(): + def _is_valid_json(json_str): + import json + + try: + json.loads(json_str) + except json.JSONDecodeError: + return False + return True + + return _is_valid_json + + @pytest.fixture() def mock_xbuildenv_url(tmp_path_factory, httpserver): """ From 17932567b31608c1a25c68d330a489a2b4e985e3 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 12 Sep 2024 01:00:55 +0530 Subject: [PATCH 04/18] Convert `json` import to a top-level one --- pyodide_build/tests/test_cli_xbuildenv.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyodide_build/tests/test_cli_xbuildenv.py b/pyodide_build/tests/test_cli_xbuildenv.py index aa1cddb..1eb2bd2 100644 --- a/pyodide_build/tests/test_cli_xbuildenv.py +++ b/pyodide_build/tests/test_cli_xbuildenv.py @@ -1,3 +1,4 @@ +import json import os import shutil from pathlib import Path @@ -28,8 +29,6 @@ def mock_pyodide_lock() -> PyodideLockSpec: @pytest.fixture() def is_valid_json(): def _is_valid_json(json_str): - import json - try: json.loads(json_str) except json.JSONDecodeError: From 65807d4b7258f90c8046770009650565dcb6cd10 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 12 Sep 2024 01:03:03 +0530 Subject: [PATCH 05/18] Test JSON output from xbuildenv search --- pyodide_build/tests/test_cli_xbuildenv.py | 64 +++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/pyodide_build/tests/test_cli_xbuildenv.py b/pyodide_build/tests/test_cli_xbuildenv.py index 1eb2bd2..4bb883a 100644 --- a/pyodide_build/tests/test_cli_xbuildenv.py +++ b/pyodide_build/tests/test_cli_xbuildenv.py @@ -355,3 +355,67 @@ def test_xbuildenv_search( row1 = result.stdout.splitlines()[2] assert row1.split() == ["0.1.0", "4.5.6", "1.39.8", "-", "No"] + + +def test_xbuildenv_search_json( + tmp_path, fake_xbuildenv_releases_compatible, is_valid_json +): + result = runner.invoke( + xbuildenv.app, + [ + "search", + "--metadata", + str(fake_xbuildenv_releases_compatible), + "--json", + "--all", + ], + ) + + # Sanity check + assert result.exit_code == 0, result.stdout + assert is_valid_json(result.stdout), "Output is not valid JSON" + + output = json.loads(result.stdout) + + # First, check overall structure of JSON response + assert isinstance(output, dict), "Output should be a dictionary" + assert "environments" in output, "Output should have an 'environments' key" + assert isinstance(output["environments"], list), "'environments' should be a list" + + # Now, we'll check types in each environment entry + for environment in output["environments"]: + assert isinstance(environment, dict), "Each environment should be a dictionary" + assert set(environment.keys()) == { + "version", + "python", + "emscripten", + "pyodide_build", + "compatible", + }, f"Environment {environment} has unexpected keys: {environment.keys()}" + + assert isinstance(environment["version"], str), "version should be a string" + assert isinstance(environment["python"], str), "python should be a string" + assert isinstance( + environment["emscripten"], str + ), "emscripten should be a string" + assert isinstance( + environment["compatible"], bool + ), "compatible should be either True or False" + + assert isinstance( + environment["pyodide_build"], dict + ), "pyodide_build should be a dictionary" + assert set(environment["pyodide_build"].keys()) == { + "min", + "max", + }, f"pyodide_build has unexpected keys: {environment['pyodide_build'].keys()}" + assert isinstance( + environment["pyodide_build"]["min"], (str, type(None)) + ), "pyodide_build-min should be a string or None" + assert isinstance( + environment["pyodide_build"]["max"], (str, type(None)) + ), "pyodide_build-max should be a string or None" + + assert any( + env["compatible"] for env in output["environments"] + ), "There should be at least one compatible environment" From c02e70ce7f3a0239d6eb6ba34b86b801b7bdb26c Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 12 Sep 2024 01:48:36 +0530 Subject: [PATCH 06/18] Add CHANGELOG entry for #28 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a82422..01ef9bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- The `pyodide xbuildenv search` command now accepts a `--json` flag to output the + search results in JSON format that is machine-readable. + [#28](https://github.com/pyodide/pyodide-build/pull/28) + - `pyo3_config_file` is no longer available in `pyodide config` command. Pyodide now sets `PYO3_CROSS_PYTHON_VERSION`, `PYO3_CROSS_LIB_DIR` to specify the cross compilation environment for PyO3. From 095f1a7722ecc554c9fffddb86b3b1411c9a0698 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 12 Sep 2024 02:40:50 +0530 Subject: [PATCH 07/18] Fix a failing test --- pyodide_build/xbuildenv_releases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyodide_build/xbuildenv_releases.py b/pyodide_build/xbuildenv_releases.py index 84c64b7..5cdf46b 100644 --- a/pyodide_build/xbuildenv_releases.py +++ b/pyodide_build/xbuildenv_releases.py @@ -231,7 +231,7 @@ def load_cross_build_env_metadata(url_or_filename: str) -> CrossBuildEnvMetaSpec CrossBuildEnvMetaSpec The Pyodide cross-build environment metadata """ - if url_or_filename.startswith("https"): + if url_or_filename.startswith("http"): import requests with _suppress_urllib3_logging(): From 8f8de85135ddcf24819bc0d25504a701c43d762a Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 12 Sep 2024 02:41:19 +0530 Subject: [PATCH 08/18] Standardise return type from JSON output --- pyodide_build/cli/xbuildenv.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyodide_build/cli/xbuildenv.py b/pyodide_build/cli/xbuildenv.py index e28a431..1a921ef 100644 --- a/pyodide_build/cli/xbuildenv.py +++ b/pyodide_build/cli/xbuildenv.py @@ -156,7 +156,7 @@ def _search( "--json", help="output results in JSON format", ), -) -> None | str: +) -> None: """ Search for available versions of cross-build environment. """ @@ -202,7 +202,7 @@ def _generate_json_output(releases, local) -> str: for release in releases ] } - return json.dumps(output, indent=2) + print(json.dumps(output, indent=2)) def _print_table_output(releases, local) -> None: """A helper function to print a tabular output""" @@ -244,6 +244,6 @@ def _print_table_output(releases, local) -> None: print("\n".join(table)) if json_output: - print(_generate_json_output(releases, local)) + _generate_json_output(releases, local) else: _print_table_output(releases, local) From ce1904a5e0003d96e3c5149829e9c4ab2f164047 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 12 Sep 2024 02:42:16 +0530 Subject: [PATCH 09/18] Use Unicode box-drawing characters for printing --- pyodide_build/cli/xbuildenv.py | 59 ++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/pyodide_build/cli/xbuildenv.py b/pyodide_build/cli/xbuildenv.py index 1a921ef..13a6c0f 100644 --- a/pyodide_build/cli/xbuildenv.py +++ b/pyodide_build/cli/xbuildenv.py @@ -206,7 +206,7 @@ def _generate_json_output(releases, local) -> str: def _print_table_output(releases, local) -> None: """A helper function to print a tabular output""" - table = [] + columns = [ ("Version", 10), ("Python", 10), @@ -214,12 +214,46 @@ def _print_table_output(releases, local) -> None: ("pyodide-build", 25), ("Compatible", 10), ] - header = [f"{name:{width}}" for name, width in columns] - divider = ["-" * width for _, width in columns] - table.append("\t".join(header)) - table.append("\t".join(divider)) + # Unicode box-drawing characters + top_left = "┌" + top_right = "┐" + bottom_left = "└" + bottom_right = "┘" + horizontal = "─" + vertical = "│" + t_down = "┬" + t_up = "┴" + t_right = "├" + t_left = "┤" + cross = "┼" + + # Table elements + top_border = ( + top_left + + t_down.join(horizontal * (width + 2) for _, width in columns) + + top_right + ) + header = ( + vertical + + vertical.join(f" {name:<{width}} " for name, width in columns) + + vertical + ) + separator = ( + t_right + + cross.join(horizontal * (width + 2) for _, width in columns) + + t_left + ) + bottom_border = ( + bottom_left + + t_up.join(horizontal * (width + 2) for _, width in columns) + + bottom_right + ) + ### Printing + print(top_border) + print(header) + print(separator) for release in releases: compatible = ( "Yes" @@ -232,16 +266,15 @@ def _print_table_output(releases, local) -> None: pyodide_build_range = f"{release.min_pyodide_build_version or ''} - {release.max_pyodide_build_version or ''}" row = [ - f"{release.version:{columns[0][1]}}", - f"{release.python_version:{columns[1][1]}}", - f"{release.emscripten_version:{columns[2][1]}}", - f"{pyodide_build_range:{columns[3][1]}}", - f"{compatible:{columns[4][1]}}", + f"{release.version:<{columns[0][1]}}", + f"{release.python_version:<{columns[1][1]}}", + f"{release.emscripten_version:<{columns[2][1]}}", + f"{pyodide_build_range:<{columns[3][1]}}", + f"{compatible:<{columns[4][1]}}", ] - table.append("\t".join(row)) - - print("\n".join(table)) + print(vertical + vertical.join(f" {cell} " for cell in row) + vertical) + print(bottom_border) if json_output: _generate_json_output(releases, local) From a778a0894908c698c216eb426d20a8261b57eeaa Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Thu, 12 Sep 2024 02:51:56 +0530 Subject: [PATCH 10/18] Fix failing table formatting test --- pyodide_build/tests/test_cli_xbuildenv.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pyodide_build/tests/test_cli_xbuildenv.py b/pyodide_build/tests/test_cli_xbuildenv.py index 4bb883a..e623ff8 100644 --- a/pyodide_build/tests/test_cli_xbuildenv.py +++ b/pyodide_build/tests/test_cli_xbuildenv.py @@ -344,8 +344,9 @@ def test_xbuildenv_search( assert result.exit_code == 0, result.stdout - header = result.stdout.splitlines()[0] - assert header.split() == [ + lines = result.stdout.splitlines() + header = lines[1].strip().split("│")[1:-1] + assert [col.strip() for col in header] == [ "Version", "Python", "Emscripten", @@ -353,8 +354,8 @@ def test_xbuildenv_search( "Compatible", ] - row1 = result.stdout.splitlines()[2] - assert row1.split() == ["0.1.0", "4.5.6", "1.39.8", "-", "No"] + row1 = lines[3].strip().split("│")[1:-1] + assert [col.strip() for col in row1] == ["0.1.0", "4.5.6", "1.39.8", "-", "No"] def test_xbuildenv_search_json( From f8f639697f7d873c57dd15f32304d2ace974d0dd Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:19:25 +0530 Subject: [PATCH 11/18] Move note to "Added" section --- CHANGELOG.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01ef9bc..c246225 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased -### Changed +### Added - The `pyodide xbuildenv search` command now accepts a `--json` flag to output the search results in JSON format that is machine-readable. [#28](https://github.com/pyodide/pyodide-build/pull/28) +### Changed + - `pyo3_config_file` is no longer available in `pyodide config` command. Pyodide now sets `PYO3_CROSS_PYTHON_VERSION`, `PYO3_CROSS_LIB_DIR` to specify the cross compilation environment for PyO3. @@ -30,7 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.27.2] - 2024/07/11 -## Changed +### Changed - `pyodide py-compile` command now accepts `excludes` flag. [#9](https://github.com/pyodide/pyodide-build/pull/9) @@ -40,7 +42,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.27.1] - 2024/06/28 -## Changed +### Changed - ported f2c_fixes patch from https://github.com/pyodide/pyodide/pull/4822 From a90b612863d502f1946b84df47cf0c9d73731b3a Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 16 Sep 2024 18:42:42 +0530 Subject: [PATCH 12/18] Shift to a `MetadataView` dataclass --- pyodide_build/views.py | 93 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 pyodide_build/views.py diff --git a/pyodide_build/views.py b/pyodide_build/views.py new file mode 100644 index 0000000..93870d7 --- /dev/null +++ b/pyodide_build/views.py @@ -0,0 +1,93 @@ +# Class for generating "views", i.e., tabular and JSON outputs from +# metadata objects, currently used in the xbuildenv CLI (search command). + + +import json +from dataclasses import dataclass + + +@dataclass +class MetadataView: + version: str + python: str + emscripten: str + pyodide_build: dict[str, str | None] + compatible: bool + + @classmethod + def to_json(cls, views: list["MetadataView"]) -> None: + result = json.dumps( + { + "environments": [ + { + "version": view.version, + "python": view.python, + "emscripten": view.emscripten, + "pyodide_build": view.pyodide_build, + "compatible": view.compatible, + } + for view in views + ] + }, + indent=2, + ) + print(result) + + @classmethod + def to_table(cls, views: list["MetadataView"]) -> None: + columns = [ + ("Version", 10), + ("Python", 10), + ("Emscripten", 10), + ("pyodide-build", 25), + ("Compatible", 10), + ] + + # Unicode box-drawing characters + top_left, top_right = "┌", "┐" + bottom_left, bottom_right = "└", "┘" + horizontal, vertical = "─", "│" + t_down, t_up, t_right, t_left = "┬", "┴", "├", "┤" + cross = "┼" + + # Table elements + top_border = ( + top_left + + t_down.join(horizontal * (width + 2) for _, width in columns) + + top_right + ) + header = ( + vertical + + vertical.join(f" {name:<{width}} " for name, width in columns) + + vertical + ) + separator = ( + t_right + + cross.join(horizontal * (width + 2) for _, width in columns) + + t_left + ) + bottom_border = ( + bottom_left + + t_up.join(horizontal * (width + 2) for _, width in columns) + + bottom_right + ) + + ### Printing + table = [top_border, header, separator] + for view in views: + pyodide_build_range = ( + f"{view.pyodide_build['min'] or ''} - {view.pyodide_build['max'] or ''}" + ) + row = [ + f"{view.version:<{columns[0][1]}}", + f"{view.python:<{columns[1][1]}}", + f"{view.emscripten:<{columns[2][1]}}", + f"{pyodide_build_range:<{columns[3][1]}}", + f"{'Yes' if view.compatible else 'No':<{columns[4][1]}}", + ] + table.append( + vertical + vertical.join(f" {cell} " for cell in row) + vertical + ) + table.append(bottom_border) + + print("\n".join(table)) From 4876f2929e72970f30ba6457b70fed9f33b4be87 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 16 Sep 2024 18:43:37 +0530 Subject: [PATCH 13/18] Reorder `to_table()` and `to_json()` methods --- pyodide_build/views.py | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/pyodide_build/views.py b/pyodide_build/views.py index 93870d7..bfa31a8 100644 --- a/pyodide_build/views.py +++ b/pyodide_build/views.py @@ -14,25 +14,6 @@ class MetadataView: pyodide_build: dict[str, str | None] compatible: bool - @classmethod - def to_json(cls, views: list["MetadataView"]) -> None: - result = json.dumps( - { - "environments": [ - { - "version": view.version, - "python": view.python, - "emscripten": view.emscripten, - "pyodide_build": view.pyodide_build, - "compatible": view.compatible, - } - for view in views - ] - }, - indent=2, - ) - print(result) - @classmethod def to_table(cls, views: list["MetadataView"]) -> None: columns = [ @@ -89,5 +70,23 @@ def to_table(cls, views: list["MetadataView"]) -> None: vertical + vertical.join(f" {cell} " for cell in row) + vertical ) table.append(bottom_border) - print("\n".join(table)) + + @classmethod + def to_json(cls, views: list["MetadataView"]) -> None: + result = json.dumps( + { + "environments": [ + { + "version": view.version, + "python": view.python, + "emscripten": view.emscripten, + "pyodide_build": view.pyodide_build, + "compatible": view.compatible, + } + for view in views + ] + }, + indent=2, + ) + print(result) From 180295c9f8e59c0978b6078997bdad01348fbeae Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 16 Sep 2024 18:44:58 +0530 Subject: [PATCH 14/18] Use `MetadataView` classmethods for outputs --- pyodide_build/cli/xbuildenv.py | 115 ++++++--------------------------- 1 file changed, 19 insertions(+), 96 deletions(-) diff --git a/pyodide_build/cli/xbuildenv.py b/pyodide_build/cli/xbuildenv.py index 13a6c0f..bc3a14e 100644 --- a/pyodide_build/cli/xbuildenv.py +++ b/pyodide_build/cli/xbuildenv.py @@ -4,6 +4,7 @@ from ..build_env import local_versions from ..common import xbuildenv_dirname +from ..views import MetadataView from ..xbuildenv import CrossBuildEnvManager from ..xbuildenv_releases import ( cross_build_env_metadata_url, @@ -180,103 +181,25 @@ def _search( ) raise typer.Exit(1) - def _generate_json_output(releases, local) -> str: - """A helper function to help generate JSON output""" - import json - - output = { - "environments": [ - { - "version": release.version, - "python": release.python_version, - "emscripten": release.emscripten_version, - "pyodide_build": { - "min": release.min_pyodide_build_version, - "max": release.max_pyodide_build_version, - }, - "compatible": release.is_compatible( - python_version=local["python"], - pyodide_build_version=local["pyodide-build"], - ), - } - for release in releases - ] - } - print(json.dumps(output, indent=2)) - - def _print_table_output(releases, local) -> None: - """A helper function to print a tabular output""" - - columns = [ - ("Version", 10), - ("Python", 10), - ("Emscripten", 10), - ("pyodide-build", 25), - ("Compatible", 10), - ] - - # Unicode box-drawing characters - top_left = "┌" - top_right = "┐" - bottom_left = "└" - bottom_right = "┘" - horizontal = "─" - vertical = "│" - t_down = "┬" - t_up = "┴" - t_right = "├" - t_left = "┤" - cross = "┼" - - # Table elements - top_border = ( - top_left - + t_down.join(horizontal * (width + 2) for _, width in columns) - + top_right - ) - header = ( - vertical - + vertical.join(f" {name:<{width}} " for name, width in columns) - + vertical + # Generate views for the metadata objects (currently tabular or JSON) + views = [ + MetadataView( + version=release.version, + python=release.python_version, + emscripten=release.emscripten_version, + pyodide_build={ + "min": release.min_pyodide_build_version, + "max": release.max_pyodide_build_version, + }, + compatible=release.is_compatible( + python_version=local["python"], + pyodide_build_version=local["pyodide-build"], + ), ) - separator = ( - t_right - + cross.join(horizontal * (width + 2) for _, width in columns) - + t_left - ) - bottom_border = ( - bottom_left - + t_up.join(horizontal * (width + 2) for _, width in columns) - + bottom_right - ) - - ### Printing - print(top_border) - print(header) - print(separator) - for release in releases: - compatible = ( - "Yes" - if release.is_compatible( - python_version=local["python"], - pyodide_build_version=local["pyodide-build"], - ) - else "No" - ) - pyodide_build_range = f"{release.min_pyodide_build_version or ''} - {release.max_pyodide_build_version or ''}" - - row = [ - f"{release.version:<{columns[0][1]}}", - f"{release.python_version:<{columns[1][1]}}", - f"{release.emscripten_version:<{columns[2][1]}}", - f"{pyodide_build_range:<{columns[3][1]}}", - f"{compatible:<{columns[4][1]}}", - ] - - print(vertical + vertical.join(f" {cell} " for cell in row) + vertical) - print(bottom_border) + for release in releases + ] if json_output: - _generate_json_output(releases, local) + MetadataView.to_json(views) else: - _print_table_output(releases, local) + MetadataView.to_table(views) From 9325e754c7bd899d4c129c08d0a0d264cfa44e7e Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Mon, 16 Sep 2024 18:50:28 +0530 Subject: [PATCH 15/18] Run [integration] tests just to be sure From b754dfaaa4e33143471ba36fd85db2ea1688c039 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Tue, 17 Sep 2024 03:43:11 +0530 Subject: [PATCH 16/18] Add note about tabular output improvements --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c246225..43fc4f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - The `pyodide xbuildenv search` command now accepts a `--json` flag to output the - search results in JSON format that is machine-readable. + search results in JSON format that is machine-readable. The design for the regular + tabular output has been improved. [#28](https://github.com/pyodide/pyodide-build/pull/28) ### Changed From 5370c25d9da737d97d1152fda35c542b4c408ac3 Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Wed, 18 Sep 2024 15:21:21 +0530 Subject: [PATCH 17/18] Return `str` output from `MetadataView` --- pyodide_build/cli/xbuildenv.py | 4 ++-- pyodide_build/views.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyodide_build/cli/xbuildenv.py b/pyodide_build/cli/xbuildenv.py index bc3a14e..7e85b91 100644 --- a/pyodide_build/cli/xbuildenv.py +++ b/pyodide_build/cli/xbuildenv.py @@ -200,6 +200,6 @@ def _search( ] if json_output: - MetadataView.to_json(views) + print(MetadataView.to_json(views)) else: - MetadataView.to_table(views) + print(MetadataView.to_table(views)) diff --git a/pyodide_build/views.py b/pyodide_build/views.py index bfa31a8..eabe42f 100644 --- a/pyodide_build/views.py +++ b/pyodide_build/views.py @@ -15,7 +15,7 @@ class MetadataView: compatible: bool @classmethod - def to_table(cls, views: list["MetadataView"]) -> None: + def to_table(cls, views: list["MetadataView"]) -> str: columns = [ ("Version", 10), ("Python", 10), @@ -70,10 +70,10 @@ def to_table(cls, views: list["MetadataView"]) -> None: vertical + vertical.join(f" {cell} " for cell in row) + vertical ) table.append(bottom_border) - print("\n".join(table)) + return "\n".join(table) @classmethod - def to_json(cls, views: list["MetadataView"]) -> None: + def to_json(cls, views: list["MetadataView"]) -> str: result = json.dumps( { "environments": [ @@ -89,4 +89,4 @@ def to_json(cls, views: list["MetadataView"]) -> None: }, indent=2, ) - print(result) + return result From a9f9a61ee3936f05f7018f6491f934de09950a9d Mon Sep 17 00:00:00 2001 From: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> Date: Wed, 18 Sep 2024 15:23:43 +0530 Subject: [PATCH 18/18] Don't make `is_valid_json` a fixture --- pyodide_build/tests/test_cli_xbuildenv.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/pyodide_build/tests/test_cli_xbuildenv.py b/pyodide_build/tests/test_cli_xbuildenv.py index e623ff8..208aa8e 100644 --- a/pyodide_build/tests/test_cli_xbuildenv.py +++ b/pyodide_build/tests/test_cli_xbuildenv.py @@ -26,16 +26,12 @@ def mock_pyodide_lock() -> PyodideLockSpec: ) -@pytest.fixture() -def is_valid_json(): - def _is_valid_json(json_str): - try: - json.loads(json_str) - except json.JSONDecodeError: - return False - return True - - return _is_valid_json +def is_valid_json(json_str) -> bool: + try: + json.loads(json_str) + except json.JSONDecodeError: + return False + return True @pytest.fixture() @@ -358,9 +354,7 @@ def test_xbuildenv_search( assert [col.strip() for col in row1] == ["0.1.0", "4.5.6", "1.39.8", "-", "No"] -def test_xbuildenv_search_json( - tmp_path, fake_xbuildenv_releases_compatible, is_valid_json -): +def test_xbuildenv_search_json(tmp_path, fake_xbuildenv_releases_compatible): result = runner.invoke( xbuildenv.app, [