# Copyright 2025 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ It performs a number of checks to determine whether the package is ready for release. """ import re import subprocess import sys import click import magika @click.command() @click.option( "--expected-version", default="", help="Expected version string (e.g., '1.2.3'). If provided, checks will be validated against this value.", ) @click.option( "--report-only", is_flag=True, help="Print errors without failing. (Default: Fails on errors)", ) @click.option( "--check-pip-show-package-version/--no-check-pip-show-package-version", is_flag=True, default=True, help="Enable/disable version check via 'pip show'. (Default: Enabled)", ) @click.option( "--use-python-client", is_flag=True, help="Use the Python client instead of Rust client. (Default: False)", ) def main( expected_version: str, report_only: bool, check_pip_show_package_version: bool, use_python_client: bool, ) -> None: """Checks versions consistency for the `magika` package.""" if report_only: click.echo('Running in "report only" mode.') if not check_pip_show_package_version: click.echo("Skipping checking package version via pip show.") if use_python_client: click.echo("Using python client instead of Rust client.") strict_mode = not report_only if strict_mode: if expected_version == "": click.secho( "ERROR: when not using --report-only, --expected-version is required." ) sys.exit(1) with_errors = False # Get the versions module_version = getattr(magika, "__version__", "") try: instance_version = magika.Magika().get_module_version() except Exception: instance_version = "" if check_pip_show_package_version: pip_show_package_version = get_magika_package_version_via_pip_show() else: pip_show_package_version = "" if use_python_client: cli_version = instance_version else: cli_version = get_rust_cli_version() if module_version == "": click.echo("ERROR: failed to get module_version.") with_errors = True if instance_version == "": click.echo("ERROR: failed to get instance_version.") with_errors = True if check_pip_show_package_version and pip_show_package_version == "": click.echo("ERROR: failed to get pip_show_package_version.") with_errors = True if cli_version == "": click.echo("ERROR: failed to get cli_version.") with_errors = True click.echo( f"Extracted versions: {expected_version=}, {module_version=}, {instance_version=}, {pip_show_package_version=}, {cli_version=}." ) if expected_version != "" and module_version != expected_version: click.echo(f"ERROR: {module_version=} != {expected_version=}") with_errors = True if module_version != instance_version: click.echo(f"ERROR: {instance_version=} != {module_version=}") with_errors = True if check_pip_show_package_version: if module_version != pip_show_package_version: click.echo(f"ERROR: {module_version=} != {pip_show_package_version=}") with_errors = True # From now on, we assume all the python-related versions are the same. If # they are not, we would have at least one error above. if not is_valid_python_version(module_version): click.echo(f"ERROR: {module_version=} is not a valid python version.") with_errors = True if module_version.endswith("-dev") or cli_version.endswith("-dev"): click.echo("ERROR: One of the versions is a -dev version.") with_errors = True if cli_version.endswith("-rc") and not module_version.endswith("-rc"): click.echo("ERROR: The CLI has an -rc version, but the python module does not.") with_errors = True if with_errors: click.secho("There was at least one error.", fg="red") if strict_mode: sys.exit(1) else: click.secho("All tests pass!", fg="green") def get_rust_cli_version() -> str: """Get the version of the Rust CLI `magika`. Returns an empty string ("") if an error is encountered. """ try: result = subprocess.run( ["magika", "--version"], capture_output=True, text=True, check=True ) parts = result.stdout.strip().split() if len(parts) < 2: click.echo("ERROR: Could not parse CLI version output.") return "" cli_version = parts[1] return cli_version except subprocess.CalledProcessError as e: click.echo(f"ERROR: Could not retrieve CLI version: {e}") return "" def get_magika_package_version_via_pip_show() -> str: try: r = subprocess.run( ["python3", "-m", "pip", "show", "magika"], capture_output=True, text=True ) lines = r.stdout.strip().split("\n") for line in lines: if line.startswith("Version: "): return line.split(": ", 1)[1] click.echo( f"ERROR: Could not extract the package version via pip show. Output from pip show:\nstdout={r.stdout}\n\nstderr={r.stderr}" ) return "" except subprocess.CalledProcessError as e: click.echo(f"ERROR: Could not retrieve package version via pip show: {e}") return "" def is_valid_python_version(version: str) -> bool: # Regex from PEP440: '[N!]N(.N)*[{a|b|rc}N][.postN][.devN]' PEP440_CANONICAL_REGEX = re.compile( r""" ^ # Optional Epoch segment (e.g., 1!) (?P\d+!)? # Required Release segment (e.g., 1.2.3) (?P[0-9]+(?:\.[0-9]+)*) # Optional Pre-release segment (e.g., a1, b2, rc3) (?P
    (?:a|b|rc)
    [0-9]+
)?

# Optional Post-release segment (e.g., .post4)
(?P
    (?:\.post[0-9]+)
)?

# Optional Development release segment (e.g., .dev5)
(?P
    (?:\.dev[0-9]+)
)?
$
""",
        re.VERBOSE | re.IGNORECASE,
    )
    return PEP440_CANONICAL_REGEX.fullmatch(version) is not None


def test_is_valid_python_version() -> None:
    assert is_valid_python_version("1.2.3") is True
    assert is_valid_python_version("1.2.3.rc") is False
    assert is_valid_python_version("1.2.3.rc0") is False
    assert is_valid_python_version("1.2.3rc0") is True
    assert is_valid_python_version("1.2.3rc1") is True
    assert is_valid_python_version("1.2.3-dev") is False
    assert is_valid_python_version("1.2.3.dev0") is True
    assert is_valid_python_version("1.2.3-dev0") is False


if __name__ == "__main__":
    main()