From 176c9661607ab26c0ac46205d8ebe299a1d61a21 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Tue, 1 Apr 2025 16:08:36 -0400 Subject: [PATCH 01/19] Use modal.com for running tests in CI They have graciously offered to sponsor us and provide CI machines with GPUs. --- .ci/config.toml | 7 + .ci/modal.com/.gitignore | 2 + .ci/modal.com/.python-version | 1 + .ci/modal.com/README.md | 0 .ci/modal.com/modal_runner.py | 323 ++++++++++++++++++ .ci/modal.com/pyproject.toml | 9 + .ci/modal.com/uv.lock | 593 ++++++++++++++++++++++++++++++++++ .github/workflows/rust.yml | 41 ++- 8 files changed, 971 insertions(+), 5 deletions(-) create mode 100644 .ci/config.toml create mode 100644 .ci/modal.com/.gitignore create mode 100644 .ci/modal.com/.python-version create mode 100644 .ci/modal.com/README.md create mode 100644 .ci/modal.com/modal_runner.py create mode 100644 .ci/modal.com/pyproject.toml create mode 100644 .ci/modal.com/uv.lock diff --git a/.ci/config.toml b/.ci/config.toml new file mode 100644 index 00000000..58194c71 --- /dev/null +++ b/.ci/config.toml @@ -0,0 +1,7 @@ +# Configuration for the target architecture building for on the GHA runner +# and subsequently running on Modal (which uses Linux containers). +[target.x86_64-unknown-linux-gnu] +# The command to execute instead of the compiled test binary. Cargo will append the +# actual path to the compiled test executable and any arguments (like --list, --exact, +# --nocapture) after this command. +runner = ["uv", "run", ".ci/modal.com/modal_test_runner.py"] diff --git a/.ci/modal.com/.gitignore b/.ci/modal.com/.gitignore new file mode 100644 index 00000000..93526df6 --- /dev/null +++ b/.ci/modal.com/.gitignore @@ -0,0 +1,2 @@ +venv/ +__pycache__/ diff --git a/.ci/modal.com/.python-version b/.ci/modal.com/.python-version new file mode 100644 index 00000000..24ee5b1b --- /dev/null +++ b/.ci/modal.com/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/.ci/modal.com/README.md b/.ci/modal.com/README.md new file mode 100644 index 00000000..e69de29b diff --git a/.ci/modal.com/modal_runner.py b/.ci/modal.com/modal_runner.py new file mode 100644 index 00000000..72522002 --- /dev/null +++ b/.ci/modal.com/modal_runner.py @@ -0,0 +1,323 @@ +import modal +import os +import subprocess +import sys +import time +from pathlib import Path +from typing import List, Dict, Any, Optional, Tuple +from dataclasses import dataclass + +APP_PREFIX = "rust-cuda-test-runner" +RUNNER_LOG_PREFIX = "[runner]" +REMOTE_LOG_PREFIX = "[remote]" + +TestResult = Dict[str, Any] + + +@dataclass(frozen=True) # Immutable config object +class Config: + """Holds validated configuration for the runner.""" + + local_test_binary_path: Path + test_binary_args: List[str] + modal_gpu_type: str + os_name: str # Original GHA OS name + cuda_version: str + modal_token_id: str + modal_token_secret: str + nvidia_os_tag: str + gpu_config: str + + +# --- Logging Helper --- +def log_runner(message: str) -> None: + """Logs a message to stderr with the Runner prefix.""" + print(f"{RUNNER_LOG_PREFIX} {message}", file=sys.stderr) + + +def log_remote(message: str) -> None: + """Logs a message to stderr with the Remote prefix (used inside Modal function).""" + print(f"{REMOTE_LOG_PREFIX} {message}", file=sys.stderr) + + +# --- Configuration Loading and Validation --- +# Mapping from GH Actions OS name to Nvidia registry tags +OS_NAME_TO_NVIDIA_TAG: Dict[str, str] = { + "ubuntu-20.04": "ubuntu20.04", + "ubuntu-24.04": "ubuntu24.04", + # Add other supported Linux GHA OS identifiers here if needed +} + + +def load_and_validate_config(argv: List[str], env: Dict[str, str]) -> Config: + """Loads config from args/env, validates, and returns a Config object or raises ValueError.""" + log_runner("Loading and validating configuration...") + if len(argv) < 2: + raise ValueError( + "Usage: python modal_test_runner.py [test_args...]" + ) + + local_test_binary_path = Path(argv[1]).resolve() + test_binary_args = argv[2:] + + # Read from env dictionary + modal_gpu_type = env.get("MODAL_GPU_TYPE") + os_name = env.get("RUNNER_OS_NAME") + cuda_version = env.get("RUNNER_CUDA_VERSION") + modal_token_id = env.get("MODAL_TOKEN_ID") + modal_token_secret = env.get("MODAL_TOKEN_SECRET") + + required_vars: Dict[str, Optional[str]] = { + "MODAL_GPU_TYPE": modal_gpu_type, + "RUNNER_OS_NAME": os_name, + "RUNNER_CUDA_VERSION": cuda_version, + "MODAL_TOKEN_ID": modal_token_id, + "MODAL_TOKEN_SECRET": modal_token_secret, + } + missing = [k for k, v in required_vars.items() if not v] + if missing: + raise ValueError( + f"Missing required environment variables: {', '.join(missing)}" + ) + + # --- Assertions for type checking --- + assert modal_gpu_type is not None + assert os_name is not None + assert cuda_version is not None + assert modal_token_id is not None + assert modal_token_secret is not None + + # Validate OS and get Nvidia Tag + if os_name not in OS_NAME_TO_NVIDIA_TAG: + supported = list(OS_NAME_TO_NVIDIA_TAG.keys()) + raise ValueError( + f"OS '{os_name}' not supported Linux target. Supported: {supported}" + ) + nvidia_os_tag = OS_NAME_TO_NVIDIA_TAG[os_name] + + log_runner("Configuration loaded successfully.") + return Config( + local_test_binary_path=local_test_binary_path, + test_binary_args=test_binary_args, + modal_gpu_type=modal_gpu_type, + os_name=os_name, + cuda_version=cuda_version, + modal_token_id=modal_token_id, + modal_token_secret=modal_token_secret, + nvidia_os_tag=nvidia_os_tag, + gpu_config=modal_gpu_type, + ) + + +# --- Image Creation (Refined based on Modal guidance) --- +def create_runtime_image( + os_tag: str, cuda_version: str, binary_path_to_add: Path +) -> modal.Image: + """Creates the Modal runtime image, adding the specific test binary at runtime + as the LAST step for better caching.""" + registry_image_tag = f"nvidia/cuda:{cuda_version}-runtime-{os_tag}" + log_runner(f"Defining image from registry: {registry_image_tag}") + remote_bin_path = "/app/test_executable" # Define remote path + + try: + # Define the base image and all steps *before* adding the local file + image_base = ( + modal.Image.from_registry(registry_image_tag) + .env({"RUST_BACKTRACE": "1"}) + .run_commands( + # These commands modify the filesystem *before* the file is added + "mkdir -p /app", + "echo '[Remote] Container environment baseline ready.'", # Use log_remote style + ) + # Add any other apt_install, pip_install, run_commands etc. HERE if needed later + ) + + # *** Apply add_local_file as the VERY LAST step. *** + image_with_file = image_base.add_local_file( + local_path=binary_path_to_add, + remote_path=remote_bin_path, + copy=False, # IMPORTANT: Adds file at runtime. This is the default but we want to be extra sure. + ) + + log_runner( + f"Configured to add binary '{binary_path_to_add.name}' to '{remote_bin_path}' at runtime (last image step)." + ) + return image_with_file + + except FileNotFoundError: + log_runner(f"Error: Local binary path not found: {binary_path_to_add}") + raise ValueError(f"Test binary not found at {binary_path_to_add}") + except Exception as e: + log_runner( + f"Error defining Modal image from registry '{registry_image_tag}': {e}" + ) + raise ValueError("Failed to define Modal image. Verify CUDA/OS tag.") from e + + +# --- Command Preparation --- +def prepare_remote_command(base_args: List[str]) -> List[str]: + """Adds --nocapture to test args if appropriate.""" + remote_command = ["/app/test_executable"] + base_args + is_list_command = any(arg == "--list" for arg in base_args) + # Check if separator '--' exists + try: + separator_index = remote_command.index("--") + test_runner_args = remote_command[separator_index + 1 :] + has_nocapture = "--nocapture" in test_runner_args + except ValueError: # '--' not found + separator_index = -1 + has_nocapture = False + + if not is_list_command: + if separator_index == -1: + # Add separator if not present + remote_command.append("--") + if not has_nocapture: + # Add --nocapture after separator (or at end if separator was just added) + remote_command.append("--nocapture") + + return remote_command + + +app = modal.App() # Name will be set dynamically later + + +@app.function() +def execute_test_binary(command: List[str]) -> TestResult: + """Executes the test binary within the Modal container.""" + start_time = time.monotonic() + try: + # Ensure the binary is executable after mounting + subprocess.run(["chmod", "+x", command[0]], check=True, cwd="/app") + except Exception as e: + log_remote(f"Error setting executable permission on {command[0]}: {e}") + # Still try to run, maybe permissions were already ok + + log_remote(f"Executing command: {' '.join(command)}") + env: Dict[str, str] = os.environ.copy() # Inherit container env + process: Optional[subprocess.CompletedProcess] = None + status: str = "failed" + stdout: str = "" + stderr: str = "" + retcode: int = -99 + + try: + process = subprocess.run( + command, + cwd="/app", + capture_output=True, + text=True, + check=False, + env=env, + ) + status = "ok" if process.returncode == 0 else "failed" + stdout, stderr, retcode = process.stdout, process.stderr, process.returncode + except subprocess.TimeoutExpired as e: + stderr = ( + e.stderr or "" + ) + "\nTimeoutException: Test execution exceeded timeout." + retcode = -1 + log_remote("Test execution TIMED OUT.") + except Exception as e: + stderr = f"[Remote] Runner Exception: Failed to execute binary: {e}\n" + retcode = -2 + log_remote(stderr.strip()) + + end_time = time.monotonic() + duration_ms: int = int((end_time - start_time) * 1000) + log_remote( + f"Execution finished. Status: {status}, Return Code: {retcode}, Duration: {duration_ms}ms" + ) + + return { + "status": status, + "stdout": stdout, + "stderr": stderr, + "returncode": retcode, + "duration_ms": duration_ms, + } + + +# --- Main Execution Flow --- +def run_tests_on_modal(config: Config) -> int: + """Orchestrates Modal setup, execution, and result processing. Returns exit code.""" + log_runner(f"Preparing Modal execution for {config.local_test_binary_path.name}") + + # Dynamically configure app name based on runtime config + app.name = f"{APP_PREFIX}-{config.os_name.replace('.', '-')}-{config.cuda_version.replace('.', '-')}" + log_runner(f"Using Modal App name: {app.name}") + + try: + # Create image definition, including the specific binary to add at runtime + runtime_image = create_runtime_image( + config.nvidia_os_tag, config.cuda_version, config.local_test_binary_path + ) + + # Prepare remote command + remote_command = prepare_remote_command(config.test_binary_args) + log_runner(f"Prepared remote command: {' '.join(remote_command)}") + + # Configure the function call with dynamic resources + # The image definition now includes the instruction to add the specific binary + configured_function = execute_test_binary.options( + image=runtime_image, + gpu=config.gpu_config, + timeout=600, + ) + + # Invoke Modal Function + log_runner(f"Invoking Modal function '{app.name}/execute_test_binary'...") + result: TestResult = configured_function.remote(remote_command) + log_runner( + f"Modal execution finished for {config.local_test_binary_path.name}." + ) + + # --- Process Results --- + print(result.get("stdout", ""), end="") # Print remote stdout to local stdout + remote_stderr: str = result.get("stderr", "") + if remote_stderr: + log_runner("--- Remote Stderr ---") + print(remote_stderr.strip(), file=sys.stderr) + log_runner("--- End Remote Stderr ---") + + return result.get("returncode", 1) # Return the exit code + + except modal.exception.AuthError as e: + log_runner(f"Modal Authentication Failed: {e}") + log_runner( + "Ensure MODAL_TOKEN_ID and MODAL_TOKEN_SECRET env vars are set correctly." + ) + return 1 # Return error code + except ValueError as e: # Catch config/setup errors (incl. image definition) + log_runner(f"Setup Error: {e}") + return 1 + except Exception as e: + log_runner(f"An unexpected error occurred during Modal orchestration: {e}") + import traceback + + traceback.print_exc(file=sys.stderr) + return 1 # Return error code + + +# --- Entry Point --- +# This will be called by cargo with thet test binary path and args +if __name__ == "__main__": + exit_code = 1 # Default exit code + try: + # Pass actual env vars and command line args from execution context + config = load_and_validate_config(sys.argv, os.environ.copy()) + exit_code = run_tests_on_modal(config) + except ValueError as e: + log_runner(f"Configuration Error: {e}") + sys.exit(1) + except ( + Exception + ) as e: # Catch unexpected errors during initial setup before run_tests_on_modal + log_runner(f"Unhandled script error: {e}") + import traceback + + traceback.print_exc(file=sys.stderr) + sys.exit(1) + + log_runner(f"Exiting with code {exit_code}") + sys.exit(exit_code) diff --git a/.ci/modal.com/pyproject.toml b/.ci/modal.com/pyproject.toml new file mode 100644 index 00000000..172234ef --- /dev/null +++ b/.ci/modal.com/pyproject.toml @@ -0,0 +1,9 @@ +[project] +name = "modal-com" +version = "0.1.0" +description = "Cargo test runner script for Modal" +readme = "README.md" +requires-python = ">=3.13" +dependencies = [ + "modal>=0.73.138", +] diff --git a/.ci/modal.com/uv.lock b/.ci/modal.com/uv.lock new file mode 100644 index 00000000..7ac9b56c --- /dev/null +++ b/.ci/modal.com/uv.lock @@ -0,0 +1,593 @@ +version = 1 +revision = 1 +requires-python = ">=3.13" + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265 }, +] + +[[package]] +name = "aiohttp" +version = "3.11.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/18/31398875dc7b9815767370f60f44284155f2e1c1b87ec721c1b0ee61d1e5/aiohttp-3.11.15.tar.gz", hash = "sha256:b9b9a1e592ac8fcc4584baea240e41f77415e0de98932fdf19565aa3b6a02d0b", size = 7676625 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/38/8324c180c8d90ff9afc724f5882e33d6ae6061db954b409dd0bd03bf767f/aiohttp-3.11.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c37aa3eb8eb92f3793f0c1e73f212a76cbc8d363e9990df54b5b7099f75ce740", size = 697977 }, + { url = "https://files.pythonhosted.org/packages/5f/2b/fe647a80696877cb99bcd3324672e054112ed022dce201cdc1491bf16314/aiohttp-3.11.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5b5edd482ff0a8585b3f4e8b3681819447324166a43a5588800a5bca340dbf27", size = 461067 }, + { url = "https://files.pythonhosted.org/packages/63/d6/8b0b6ff2557df59125527bb87ec74344da940dfa4061f41a91857ff8c327/aiohttp-3.11.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d8c22c91bdb7417bd4f5119242dbd2e2140c0e9de47342c765f127f897eb57", size = 453367 }, + { url = "https://files.pythonhosted.org/packages/69/10/a9beb08734dfadf8e322efdb79a2b31098ec5b2f29bb7378533d3b4a145c/aiohttp-3.11.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b03093d4140d926965d23497a059ec59c8c07e602d2489ce5fb990f3a897db4", size = 1666611 }, + { url = "https://files.pythonhosted.org/packages/ca/ec/9a6724dd72a356298049913f4590b281fbe01386bc7d0e0c3cc6bff46af0/aiohttp-3.11.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05eea49d598c4ece6f285e00464de206f838b48ff21938d5aa9c394e115945b9", size = 1721916 }, + { url = "https://files.pythonhosted.org/packages/5a/61/dbcd2f2ff8f8282a56166b308dd9a1ca67ff844e05c20a7eade80f8fc99f/aiohttp-3.11.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63f8d6106566f98fcfde7a643c9da52d90679b8592dea76c4adfc3cd5d06d22c", size = 1774421 }, + { url = "https://files.pythonhosted.org/packages/4f/fe/fa6952f41486a18860804d685b978793bd4accd5dd2a5a49801ad76c16e2/aiohttp-3.11.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36a490f1ebe0b982366314c77f02258f87bd5d9bd362839dc6a24188447f37eb", size = 1677753 }, + { url = "https://files.pythonhosted.org/packages/4d/f4/e7ee9b27adae614dd30598cbbb64c6484fa475864392781da7bd3ab43871/aiohttp-3.11.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73a7f6283634dd30f93b9a67c414c00517869478b50361c503535e075fa07eaf", size = 1608419 }, + { url = "https://files.pythonhosted.org/packages/30/c1/f0513cf705ea1e6366530f5b9f1683129e6f9a70e32ee0a730fcbf85f393/aiohttp-3.11.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0e97c1e55f6931f07ecaf53aef8352def8386adfd0cd3caa6429cc40e886d6a9", size = 1622394 }, + { url = "https://files.pythonhosted.org/packages/c9/b9/65c94e9578591b49ee4c7cf26131cd0c4e693f9173e78a9aba4f8c60d24d/aiohttp-3.11.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d8370d31e6d8ecccd97cd791c466d0acb56527df51b0c105d7ea54c7fcc0f75b", size = 1660913 }, + { url = "https://files.pythonhosted.org/packages/65/28/bbeba6d8a30a42ba591f7feb333ff2a01ed1e2ee0b31bc7195354c2c605a/aiohttp-3.11.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c2de66177e087999568638c02639cf0248011b5c7fca69b006947153c534fcab", size = 1622763 }, + { url = "https://files.pythonhosted.org/packages/d1/39/e123f8dde599ec42da17cf094cfacd343569617c9bce68fe257e627395a9/aiohttp-3.11.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:edcddb97574402ff7481bc6f70819ba863e77b0be58a840ed5f59d52d2f20a71", size = 1687127 }, + { url = "https://files.pythonhosted.org/packages/35/cf/1eaaae2dd6f0ea36926cdd0af131d11315a3a06d0a6d9bc79f6e8b11c090/aiohttp-3.11.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:29cce2a7e009494e675018c0b1819a133befbab8629c797276c5d793bbdf1138", size = 1718485 }, + { url = "https://files.pythonhosted.org/packages/80/09/1824997f72def3567f451130b7618beab65cf5e99d11611dae87b251de82/aiohttp-3.11.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:825ec92391e3e4ddda74de79ed0f8b443e9b412a0c9c82618ca2163abd875df5", size = 1675221 }, + { url = "https://files.pythonhosted.org/packages/56/2f/c1e86f6452a9abae346b31c48bfd476984baa3a73978ad3eabf53139c513/aiohttp-3.11.15-cp313-cp313-win32.whl", hash = "sha256:430f9707f0c1239a92bff7769b0db185ef400278dc63c89f88ed1bd5153aab7a", size = 410657 }, + { url = "https://files.pythonhosted.org/packages/ef/9e/9913ed27bb195224c3fc6a41f0b169d6d93e3843cd792d24013a112ffb3e/aiohttp-3.11.15-cp313-cp313-win_amd64.whl", hash = "sha256:f30e6980ec5d6ad815a233e19e39fe27ea94b1081c31c8aa1df1b629da3737b8", size = 436351 }, +] + +[[package]] +name = "aiosignal" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/b5/6d55e80f6d8a08ce22b982eafa278d823b541c925f11ee774b0b9c43473d/aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54", size = 19424 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597 }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + +[[package]] +name = "anyio" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 }, +] + +[[package]] +name = "certifi" +version = "2025.1.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, +] + +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "fastapi" +version = "0.115.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164 }, +] + +[[package]] +name = "frozenlist" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/ed/0f4cec13a93c02c47ec32d81d11c0c1efbadf4a471e3f3ce7cad366cbbd3/frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817", size = 39930 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/3b/915f0bca8a7ea04483622e84a9bd90033bab54bdf485479556c74fd5eaf5/frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953", size = 91538 }, + { url = "https://files.pythonhosted.org/packages/c7/d1/a7c98aad7e44afe5306a2b068434a5830f1470675f0e715abb86eb15f15b/frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0", size = 52849 }, + { url = "https://files.pythonhosted.org/packages/3a/c8/76f23bf9ab15d5f760eb48701909645f686f9c64fbb8982674c241fbef14/frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2", size = 50583 }, + { url = "https://files.pythonhosted.org/packages/1f/22/462a3dd093d11df623179d7754a3b3269de3b42de2808cddef50ee0f4f48/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f", size = 265636 }, + { url = "https://files.pythonhosted.org/packages/80/cf/e075e407fc2ae7328155a1cd7e22f932773c8073c1fc78016607d19cc3e5/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608", size = 270214 }, + { url = "https://files.pythonhosted.org/packages/a1/58/0642d061d5de779f39c50cbb00df49682832923f3d2ebfb0fedf02d05f7f/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b", size = 273905 }, + { url = "https://files.pythonhosted.org/packages/ab/66/3fe0f5f8f2add5b4ab7aa4e199f767fd3b55da26e3ca4ce2cc36698e50c4/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840", size = 250542 }, + { url = "https://files.pythonhosted.org/packages/f6/b8/260791bde9198c87a465224e0e2bb62c4e716f5d198fc3a1dacc4895dbd1/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439", size = 267026 }, + { url = "https://files.pythonhosted.org/packages/2e/a4/3d24f88c527f08f8d44ade24eaee83b2627793fa62fa07cbb7ff7a2f7d42/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de", size = 257690 }, + { url = "https://files.pythonhosted.org/packages/de/9a/d311d660420b2beeff3459b6626f2ab4fb236d07afbdac034a4371fe696e/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641", size = 253893 }, + { url = "https://files.pythonhosted.org/packages/c6/23/e491aadc25b56eabd0f18c53bb19f3cdc6de30b2129ee0bc39cd387cd560/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e", size = 267006 }, + { url = "https://files.pythonhosted.org/packages/08/c4/ab918ce636a35fb974d13d666dcbe03969592aeca6c3ab3835acff01f79c/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9", size = 276157 }, + { url = "https://files.pythonhosted.org/packages/c0/29/3b7a0bbbbe5a34833ba26f686aabfe982924adbdcafdc294a7a129c31688/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03", size = 264642 }, + { url = "https://files.pythonhosted.org/packages/ab/42/0595b3dbffc2e82d7fe658c12d5a5bafcd7516c6bf2d1d1feb5387caa9c1/frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c", size = 44914 }, + { url = "https://files.pythonhosted.org/packages/17/c4/b7db1206a3fea44bf3b838ca61deb6f74424a8a5db1dd53ecb21da669be6/frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28", size = 51167 }, + { url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 }, +] + +[[package]] +name = "grpclib" +version = "0.4.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h2" }, + { name = "multidict" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/b9/55936e462a5925190d7427e880b3033601d1effd13809b483d13a926061a/grpclib-0.4.7.tar.gz", hash = "sha256:2988ef57c02b22b7a2e8e961792c41ccf97efc2ace91ae7a5b0de03c363823c3", size = 61254 } + +[[package]] +name = "h2" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "hpack" }, + { name = "hyperframe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/38/d7f80fd13e6582fb8e0df8c9a653dcc02b03ca34f4d72f34869298c5baf8/h2-4.2.0.tar.gz", hash = "sha256:c8a52129695e88b1a0578d8d2cc6842bbd79128ac685463b887ee278126ad01f", size = 2150682 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/9e/984486f2d0a0bd2b024bf4bc1c62688fcafa9e61991f041fb0e2def4a982/h2-4.2.0-py3-none-any.whl", hash = "sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0", size = 60957 }, +] + +[[package]] +name = "hpack" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357 }, +] + +[[package]] +name = "hyperframe" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + +[[package]] +name = "modal" +version = "0.73.138" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "certifi" }, + { name = "click" }, + { name = "fastapi" }, + { name = "grpclib" }, + { name = "protobuf" }, + { name = "rich" }, + { name = "synchronicity" }, + { name = "toml" }, + { name = "typer" }, + { name = "types-certifi" }, + { name = "types-toml" }, + { name = "typing-extensions" }, + { name = "watchfiles" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/27/67290f59fee2e87cb0508e1c2deb5e2b937b89fb098190feef9e53455f9e/modal-0.73.138.tar.gz", hash = "sha256:943daebf7dea70d16b978c633ff3cfc1d453151043f64c7a9012e2526b6be2d2", size = 483747 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/d9/cc4812c56894458aa6361817df1faa84a67f9b2e4f99d27efdec890dccf5/modal-0.73.138-py3-none-any.whl", hash = "sha256:da60510d789509e1a5c9c4a2458e0295dfbee768eae71f0bba739b99215d2a08", size = 551175 }, +] + +[[package]] +name = "modal-com" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "modal" }, +] + +[package.metadata] +requires-dist = [{ name = "modal", specifier = ">=0.73.138" }] + +[[package]] +name = "multidict" +version = "6.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/99/68/42bf1fb4272959aa7c0775caf53265c1a0da9d77f2d4e76326296c943811/multidict-6.3.0.tar.gz", hash = "sha256:2cf3e0781febf9f093eff3eca2d6dd7954ef2969ff46f6cd95173a4db8397fd8", size = 85840 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/de/988a79bc03f03a191458d938236fb06fa7ba2e03e1fec6ce53c86ababd8a/multidict-6.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90c3c60985e7da13e44aeaaf2e1c10fe1b7825788a18c82b0f9eaeb6c4e9d9c6", size = 61608 }, + { url = "https://files.pythonhosted.org/packages/b8/30/a8e15a3ac94fba52c8a6eb85dc8552b39e60112002317f7542c890dbff15/multidict-6.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:80935f26af0eec490c4e52381a28f39b08c2bc4ef4562448890027e4a4cfa3a4", size = 36724 }, + { url = "https://files.pythonhosted.org/packages/06/49/88d4971e61d98b208c98eec56ae13af6fb128d73fee18e9bb568a7a0415a/multidict-6.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e18db3b38ac30d8cc1ddbdfc7f6d3d814c1abb8936c57bd1c09c5da02873c8c4", size = 35611 }, + { url = "https://files.pythonhosted.org/packages/e6/1b/08ba37f64d4eacfceec12cc11aecd0a6482cca2c57a94dafef41ed66dd0a/multidict-6.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c8836280abc310ea6260dac93126645d21284885008c317c2ac4281a90f1398", size = 245274 }, + { url = "https://files.pythonhosted.org/packages/bb/d9/f5c5a381cffef4bf500e710ca73d6ef00a2de9647abf7bcd0a9f032dd408/multidict-6.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5737f40ffb1926b8bf38d32fdac48a27c441b757f1bf44cbaa100dafef049a01", size = 256891 }, + { url = "https://files.pythonhosted.org/packages/17/23/553528d531fd8d93834365d8e6b7c0bda25c787a8b5ae738099266f34bd7/multidict-6.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6df1b707adab4858dfaa74877f60f07d9d6be01a57015cf4e62aa8f00f3576b", size = 253116 }, + { url = "https://files.pythonhosted.org/packages/07/d0/79229446cb1507ff5f83ae372a4648e703fda7a4f7729332da0858d47e4e/multidict-6.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:162af9c2e0743465a47e2b32d9b0a7c260b7843629b5e6f0a8a92819b5a40d27", size = 245941 }, + { url = "https://files.pythonhosted.org/packages/d4/c9/91b09bda811e212816776967a3232f8776aa846af4c44e0e9139cf73fc60/multidict-6.3.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc4e82c5db4e65703c842b0947699dd958a7262a8b854d1c19bbfe2d27be9333", size = 232343 }, + { url = "https://files.pythonhosted.org/packages/c4/94/2941f8605a3ff8aaaef31c1c8adfd7c889d78763efc4e7f963fbca96a6c4/multidict-6.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5a922601cb94f427dd96eec3a7c776537ce7490f2beb69e88a81859e537343e4", size = 249610 }, + { url = "https://files.pythonhosted.org/packages/e3/8b/04a18732ab7d29db3d6009d8cab1a737c3262cd06ba1764756edb66d9a96/multidict-6.3.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3da30fb560b37cede31306b890c982213a23fa7335d368cdc16cf7034170860b", size = 244832 }, + { url = "https://files.pythonhosted.org/packages/67/27/4fafdc178bb7c5a870ca449e922a0e069b77fda1ba4e1729fde385ca6314/multidict-6.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5a1996d963016e6486b6a672f64f868e6b4e7e9e2caf710994df11b04790903e", size = 256546 }, + { url = "https://files.pythonhosted.org/packages/a6/09/f2c0d6974d1b3ac922834eb159d39f4a7f61b4560373821e5028623645a1/multidict-6.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9584441b7a603a12aa382adf8c093ddc5a22328a108dabc6a4a112fa5b4facae", size = 252193 }, + { url = "https://files.pythonhosted.org/packages/e1/6b/7b2ec53aea30c3729ac6bd92bcc620584b08e1333621e0fe48dc5dc36fdb/multidict-6.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:71a8ce430f6c341725aefc0031626c484e0858fd95d1bf3625e0d27919d8edca", size = 247797 }, + { url = "https://files.pythonhosted.org/packages/fa/07/086ac59a24e05ba5748abc57298a27705bab824f47842494dfa4b50bff15/multidict-6.3.0-cp313-cp313-win32.whl", hash = "sha256:b7d3053742a9a559dda8598a52e0c1bcbd18258cc199cba52137ce8c8e92c537", size = 34662 }, + { url = "https://files.pythonhosted.org/packages/cf/a3/5e0b74e8c1507623b7564fa8bfd07e626d45fc05fbb03f6248902c00c749/multidict-6.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:6b7e30e4246c2cd5e6c2c907486d235e4f3e8a39979744e9e0b8090629c62da4", size = 37826 }, + { url = "https://files.pythonhosted.org/packages/33/0c/e92a3398e80339e356e7aa8b2566d075ed876f5c12e9ad08704c49301a1d/multidict-6.3.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:be034aa2841c57ea43a55bc123d8f3f41cc2d3d102a22390c863791ba6bf23f1", size = 66383 }, + { url = "https://files.pythonhosted.org/packages/02/23/21ea785c2bbd36ad832e4365ac518bc7c14c72cc8be117fccb853ac3ee1f/multidict-6.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:168a02178b7e980899f3475ff008436eaab035d50e39cb8f7de641bbe9bbc3a6", size = 38709 }, + { url = "https://files.pythonhosted.org/packages/42/7b/52f65ed679b25e16a453bbacc06892622710ad3fc31cfa5c61f862af99fd/multidict-6.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c4a6ec469c30f4162745d6485b778432c497032b8a2cb3a0332eea9d3b6aef6", size = 38314 }, + { url = "https://files.pythonhosted.org/packages/2e/5b/923e8b22268e53be4f11d0abe3f7b091e5ee7d213e7ca837f215cbc22bdb/multidict-6.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b293804f4b53be6297bc2afdeaf15aff76c1b6be69f3a3da785143eebdfb656", size = 244220 }, + { url = "https://files.pythonhosted.org/packages/1f/9c/d0c515f234f2958771c7463f6a03383d36e4074f1eb00459ec3c7190e8dd/multidict-6.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5eb2a8415a891564054447a327a7ef7ec90e69e0e63d85d1ffb03f82e102c740", size = 243508 }, + { url = "https://files.pythonhosted.org/packages/64/41/9a1a3308b4b99302a4502758baba6bb79c853332f26ef5a90968737d4563/multidict-6.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db61c6ae9ee7268dc69a078ea22deaaf861350ad2e4c194c70798b8ec9789131", size = 238985 }, + { url = "https://files.pythonhosted.org/packages/40/68/fba5926f53ff3e7b19799399bd82e27cb3df5d569839d07b6b42827194f1/multidict-6.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88548dec2f6f69e547252fe3113bf1b3b08c0879f5b44633927be07ed18c5cc0", size = 238675 }, + { url = "https://files.pythonhosted.org/packages/f2/c6/3a5160331b9842905a3e8ae81527068318c9f6ebddfe7ed07853b97ba216/multidict-6.3.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:864684b36e0488715aac531785abe7514e872bfe83ecc768828e9ddaadeed320", size = 225747 }, + { url = "https://files.pythonhosted.org/packages/ed/0d/cc98fde65ee79beb0632c8d2f7a9e639e5175885d7c7ac2400f56bc78f73/multidict-6.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:71d92e846541669ae1d11e00906c408c1dc9890196e13f11d4d62bd235ac9ddb", size = 240611 }, + { url = "https://files.pythonhosted.org/packages/f6/73/580793855a587663b2e26aa9fa2fba3d16dbce26aff4cb92d48ae4814ff0/multidict-6.3.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c5223ab45639ce93c7233d518f6c3199422b49dbd0ebfb1d7917b5da2636712e", size = 227815 }, + { url = "https://files.pythonhosted.org/packages/90/a6/ba293045efd338e4131726d7226c9d0870568486a6d025ec20dbf79f3972/multidict-6.3.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:56225de73d69f5ee6b783648eb1936e1bbe874a529cb1e15d64038904c54efb2", size = 239895 }, + { url = "https://files.pythonhosted.org/packages/4b/4a/ecda417af238696daafe921fbbdc96fa7e829656206442a785174377c61d/multidict-6.3.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:66c108d8e02da2febb0aa7d7002e14c4a0571460993c9edf8249393cdae7eeef", size = 233297 }, + { url = "https://files.pythonhosted.org/packages/73/90/8cea643f4e9b7f9c73b72032aa38f765e96db07636ea4b00f0420d9f6a5f/multidict-6.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b95d96a0640decaa24cd9cf386fd4d8a96c973aafa42dd9c65609f9f0d66cc34", size = 232030 }, + { url = "https://files.pythonhosted.org/packages/1b/37/8d42820299fbfbc774ed8247a75b16dfe2f09c4e0d1ae62ac751b6c25397/multidict-6.3.0-cp313-cp313t-win32.whl", hash = "sha256:6b25953a1d6a97746becbd663b49e3b436a5001c995a62662d65835a2ba996a7", size = 41166 }, + { url = "https://files.pythonhosted.org/packages/74/63/44bb663fdd4da768d55fb0406daa50e2ea1904da014a0972068e6a7e44d1/multidict-6.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d9c2b1ca98e5454b78cd434f29fc33eb8f8a2f343efc5f975225d92070b9f7f6", size = 44929 }, + { url = "https://files.pythonhosted.org/packages/65/66/730bb6dbfbf87df8a341707ebd468044ea6c530605d41b3f31b494f03d6a/multidict-6.3.0-py3-none-any.whl", hash = "sha256:9ca652d9c6f68535537d75502b549ed0ca07fa6d3908f84f29f92148ec7310f2", size = 10266 }, +] + +[[package]] +name = "propcache" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/07/c8/fdc6686a986feae3541ea23dcaa661bd93972d3940460646c6bb96e21c40/propcache-0.3.1.tar.gz", hash = "sha256:40d980c33765359098837527e18eddefc9a24cea5b45e078a7f3bb5b032c6ecf", size = 43651 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/60/f645cc8b570f99be3cf46714170c2de4b4c9d6b827b912811eff1eb8a412/propcache-0.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f1528ec4374617a7a753f90f20e2f551121bb558fcb35926f99e3c42367164b8", size = 77865 }, + { url = "https://files.pythonhosted.org/packages/6f/d4/c1adbf3901537582e65cf90fd9c26fde1298fde5a2c593f987112c0d0798/propcache-0.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc1915ec523b3b494933b5424980831b636fe483d7d543f7afb7b3bf00f0c10f", size = 45452 }, + { url = "https://files.pythonhosted.org/packages/d1/b5/fe752b2e63f49f727c6c1c224175d21b7d1727ce1d4873ef1c24c9216830/propcache-0.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a110205022d077da24e60b3df8bcee73971be9575dec5573dd17ae5d81751111", size = 44800 }, + { url = "https://files.pythonhosted.org/packages/62/37/fc357e345bc1971e21f76597028b059c3d795c5ca7690d7a8d9a03c9708a/propcache-0.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d249609e547c04d190e820d0d4c8ca03ed4582bcf8e4e160a6969ddfb57b62e5", size = 225804 }, + { url = "https://files.pythonhosted.org/packages/0d/f1/16e12c33e3dbe7f8b737809bad05719cff1dccb8df4dafbcff5575002c0e/propcache-0.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ced33d827625d0a589e831126ccb4f5c29dfdf6766cac441d23995a65825dcb", size = 230650 }, + { url = "https://files.pythonhosted.org/packages/3e/a2/018b9f2ed876bf5091e60153f727e8f9073d97573f790ff7cdf6bc1d1fb8/propcache-0.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4114c4ada8f3181af20808bedb250da6bae56660e4b8dfd9cd95d4549c0962f7", size = 234235 }, + { url = "https://files.pythonhosted.org/packages/45/5f/3faee66fc930dfb5da509e34c6ac7128870631c0e3582987fad161fcb4b1/propcache-0.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:975af16f406ce48f1333ec5e912fe11064605d5c5b3f6746969077cc3adeb120", size = 228249 }, + { url = "https://files.pythonhosted.org/packages/62/1e/a0d5ebda5da7ff34d2f5259a3e171a94be83c41eb1e7cd21a2105a84a02e/propcache-0.3.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a34aa3a1abc50740be6ac0ab9d594e274f59960d3ad253cd318af76b996dd654", size = 214964 }, + { url = "https://files.pythonhosted.org/packages/db/a0/d72da3f61ceab126e9be1f3bc7844b4e98c6e61c985097474668e7e52152/propcache-0.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9cec3239c85ed15bfaded997773fdad9fb5662b0a7cbc854a43f291eb183179e", size = 222501 }, + { url = "https://files.pythonhosted.org/packages/18/6d/a008e07ad7b905011253adbbd97e5b5375c33f0b961355ca0a30377504ac/propcache-0.3.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:05543250deac8e61084234d5fc54f8ebd254e8f2b39a16b1dce48904f45b744b", size = 217917 }, + { url = "https://files.pythonhosted.org/packages/98/37/02c9343ffe59e590e0e56dc5c97d0da2b8b19fa747ebacf158310f97a79a/propcache-0.3.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5cb5918253912e088edbf023788de539219718d3b10aef334476b62d2b53de53", size = 217089 }, + { url = "https://files.pythonhosted.org/packages/53/1b/d3406629a2c8a5666d4674c50f757a77be119b113eedd47b0375afdf1b42/propcache-0.3.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f3bbecd2f34d0e6d3c543fdb3b15d6b60dd69970c2b4c822379e5ec8f6f621d5", size = 228102 }, + { url = "https://files.pythonhosted.org/packages/cd/a7/3664756cf50ce739e5f3abd48febc0be1a713b1f389a502ca819791a6b69/propcache-0.3.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aca63103895c7d960a5b9b044a83f544b233c95e0dcff114389d64d762017af7", size = 230122 }, + { url = "https://files.pythonhosted.org/packages/35/36/0bbabaacdcc26dac4f8139625e930f4311864251276033a52fd52ff2a274/propcache-0.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a0a9898fdb99bf11786265468571e628ba60af80dc3f6eb89a3545540c6b0ef", size = 226818 }, + { url = "https://files.pythonhosted.org/packages/cc/27/4e0ef21084b53bd35d4dae1634b6d0bad35e9c58ed4f032511acca9d4d26/propcache-0.3.1-cp313-cp313-win32.whl", hash = "sha256:3a02a28095b5e63128bcae98eb59025924f121f048a62393db682f049bf4ac24", size = 40112 }, + { url = "https://files.pythonhosted.org/packages/a6/2c/a54614d61895ba6dd7ac8f107e2b2a0347259ab29cbf2ecc7b94fa38c4dc/propcache-0.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:813fbb8b6aea2fc9659815e585e548fe706d6f663fa73dff59a1677d4595a037", size = 44034 }, + { url = "https://files.pythonhosted.org/packages/5a/a8/0a4fd2f664fc6acc66438370905124ce62e84e2e860f2557015ee4a61c7e/propcache-0.3.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a444192f20f5ce8a5e52761a031b90f5ea6288b1eef42ad4c7e64fef33540b8f", size = 82613 }, + { url = "https://files.pythonhosted.org/packages/4d/e5/5ef30eb2cd81576256d7b6caaa0ce33cd1d2c2c92c8903cccb1af1a4ff2f/propcache-0.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fbe94666e62ebe36cd652f5fc012abfbc2342de99b523f8267a678e4dfdee3c", size = 47763 }, + { url = "https://files.pythonhosted.org/packages/87/9a/87091ceb048efeba4d28e903c0b15bcc84b7c0bf27dc0261e62335d9b7b8/propcache-0.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f011f104db880f4e2166bcdcf7f58250f7a465bc6b068dc84c824a3d4a5c94dc", size = 47175 }, + { url = "https://files.pythonhosted.org/packages/3e/2f/854e653c96ad1161f96194c6678a41bbb38c7947d17768e8811a77635a08/propcache-0.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e584b6d388aeb0001d6d5c2bd86b26304adde6d9bb9bfa9c4889805021b96de", size = 292265 }, + { url = "https://files.pythonhosted.org/packages/40/8d/090955e13ed06bc3496ba4a9fb26c62e209ac41973cb0d6222de20c6868f/propcache-0.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a17583515a04358b034e241f952f1715243482fc2c2945fd99a1b03a0bd77d6", size = 294412 }, + { url = "https://files.pythonhosted.org/packages/39/e6/d51601342e53cc7582449e6a3c14a0479fab2f0750c1f4d22302e34219c6/propcache-0.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5aed8d8308215089c0734a2af4f2e95eeb360660184ad3912686c181e500b2e7", size = 294290 }, + { url = "https://files.pythonhosted.org/packages/3b/4d/be5f1a90abc1881884aa5878989a1acdafd379a91d9c7e5e12cef37ec0d7/propcache-0.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d8e309ff9a0503ef70dc9a0ebd3e69cf7b3894c9ae2ae81fc10943c37762458", size = 282926 }, + { url = "https://files.pythonhosted.org/packages/57/2b/8f61b998c7ea93a2b7eca79e53f3e903db1787fca9373af9e2cf8dc22f9d/propcache-0.3.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b655032b202028a582d27aeedc2e813299f82cb232f969f87a4fde491a233f11", size = 267808 }, + { url = "https://files.pythonhosted.org/packages/11/1c/311326c3dfce59c58a6098388ba984b0e5fb0381ef2279ec458ef99bd547/propcache-0.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f64d91b751df77931336b5ff7bafbe8845c5770b06630e27acd5dbb71e1931c", size = 290916 }, + { url = "https://files.pythonhosted.org/packages/4b/74/91939924b0385e54dc48eb2e4edd1e4903ffd053cf1916ebc5347ac227f7/propcache-0.3.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:19a06db789a4bd896ee91ebc50d059e23b3639c25d58eb35be3ca1cbe967c3bf", size = 262661 }, + { url = "https://files.pythonhosted.org/packages/c2/d7/e6079af45136ad325c5337f5dd9ef97ab5dc349e0ff362fe5c5db95e2454/propcache-0.3.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:bef100c88d8692864651b5f98e871fb090bd65c8a41a1cb0ff2322db39c96c27", size = 264384 }, + { url = "https://files.pythonhosted.org/packages/b7/d5/ba91702207ac61ae6f1c2da81c5d0d6bf6ce89e08a2b4d44e411c0bbe867/propcache-0.3.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:87380fb1f3089d2a0b8b00f006ed12bd41bd858fabfa7330c954c70f50ed8757", size = 291420 }, + { url = "https://files.pythonhosted.org/packages/58/70/2117780ed7edcd7ba6b8134cb7802aada90b894a9810ec56b7bb6018bee7/propcache-0.3.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e474fc718e73ba5ec5180358aa07f6aded0ff5f2abe700e3115c37d75c947e18", size = 290880 }, + { url = "https://files.pythonhosted.org/packages/4a/1f/ecd9ce27710021ae623631c0146719280a929d895a095f6d85efb6a0be2e/propcache-0.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:17d1c688a443355234f3c031349da69444be052613483f3e4158eef751abcd8a", size = 287407 }, + { url = "https://files.pythonhosted.org/packages/3e/66/2e90547d6b60180fb29e23dc87bd8c116517d4255240ec6d3f7dc23d1926/propcache-0.3.1-cp313-cp313t-win32.whl", hash = "sha256:359e81a949a7619802eb601d66d37072b79b79c2505e6d3fd8b945538411400d", size = 42573 }, + { url = "https://files.pythonhosted.org/packages/cb/8f/50ad8599399d1861b4d2b6b45271f0ef6af1b09b0a2386a46dbaf19c9535/propcache-0.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e7fb9a84c9abbf2b2683fa3e7b0d7da4d8ecf139a1c635732a8bda29c5214b0e", size = 46757 }, + { url = "https://files.pythonhosted.org/packages/b8/d3/c3cb8f1d6ae3b37f83e1de806713a9b3642c5895f0215a62e1a4bd6e5e34/propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40", size = 12376 }, +] + +[[package]] +name = "protobuf" +version = "5.29.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/17/7d/b9dca7365f0e2c4fa7c193ff795427cfa6290147e5185ab11ece280a18e7/protobuf-5.29.4.tar.gz", hash = "sha256:4f1dfcd7997b31ef8f53ec82781ff434a28bf71d9102ddde14d076adcfc78c99", size = 424902 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/b2/043a1a1a20edd134563699b0e91862726a0dc9146c090743b6c44d798e75/protobuf-5.29.4-cp310-abi3-win32.whl", hash = "sha256:13eb236f8eb9ec34e63fc8b1d6efd2777d062fa6aaa68268fb67cf77f6839ad7", size = 422709 }, + { url = "https://files.pythonhosted.org/packages/79/fc/2474b59570daa818de6124c0a15741ee3e5d6302e9d6ce0bdfd12e98119f/protobuf-5.29.4-cp310-abi3-win_amd64.whl", hash = "sha256:bcefcdf3976233f8a502d265eb65ea740c989bacc6c30a58290ed0e519eb4b8d", size = 434506 }, + { url = "https://files.pythonhosted.org/packages/46/de/7c126bbb06aa0f8a7b38aaf8bd746c514d70e6a2a3f6dd460b3b7aad7aae/protobuf-5.29.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:307ecba1d852ec237e9ba668e087326a67564ef83e45a0189a772ede9e854dd0", size = 417826 }, + { url = "https://files.pythonhosted.org/packages/a2/b5/bade14ae31ba871a139aa45e7a8183d869efe87c34a4850c87b936963261/protobuf-5.29.4-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:aec4962f9ea93c431d5714ed1be1c93f13e1a8618e70035ba2b0564d9e633f2e", size = 319574 }, + { url = "https://files.pythonhosted.org/packages/46/88/b01ed2291aae68b708f7d334288ad5fb3e7aa769a9c309c91a0d55cb91b0/protobuf-5.29.4-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:d7d3f7d1d5a66ed4942d4fefb12ac4b14a29028b209d4bfb25c68ae172059922", size = 319672 }, + { url = "https://files.pythonhosted.org/packages/12/fb/a586e0c973c95502e054ac5f81f88394f24ccc7982dac19c515acd9e2c93/protobuf-5.29.4-py3-none-any.whl", hash = "sha256:3fde11b505e1597f71b875ef2fc52062b6a9740e5f7c8997ce878b6009145862", size = 172551 }, +] + +[[package]] +name = "pydantic" +version = "2.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/93/a3/698b87a4d4d303d7c5f62ea5fbf7a79cab236ccfbd0a17847b7f77f8163e/pydantic-2.11.1.tar.gz", hash = "sha256:442557d2910e75c991c39f4b4ab18963d57b9b55122c8b2a9cd176d8c29ce968", size = 782817 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/12/f9221a949f2419e2e23847303c002476c26fbcfd62dc7f3d25d0bec5ca99/pydantic-2.11.1-py3-none-any.whl", hash = "sha256:5b6c415eee9f8123a14d859be0c84363fec6b1feb6b688d6435801230b56e0b8", size = 442648 }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/05/91ce14dfd5a3a99555fce436318cc0fd1f08c4daa32b3248ad63669ea8b4/pydantic_core-2.33.0.tar.gz", hash = "sha256:40eb8af662ba409c3cbf4a8150ad32ae73514cd7cb1f1a2113af39763dd616b3", size = 434080 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/20/de2ad03ce8f5b3accf2196ea9b44f31b0cd16ac6e8cfc6b21976ed45ec35/pydantic_core-2.33.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f00e8b59e1fc8f09d05594aa7d2b726f1b277ca6155fc84c0396db1b373c4555", size = 2032214 }, + { url = "https://files.pythonhosted.org/packages/f9/af/6817dfda9aac4958d8b516cbb94af507eb171c997ea66453d4d162ae8948/pydantic_core-2.33.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a73be93ecef45786d7d95b0c5e9b294faf35629d03d5b145b09b81258c7cd6d", size = 1852338 }, + { url = "https://files.pythonhosted.org/packages/44/f3/49193a312d9c49314f2b953fb55740b7c530710977cabe7183b8ef111b7f/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff48a55be9da6930254565ff5238d71d5e9cd8c5487a191cb85df3bdb8c77365", size = 1896913 }, + { url = "https://files.pythonhosted.org/packages/06/e0/c746677825b2e29a2fa02122a8991c83cdd5b4c5f638f0664d4e35edd4b2/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a4ea04195638dcd8c53dadb545d70badba51735b1594810e9768c2c0b4a5da", size = 1986046 }, + { url = "https://files.pythonhosted.org/packages/11/ec/44914e7ff78cef16afb5e5273d480c136725acd73d894affdbe2a1bbaad5/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41d698dcbe12b60661f0632b543dbb119e6ba088103b364ff65e951610cb7ce0", size = 2128097 }, + { url = "https://files.pythonhosted.org/packages/fe/f5/c6247d424d01f605ed2e3802f338691cae17137cee6484dce9f1ac0b872b/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae62032ef513fe6281ef0009e30838a01057b832dc265da32c10469622613885", size = 2681062 }, + { url = "https://files.pythonhosted.org/packages/f0/85/114a2113b126fdd7cf9a9443b1b1fe1b572e5bd259d50ba9d5d3e1927fa9/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f225f3a3995dbbc26affc191d0443c6c4aa71b83358fd4c2b7d63e2f6f0336f9", size = 2007487 }, + { url = "https://files.pythonhosted.org/packages/e6/40/3c05ed28d225c7a9acd2b34c5c8010c279683a870219b97e9f164a5a8af0/pydantic_core-2.33.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5bdd36b362f419c78d09630cbaebc64913f66f62bda6d42d5fbb08da8cc4f181", size = 2121382 }, + { url = "https://files.pythonhosted.org/packages/8a/22/e70c086f41eebd323e6baa92cc906c3f38ddce7486007eb2bdb3b11c8f64/pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2a0147c0bef783fd9abc9f016d66edb6cac466dc54a17ec5f5ada08ff65caf5d", size = 2072473 }, + { url = "https://files.pythonhosted.org/packages/3e/84/d1614dedd8fe5114f6a0e348bcd1535f97d76c038d6102f271433cd1361d/pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:c860773a0f205926172c6644c394e02c25421dc9a456deff16f64c0e299487d3", size = 2249468 }, + { url = "https://files.pythonhosted.org/packages/b0/c0/787061eef44135e00fddb4b56b387a06c303bfd3884a6df9bea5cb730230/pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:138d31e3f90087f42aa6286fb640f3c7a8eb7bdae829418265e7e7474bd2574b", size = 2254716 }, + { url = "https://files.pythonhosted.org/packages/ae/e2/27262eb04963201e89f9c280f1e10c493a7a37bc877e023f31aa72d2f911/pydantic_core-2.33.0-cp313-cp313-win32.whl", hash = "sha256:d20cbb9d3e95114325780f3cfe990f3ecae24de7a2d75f978783878cce2ad585", size = 1916450 }, + { url = "https://files.pythonhosted.org/packages/13/8d/25ff96f1e89b19e0b70b3cd607c9ea7ca27e1dcb810a9cd4255ed6abf869/pydantic_core-2.33.0-cp313-cp313-win_amd64.whl", hash = "sha256:ca1103d70306489e3d006b0f79db8ca5dd3c977f6f13b2c59ff745249431a606", size = 1956092 }, + { url = "https://files.pythonhosted.org/packages/1b/64/66a2efeff657b04323ffcd7b898cb0354d36dae3a561049e092134a83e9c/pydantic_core-2.33.0-cp313-cp313-win_arm64.whl", hash = "sha256:6291797cad239285275558e0a27872da735b05c75d5237bbade8736f80e4c225", size = 1908367 }, + { url = "https://files.pythonhosted.org/packages/52/54/295e38769133363d7ec4a5863a4d579f331728c71a6644ff1024ee529315/pydantic_core-2.33.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7b79af799630af263eca9ec87db519426d8c9b3be35016eddad1832bac812d87", size = 1813331 }, + { url = "https://files.pythonhosted.org/packages/4c/9c/0c8ea02db8d682aa1ef48938abae833c1d69bdfa6e5ec13b21734b01ae70/pydantic_core-2.33.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eabf946a4739b5237f4f56d77fa6668263bc466d06a8036c055587c130a46f7b", size = 1986653 }, + { url = "https://files.pythonhosted.org/packages/8e/4f/3fb47d6cbc08c7e00f92300e64ba655428c05c56b8ab6723bd290bae6458/pydantic_core-2.33.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8a1d581e8cdbb857b0e0e81df98603376c1a5c34dc5e54039dcc00f043df81e7", size = 1931234 }, +] + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, +] + +[[package]] +name = "rich" +version = "14.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229 }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, +] + +[[package]] +name = "sigtools" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5f/db/669ca14166814da187b3087b908ca924cf83f5b504fe23b3859a3ef67d4f/sigtools-4.0.1.tar.gz", hash = "sha256:4b8e135a9cd4d2ea00da670c093372d74e672ba3abb87f4c98d8e73dea54445c", size = 71910 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/91/853dbf6ec096197dba9cd5fd0c836c5fc19142038b7db60ebe6332b1bab1/sigtools-4.0.1-py2.py3-none-any.whl", hash = "sha256:d216b4cf920bbab0fce636ddc429ed8463a5b533d9e1492acb45a2a1bc36ac6c", size = 76419 }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, +] + +[[package]] +name = "starlette" +version = "0.46.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/1b/52b27f2e13ceedc79a908e29eac426a63465a1a01248e5f24aa36a62aeb3/starlette-0.46.1.tar.gz", hash = "sha256:3c88d58ee4bd1bb807c0d1acb381838afc7752f9ddaec81bbe4383611d833230", size = 2580102 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/4b/528ccf7a982216885a1ff4908e886b8fb5f19862d1962f56a3fce2435a70/starlette-0.46.1-py3-none-any.whl", hash = "sha256:77c74ed9d2720138b25875133f3a2dae6d854af2ec37dceb56aef370c1d8a227", size = 71995 }, +] + +[[package]] +name = "synchronicity" +version = "0.9.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sigtools" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/52/f34a9ab6d514e0808d0f572affb360411d596b3439107318c00889277dd6/synchronicity-0.9.11.tar.gz", hash = "sha256:cb5dbbcb43d637e516ae50db05a776da51a705d1e1a9c0e301f6049afc3c2cae", size = 50323 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/d5/7675cd9b8e18f05b9ea261acad5d197fcb8027d2a65b1a750427ec084593/synchronicity-0.9.11-py3-none-any.whl", hash = "sha256:231129654d2f56b1aa148e85ebd8545231be135771f6d2196d414175b1594ef6", size = 36827 }, +] + +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588 }, +] + +[[package]] +name = "typer" +version = "0.15.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/6f/3991f0f1c7fcb2df31aef28e0594d8d54b05393a0e4e34c65e475c2a5d41/typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5", size = 100711 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/fc/5b29fea8cee020515ca82cc68e3b8e1e34bb19a3535ad854cac9257b414c/typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc", size = 45061 }, +] + +[[package]] +name = "types-certifi" +version = "2021.10.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/68/943c3aeaf14624712a0357c4a67814dba5cea36d194f5c764dad7959a00c/types-certifi-2021.10.8.3.tar.gz", hash = "sha256:72cf7798d165bc0b76e1c10dd1ea3097c7063c42c21d664523b928e88b554a4f", size = 2095 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/63/2463d89481e811f007b0e1cd0a91e52e141b47f9de724d20db7b861dcfec/types_certifi-2021.10.8.3-py3-none-any.whl", hash = "sha256:b2d1e325e69f71f7c78e5943d410e650b4707bb0ef32e4ddf3da37f54176e88a", size = 2136 }, +] + +[[package]] +name = "types-toml" +version = "0.10.8.20240310" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/86/47/3e4c75042792bff8e90d7991aa5c51812cc668828cc6cce711e97f63a607/types-toml-0.10.8.20240310.tar.gz", hash = "sha256:3d41501302972436a6b8b239c850b26689657e25281b48ff0ec06345b8830331", size = 4392 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/a2/d32ab58c0b216912638b140ab2170ee4b8644067c293b170e19fba340ccc/types_toml-0.10.8.20240310-py3-none-any.whl", hash = "sha256:627b47775d25fa29977d9c70dc0cbab3f314f32c8d8d0c012f2ef5de7aaec05d", size = 4777 }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/3e/b00a62db91a83fff600de219b6ea9908e6918664899a2d85db222f4fbf19/typing_extensions-4.13.0.tar.gz", hash = "sha256:0a4ac55a5820789d87e297727d229866c9650f6521b64206413c4fbada24d95b", size = 106520 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/86/39b65d676ec5732de17b7e3c476e45bb80ec64eb50737a8dce1a4178aba1/typing_extensions-4.13.0-py3-none-any.whl", hash = "sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5", size = 45683 }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 }, +] + +[[package]] +name = "watchfiles" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/26/c705fc77d0a9ecdb9b66f1e2976d95b81df3cae518967431e7dbf9b5e219/watchfiles-1.0.4.tar.gz", hash = "sha256:6ba473efd11062d73e4f00c2b730255f9c1bdd73cd5f9fe5b5da8dbd4a717205", size = 94625 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/98/f03efabec64b5b1fa58c0daab25c68ef815b0f320e54adcacd0d6847c339/watchfiles-1.0.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:8012bd820c380c3d3db8435e8cf7592260257b378b649154a7948a663b5f84e9", size = 390954 }, + { url = "https://files.pythonhosted.org/packages/16/09/4dd49ba0a32a45813debe5fb3897955541351ee8142f586303b271a02b40/watchfiles-1.0.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa216f87594f951c17511efe5912808dfcc4befa464ab17c98d387830ce07b60", size = 381133 }, + { url = "https://files.pythonhosted.org/packages/76/59/5aa6fc93553cd8d8ee75c6247763d77c02631aed21551a97d94998bf1dae/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c9953cf85529c05b24705639ffa390f78c26449e15ec34d5339e8108c7c407", size = 449516 }, + { url = "https://files.pythonhosted.org/packages/4c/aa/df4b6fe14b6317290b91335b23c96b488d365d65549587434817e06895ea/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7cf684aa9bba4cd95ecb62c822a56de54e3ae0598c1a7f2065d51e24637a3c5d", size = 454820 }, + { url = "https://files.pythonhosted.org/packages/5e/71/185f8672f1094ce48af33252c73e39b48be93b761273872d9312087245f6/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f44a39aee3cbb9b825285ff979ab887a25c5d336e5ec3574f1506a4671556a8d", size = 481550 }, + { url = "https://files.pythonhosted.org/packages/85/d7/50ebba2c426ef1a5cb17f02158222911a2e005d401caf5d911bfca58f4c4/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38320582736922be8c865d46520c043bff350956dfc9fbaee3b2df4e1740a4b", size = 518647 }, + { url = "https://files.pythonhosted.org/packages/f0/7a/4c009342e393c545d68987e8010b937f72f47937731225b2b29b7231428f/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39f4914548b818540ef21fd22447a63e7be6e24b43a70f7642d21f1e73371590", size = 497547 }, + { url = "https://files.pythonhosted.org/packages/0f/7c/1cf50b35412d5c72d63b2bf9a4fffee2e1549a245924960dd087eb6a6de4/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f12969a3765909cf5dc1e50b2436eb2c0e676a3c75773ab8cc3aa6175c16e902", size = 452179 }, + { url = "https://files.pythonhosted.org/packages/d6/a9/3db1410e1c1413735a9a472380e4f431ad9a9e81711cda2aaf02b7f62693/watchfiles-1.0.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:0986902677a1a5e6212d0c49b319aad9cc48da4bd967f86a11bde96ad9676ca1", size = 614125 }, + { url = "https://files.pythonhosted.org/packages/f2/e1/0025d365cf6248c4d1ee4c3d2e3d373bdd3f6aff78ba4298f97b4fad2740/watchfiles-1.0.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:308ac265c56f936636e3b0e3f59e059a40003c655228c131e1ad439957592303", size = 611911 }, + { url = "https://files.pythonhosted.org/packages/55/55/035838277d8c98fc8c917ac9beeb0cd6c59d675dc2421df5f9fcf44a0070/watchfiles-1.0.4-cp313-cp313-win32.whl", hash = "sha256:aee397456a29b492c20fda2d8961e1ffb266223625346ace14e4b6d861ba9c80", size = 271152 }, + { url = "https://files.pythonhosted.org/packages/f0/e5/96b8e55271685ddbadc50ce8bc53aa2dff278fb7ac4c2e473df890def2dc/watchfiles-1.0.4-cp313-cp313-win_amd64.whl", hash = "sha256:d6097538b0ae5c1b88c3b55afa245a66793a8fec7ada6755322e465fb1a0e8cc", size = 285216 }, +] + +[[package]] +name = "yarl" +version = "1.18.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b7/9d/4b94a8e6d2b51b599516a5cb88e5bc99b4d8d4583e468057eaa29d5f0918/yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1", size = 181062 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/c7/c790513d5328a8390be8f47be5d52e141f78b66c6c48f48d241ca6bd5265/yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb", size = 140789 }, + { url = "https://files.pythonhosted.org/packages/30/aa/a2f84e93554a578463e2edaaf2300faa61c8701f0898725842c704ba5444/yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa", size = 94144 }, + { url = "https://files.pythonhosted.org/packages/c6/fc/d68d8f83714b221a85ce7866832cba36d7c04a68fa6a960b908c2c84f325/yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782", size = 91974 }, + { url = "https://files.pythonhosted.org/packages/56/4e/d2563d8323a7e9a414b5b25341b3942af5902a2263d36d20fb17c40411e2/yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0", size = 333587 }, + { url = "https://files.pythonhosted.org/packages/25/c9/cfec0bc0cac8d054be223e9f2c7909d3e8442a856af9dbce7e3442a8ec8d/yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482", size = 344386 }, + { url = "https://files.pythonhosted.org/packages/ab/5d/4c532190113b25f1364d25f4c319322e86232d69175b91f27e3ebc2caf9a/yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186", size = 345421 }, + { url = "https://files.pythonhosted.org/packages/23/d1/6cdd1632da013aa6ba18cee4d750d953104a5e7aac44e249d9410a972bf5/yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58", size = 339384 }, + { url = "https://files.pythonhosted.org/packages/9a/c4/6b3c39bec352e441bd30f432cda6ba51681ab19bb8abe023f0d19777aad1/yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53", size = 326689 }, + { url = "https://files.pythonhosted.org/packages/23/30/07fb088f2eefdc0aa4fc1af4e3ca4eb1a3aadd1ce7d866d74c0f124e6a85/yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2", size = 345453 }, + { url = "https://files.pythonhosted.org/packages/63/09/d54befb48f9cd8eec43797f624ec37783a0266855f4930a91e3d5c7717f8/yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8", size = 341872 }, + { url = "https://files.pythonhosted.org/packages/91/26/fd0ef9bf29dd906a84b59f0cd1281e65b0c3e08c6aa94b57f7d11f593518/yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1", size = 347497 }, + { url = "https://files.pythonhosted.org/packages/d9/b5/14ac7a256d0511b2ac168d50d4b7d744aea1c1aa20c79f620d1059aab8b2/yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a", size = 359981 }, + { url = "https://files.pythonhosted.org/packages/ca/b3/d493221ad5cbd18bc07e642894030437e405e1413c4236dd5db6e46bcec9/yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10", size = 366229 }, + { url = "https://files.pythonhosted.org/packages/04/56/6a3e2a5d9152c56c346df9b8fb8edd2c8888b1e03f96324d457e5cf06d34/yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8", size = 360383 }, + { url = "https://files.pythonhosted.org/packages/fd/b7/4b3c7c7913a278d445cc6284e59b2e62fa25e72758f888b7a7a39eb8423f/yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d", size = 310152 }, + { url = "https://files.pythonhosted.org/packages/f5/d5/688db678e987c3e0fb17867970700b92603cadf36c56e5fb08f23e822a0c/yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c", size = 315723 }, + { url = "https://files.pythonhosted.org/packages/f5/4b/a06e0ec3d155924f77835ed2d167ebd3b211a7b0853da1cf8d8414d784ef/yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b", size = 45109 }, +] diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index dafff383..379eca65 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -25,11 +25,13 @@ jobs: - os: ubuntu-20.04 target: x86_64-unknown-linux-gnu cuda: "11.2.2" + gpu: "T4" linux-local-args: ["--toolkit"] - os: ubuntu-24.04 target: x86_64-unknown-linux-gnu cuda: "12.8.1" linux-local-args: ["--toolkit"] + gpu: "T4" - os: windows-latest target: x86_64-pc-windows-msvc cuda: "11.2.2" @@ -104,12 +106,8 @@ jobs: - name: Build run: cargo build --workspace --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "add" --exclude "ex*" - # Don't currently test because many tests rely on the system having a CUDA GPU - # - name: Test - # run: cargo test --workspace - - name: Clippy - if: contains(matrix.os, 'ubuntu') + if: runner.os == 'Linux' env: RUSTFLAGS: -Dwarnings run: cargo clippy --workspace --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "add" --exclude "ex*" @@ -118,3 +116,36 @@ jobs: env: RUSTDOCFLAGS: -Dwarnings run: cargo doc --workspace --all-features --document-private-items --no-deps --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "add" --exclude "ex*" + + # We now run tests. We run them last because they run on donated machines with + # GPUs. + + - name: Install python (for CI test runner script) + if: runner.os == 'Linux' + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install CI test runner deps + if: runner.os == 'Linux' + working-directory: .ci/modal.com + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + uv sync + + - name: Check CI test runner formatting + if: runner.os == 'Linux' + working-directory: .ci/modal.com + run: uvx black --check . + + - name: Run tests via CI test runner + if: runner.os == 'Linux' + env: + # Pass runtime config TO THE python runner script + RUNNER_OS_NAME: ${{ matrix.os }} + RUNNER_CUDA_VERSION: ${{ matrix.cuda }} + MODAL_GPU_TYPE: ${{ matrix.gpu }} + MODAL_TOKEN_ID: ${{ secrets.MODAL_TOKEN_ID }} + MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }} + # Run the tests, but point at a config that sets the runner to our test runner. + run: cargo --config .ci/config.toml test --workspace From 6872c218dc58acf21d8e9e5e3cf9216a2e0b698a Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Tue, 1 Apr 2025 18:10:53 -0400 Subject: [PATCH 02/19] Exclude optix crates --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 379eca65..1b9919ae 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -148,4 +148,4 @@ jobs: MODAL_TOKEN_ID: ${{ secrets.MODAL_TOKEN_ID }} MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }} # Run the tests, but point at a config that sets the runner to our test runner. - run: cargo --config .ci/config.toml test --workspace + run: cargo --config .ci/config.toml test --workspace --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "ex*" From 117a6cb912acb6ec96e63495fb50764f5e873ea2 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Tue, 1 Apr 2025 18:19:27 -0400 Subject: [PATCH 03/19] Make excludes match exactly --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 1b9919ae..37637791 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -148,4 +148,4 @@ jobs: MODAL_TOKEN_ID: ${{ secrets.MODAL_TOKEN_ID }} MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }} # Run the tests, but point at a config that sets the runner to our test runner. - run: cargo --config .ci/config.toml test --workspace --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "ex*" + run: cargo --config .ci/config.toml test --workspace --workspace --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "add" --exclude "ex*" From dc208adbd0dc1c1735a6fcb3d209d900f0054ba8 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Tue, 1 Apr 2025 18:25:45 -0400 Subject: [PATCH 04/19] Fix typo --- .ci/modal.com/modal_runner.py | 4 +--- .github/workflows/rust.yml | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.ci/modal.com/modal_runner.py b/.ci/modal.com/modal_runner.py index 72522002..5e970c5c 100644 --- a/.ci/modal.com/modal_runner.py +++ b/.ci/modal.com/modal_runner.py @@ -29,7 +29,6 @@ class Config: gpu_config: str -# --- Logging Helper --- def log_runner(message: str) -> None: """Logs a message to stderr with the Runner prefix.""" print(f"{RUNNER_LOG_PREFIX} {message}", file=sys.stderr) @@ -41,11 +40,11 @@ def log_remote(message: str) -> None: # --- Configuration Loading and Validation --- + # Mapping from GH Actions OS name to Nvidia registry tags OS_NAME_TO_NVIDIA_TAG: Dict[str, str] = { "ubuntu-20.04": "ubuntu20.04", "ubuntu-24.04": "ubuntu24.04", - # Add other supported Linux GHA OS identifiers here if needed } @@ -80,7 +79,6 @@ def load_and_validate_config(argv: List[str], env: Dict[str, str]) -> Config: f"Missing required environment variables: {', '.join(missing)}" ) - # --- Assertions for type checking --- assert modal_gpu_type is not None assert os_name is not None assert cuda_version is not None diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 37637791..95c3d117 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -148,4 +148,4 @@ jobs: MODAL_TOKEN_ID: ${{ secrets.MODAL_TOKEN_ID }} MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }} # Run the tests, but point at a config that sets the runner to our test runner. - run: cargo --config .ci/config.toml test --workspace --workspace --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "add" --exclude "ex*" + run: cargo --config .ci/config.toml test --workspace --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "add" --exclude "ex*" From e1abce2f4eb363411f1e02755d149c04ec2a1122 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Tue, 1 Apr 2025 18:41:17 -0400 Subject: [PATCH 05/19] Exclude `cdnn` --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 95c3d117..ce591814 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -148,4 +148,4 @@ jobs: MODAL_TOKEN_ID: ${{ secrets.MODAL_TOKEN_ID }} MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }} # Run the tests, but point at a config that sets the runner to our test runner. - run: cargo --config .ci/config.toml test --workspace --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "add" --exclude "ex*" + run: cargo --config .ci/config.toml test --workspace --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "add" --exclude "ex*" --exclude "cudnn" From fd1755e3bd51e1e242712d1c2b6a226e7b5c0b21 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Tue, 1 Apr 2025 18:54:31 -0400 Subject: [PATCH 06/19] Add `pwd` to the runner command --- .ci/config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/config.toml b/.ci/config.toml index 58194c71..4d90230e 100644 --- a/.ci/config.toml +++ b/.ci/config.toml @@ -4,4 +4,4 @@ # The command to execute instead of the compiled test binary. Cargo will append the # actual path to the compiled test executable and any arguments (like --list, --exact, # --nocapture) after this command. -runner = ["uv", "run", ".ci/modal.com/modal_test_runner.py"] +runner = ["pwd", "&&", "uv", "run", ".ci/modal.com/modal_test_runner.py"] From 32c92974b97cec028e8ba6636d18a957ecd819e0 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Tue, 1 Apr 2025 19:37:58 -0400 Subject: [PATCH 07/19] Make tool work --- .ci/cargo_glue.sh | 10 ++++++++++ .ci/config.toml | 5 ++++- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100755 .ci/cargo_glue.sh diff --git a/.ci/cargo_glue.sh b/.ci/cargo_glue.sh new file mode 100755 index 00000000..9cc6fbe0 --- /dev/null +++ b/.ci/cargo_glue.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +# This script is used by cargo to run the Modal test runner with the +# specified arguments. +# +# We need this script because the cwd that cargo runs the runner in changes +# depending on crate. + +cd "$GITHUB_WORKSPACE/.ci/modal.com" && uv run modal_test_runner.py "$@" diff --git a/.ci/config.toml b/.ci/config.toml index 4d90230e..037cc208 100644 --- a/.ci/config.toml +++ b/.ci/config.toml @@ -4,4 +4,7 @@ # The command to execute instead of the compiled test binary. Cargo will append the # actual path to the compiled test executable and any arguments (like --list, --exact, # --nocapture) after this command. -runner = ["pwd", "&&", "uv", "run", ".ci/modal.com/modal_test_runner.py"] + +# We use a tool without a slash in it so cargo will search $PATH. Before calling the +# `cargo test` we append the location of the glue to the path. +runner = ["cargo_glue.sh"] From adf72853c6f56859a40f7d4ba30deff94a5e102e Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Tue, 1 Apr 2025 19:55:42 -0400 Subject: [PATCH 08/19] Append a full path to the path --- .github/workflows/rust.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index ce591814..042239f1 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -138,7 +138,7 @@ jobs: working-directory: .ci/modal.com run: uvx black --check . - - name: Run tests via CI test runner + - name: Run tests if: runner.os == 'Linux' env: # Pass runtime config TO THE python runner script @@ -148,4 +148,6 @@ jobs: MODAL_TOKEN_ID: ${{ secrets.MODAL_TOKEN_ID }} MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }} # Run the tests, but point at a config that sets the runner to our test runner. - run: cargo --config .ci/config.toml test --workspace --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "add" --exclude "ex*" --exclude "cudnn" + run: | + export PATH="$PATH:$GITHUB_WORKSPACE/.ci" + cargo --config .ci/config.toml test --workspace --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "add" --exclude "ex*" --exclude "cudnn" From 7034b6548eadba02f11bccacc2611e8ded7a2173 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Tue, 1 Apr 2025 20:06:42 -0400 Subject: [PATCH 09/19] Use correct script name --- .ci/cargo_glue.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/cargo_glue.sh b/.ci/cargo_glue.sh index 9cc6fbe0..7fbafdd1 100755 --- a/.ci/cargo_glue.sh +++ b/.ci/cargo_glue.sh @@ -7,4 +7,4 @@ set -e # We need this script because the cwd that cargo runs the runner in changes # depending on crate. -cd "$GITHUB_WORKSPACE/.ci/modal.com" && uv run modal_test_runner.py "$@" +cd "$GITHUB_WORKSPACE/.ci/modal.com" && uv run modal_runner.py "$@" From ae87adcd74efd8ce98342acda6b19e78ddcfa963 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Tue, 1 Apr 2025 20:29:35 -0400 Subject: [PATCH 10/19] Remove comment and make capitalization consistent --- .ci/modal.com/modal_runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/modal.com/modal_runner.py b/.ci/modal.com/modal_runner.py index 5e970c5c..977e6052 100644 --- a/.ci/modal.com/modal_runner.py +++ b/.ci/modal.com/modal_runner.py @@ -125,7 +125,7 @@ def create_runtime_image( .run_commands( # These commands modify the filesystem *before* the file is added "mkdir -p /app", - "echo '[Remote] Container environment baseline ready.'", # Use log_remote style + "echo '[remote] Container environment baseline ready.'", ) # Add any other apt_install, pip_install, run_commands etc. HERE if needed later ) From c00f2ad1325a5ec0973c4a669535ac43e06c0c0b Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Tue, 1 Apr 2025 20:48:41 -0400 Subject: [PATCH 11/19] Add colors to logging --- .ci/modal.com/modal_runner.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/.ci/modal.com/modal_runner.py b/.ci/modal.com/modal_runner.py index 977e6052..9876290b 100644 --- a/.ci/modal.com/modal_runner.py +++ b/.ci/modal.com/modal_runner.py @@ -7,11 +7,23 @@ from typing import List, Dict, Any, Optional, Tuple from dataclasses import dataclass +TestResult = Dict[str, Any] + APP_PREFIX = "rust-cuda-test-runner" -RUNNER_LOG_PREFIX = "[runner]" -REMOTE_LOG_PREFIX = "[remote]" -TestResult = Dict[str, Any] +RED = "\033[31m" +BLUE = "\033[34m" + + +def _colorize_prefix(prefix: str, color_code: str) -> str: + """Adds color to a prefix if running in a TTY.""" + if sys.stderr.isatty(): + return f"{color_code}{prefix}\033[0m" + return prefix + + +RUNNER_LOG_PREFIX = _colorize_prefix("[runner]", RED) +REMOTE_LOG_PREFIX = _colorize_prefix("[remote]", BLUE) @dataclass(frozen=True) # Immutable config object From bff33b4991d64b533bbe448d44e7ae2fbc759ada Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Tue, 1 Apr 2025 21:05:26 -0400 Subject: [PATCH 12/19] Fix comment --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 042239f1..fd55f7af 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -141,7 +141,7 @@ jobs: - name: Run tests if: runner.os == 'Linux' env: - # Pass runtime config TO THE python runner script + # Pass runtime config to the python runner script RUNNER_OS_NAME: ${{ matrix.os }} RUNNER_CUDA_VERSION: ${{ matrix.cuda }} MODAL_GPU_TYPE: ${{ matrix.gpu }} From 3fa78b09d119945cfb2b43606e680fc2f2c2c990 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Tue, 1 Apr 2025 22:13:24 -0400 Subject: [PATCH 13/19] Make secrets run when a label is applied --- .github/workflows/rust.yml | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index fd55f7af..f7502611 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -65,7 +65,6 @@ jobs: shell: pwsh run: Get-ChildItem -Path $env:CUDA_PATH -Recurse | ForEach-Object { $_.FullName } - # random command that forces rustup to install stuff in rust-toolchain - name: Install rust-toolchain run: cargo version @@ -117,37 +116,54 @@ jobs: RUSTDOCFLAGS: -Dwarnings run: cargo doc --workspace --all-features --document-private-items --no-deps --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "add" --exclude "ex*" - # We now run tests. We run them last because they run on donated machines with - # GPUs. - - name: Install python (for CI test runner script) - if: runner.os == 'Linux' + if: > + runner.os == 'Linux' && + (github.event_name == 'push' || + (github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name == github.repository && + contains(github.event.pull_request.labels.*.name, 'DANGER-expose-secrets-and-run-tests'))) uses: actions/setup-python@v5 with: python-version: "3.10" - name: Install CI test runner deps - if: runner.os == 'Linux' + if: > + runner.os == 'Linux' && + (github.event_name == 'push' || + (github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name == github.repository && + contains(github.event.pull_request.labels.*.name, 'DANGER-expose-secrets-and-run-tests'))) working-directory: .ci/modal.com run: | curl -LsSf https://astral.sh/uv/install.sh | sh uv sync - name: Check CI test runner formatting - if: runner.os == 'Linux' + if: > + runner.os == 'Linux' && + (github.event_name == 'push' || + (github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name == github.repository && + contains(github.event.pull_request.labels.*.name, 'DANGER-expose-secrets-and-run-tests'))) working-directory: .ci/modal.com run: uvx black --check . - - name: Run tests - if: runner.os == 'Linux' + # Secrets are not available in forked PRs, so we have to manually add a label and + # re-run. + - name: Run integration tests + if: > + runner.os == 'Linux' && + (github.event_name == 'push' || + (github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name == github.repository && + contains(github.event.pull_request.labels.*.name, 'DANGER-expose-secrets-and-run-tests'))) env: - # Pass runtime config to the python runner script RUNNER_OS_NAME: ${{ matrix.os }} RUNNER_CUDA_VERSION: ${{ matrix.cuda }} MODAL_GPU_TYPE: ${{ matrix.gpu }} MODAL_TOKEN_ID: ${{ secrets.MODAL_TOKEN_ID }} MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }} - # Run the tests, but point at a config that sets the runner to our test runner. run: | export PATH="$PATH:$GITHUB_WORKSPACE/.ci" cargo --config .ci/config.toml test --workspace --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "add" --exclude "ex*" --exclude "cudnn" From 8e5ddfe5bfa1852eeda4e0ec3471cf37a51b03d8 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Tue, 1 Apr 2025 22:59:59 -0400 Subject: [PATCH 14/19] Split up workflow and actions to handle secrets on PRs --- .github/actions/common-steps/action.yml | 88 +++++++++++++ .github/workflows/rust.yml | 159 +++++++++--------------- 2 files changed, 146 insertions(+), 101 deletions(-) create mode 100644 .github/actions/common-steps/action.yml diff --git a/.github/actions/common-steps/action.yml b/.github/actions/common-steps/action.yml new file mode 100644 index 00000000..5da73711 --- /dev/null +++ b/.github/actions/common-steps/action.yml @@ -0,0 +1,88 @@ +name: "Common CI Steps" +description: "Runs steps between builds, checks, and integration tests." +inputs: + matrix_os: + description: "OS from matrix" + required: true + matrix_cuda: + description: "CUDA version from matrix" + required: true + matrix_target: + description: "Target triple from matrix" + required: true + matrix_gpu: + description: "GPU type from matrix" + required: true + matrix_linux_local_args: + description: "Linux local args from matrix (as JSON)" + required: true +runs: + using: "composite" + steps: + - name: Install CUDA + uses: Jimver/cuda-toolkit@v0.2.22 + id: cuda-toolkit + with: + cuda: ${{ inputs.matrix_cuda }} + linux-local-args: ${{ inputs.matrix_linux_local_args }} + use-local-cache: false + + - name: Verify CUDA installation + run: nvcc --version + + - name: List CUDA_PATH files (Linux) + if: runner.os == 'Linux' + run: find "$CUDA_PATH" -type f + + - name: List CUDA_PATH files (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: Get-ChildItem -Path $env:CUDA_PATH -Recurse | ForEach-Object { $_.FullName } + + - name: Install rust-toolchain + run: cargo version + + - name: Add rustup components + run: rustup component add rustfmt clippy + + - name: Install dependencies for LLVM 7 + if: inputs.matrix_os == 'ubuntu-24.04' + run: | + wget -O libffi7.deb http://security.ubuntu.com/ubuntu/pool/universe/libf/libffi7/libffi7_3.3-5ubuntu1_amd64.deb + sudo apt-get update + sudo apt-get install -y ./*.deb liblzma-dev libssl-dev libcurl4-openssl-dev + + - name: Install LLVM 7 + if: contains(inputs.matrix_os, 'ubuntu') + run: | + mkdir -p ~/llvm7 && cd ~/llvm7 + wget http://mirrors.kernel.org/ubuntu/pool/universe/l/llvm-toolchain-7/llvm-7_7.0.1-12_amd64.deb \ + http://mirrors.kernel.org/ubuntu/pool/universe/l/llvm-toolchain-7/llvm-7-dev_7.0.1-12_amd64.deb \ + http://mirrors.kernel.org/ubuntu/pool/universe/l/llvm-toolchain-7/libllvm7_7.0.1-12_amd64.deb \ + http://mirrors.kernel.org/ubuntu/pool/universe/l/llvm-toolchain-7/llvm-7-runtime_7.0.1-12_amd64.deb + sudo apt-get update + sudo apt-get install -y ./*.deb + sudo ln -s /usr/bin/llvm-config-7 /usr/local/bin/llvm-config + + - name: Load Rust Cache + uses: Swatinem/rust-cache@v2.7.7 + with: + key: ${{ inputs.matrix_os }}-${{ inputs.matrix_target }}-${{ inputs.matrix_cuda }} + + - name: Rustfmt + if: contains(inputs.matrix_os, 'ubuntu') + run: cargo fmt --all -- --check + + - name: Build + run: cargo build --workspace --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "add" --exclude "ex*" + + - name: Clippy + if: runner.os == 'Linux' + env: + RUSTFLAGS: -Dwarnings + run: cargo clippy --workspace --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "add" --exclude "ex*" + + - name: Check documentation + env: + RUSTDOCFLAGS: -Dwarnings + run: cargo doc --workspace --all-features --document-private-items --no-deps --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "add" --exclude "ex*" diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index f7502611..3d2cb6a5 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,10 +1,15 @@ name: Rust CI on: + push: + paths-ignore: + - "**.md" pull_request: + types: [opened, synchronize, reopened] paths-ignore: - "**.md" - push: + pull_request_target: + types: [labeled] paths-ignore: - "**.md" @@ -13,11 +18,9 @@ env: RUST_BACKTRACE: 1 jobs: - rust: - name: Rust on ${{ matrix.os }} with CUDA ${{ matrix.cuda }} + build-and-check: + name: Build & check ${{ matrix.os }} with CUDA ${{ matrix.cuda }} runs-on: ${{ matrix.os }} - env: - LLVM_LINK_STATIC: 1 strategy: fail-fast: false matrix: @@ -30,8 +33,8 @@ jobs: - os: ubuntu-24.04 target: x86_64-unknown-linux-gnu cuda: "12.8.1" - linux-local-args: ["--toolkit"] gpu: "T4" + linux-local-args: ["--toolkit"] - os: windows-latest target: x86_64-pc-windows-msvc cuda: "11.2.2" @@ -40,124 +43,78 @@ jobs: target: x86_64-pc-windows-msvc cuda: "12.8.1" linux-local-args: [] - steps: - name: Checkout repository uses: actions/checkout@v2 - - name: Install CUDA - uses: Jimver/cuda-toolkit@v0.2.22 - id: cuda-toolkit + - name: Run common steps + uses: ./.github/actions/common-steps with: - cuda: ${{ matrix.cuda }} - linux-local-args: ${{ toJson(matrix.linux-local-args) }} - use-local-cache: false - - - name: Verify CUDA installation - run: nvcc --version - - - name: List CUDA_PATH files (Linux) - if: runner.os == 'Linux' - run: find "$CUDA_PATH" -type f - - - name: List CUDA_PATH files (Windows) - if: runner.os == 'Windows' - shell: pwsh - run: Get-ChildItem -Path $env:CUDA_PATH -Recurse | ForEach-Object { $_.FullName } - - - name: Install rust-toolchain - run: cargo version - - - name: Add rustup components - run: rustup component add rustfmt clippy - - - name: Install dependencies for LLVM 7 - if: matrix.os == 'ubuntu-24.04' - run: | - wget -O libffi7.deb http://security.ubuntu.com/ubuntu/pool/universe/libf/libffi7/libffi7_3.3-5ubuntu1_amd64.deb - sudo apt-get update - sudo apt-get install -y ./*.deb - sudo apt-get install -y liblzma-dev - sudo apt-get install -y libssl-dev - sudo apt-get install -y libcurl4-openssl-dev - - - name: Install LLVM 7 - if: contains(matrix.os, 'ubuntu') - run: | - mkdir -p ~/llvm7 && cd ~/llvm7 - wget http://mirrors.kernel.org/ubuntu/pool/universe/l/llvm-toolchain-7/llvm-7_7.0.1-12_amd64.deb \ - http://mirrors.kernel.org/ubuntu/pool/universe/l/llvm-toolchain-7/llvm-7-dev_7.0.1-12_amd64.deb \ - http://mirrors.kernel.org/ubuntu/pool/universe/l/llvm-toolchain-7/libllvm7_7.0.1-12_amd64.deb \ - http://mirrors.kernel.org/ubuntu/pool/universe/l/llvm-toolchain-7/llvm-7-runtime_7.0.1-12_amd64.deb - sudo apt-get update - sudo apt-get install -y ./*.deb - sudo ln -s /usr/bin/llvm-config-7 /usr/local/bin/llvm-config - - - name: Load Rust Cache - uses: Swatinem/rust-cache@v2.7.7 + matrix_os: ${{ matrix.os }} + matrix_cuda: ${{ matrix.cuda }} + matrix_target: ${{ matrix.target }} + matrix_gpu: ${{ matrix.gpu }} + matrix_linux_local_args: ${{ matrix.linux-local-args }} + + integration-tests: + name: Integration tests (after manual review) on ${{ matrix.os }} with CUDA ${{ matrix.cuda }} + runs-on: ${{ matrix.os }} + if: runner.os == 'Linux' && ( github.event_name == 'push' || (github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'DANGER-expose-secrets-and-run-tests')) ) + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-20.04 + target: x86_64-unknown-linux-gnu + cuda: "11.2.2" + gpu: "T4" + linux-local-args: ["--toolkit"] + - os: ubuntu-24.04 + target: x86_64-unknown-linux-gnu + cuda: "12.8.1" + gpu: "T4" + linux-local-args: ["--toolkit"] + env: + RUNNER_OS_NAME: ${{ matrix.os }} + RUNNER_CUDA_VERSION: ${{ matrix.cuda }} + MODAL_GPU_TYPE: ${{ matrix.gpu }} + MODAL_TOKEN_ID: ${{ secrets.MODAL_TOKEN_ID }} + MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }} + steps: + - name: Checkout repository + uses: actions/checkout@v2 with: - key: ${{ matrix.os }}-${{ matrix.target }}-${{ matrix.cuda }} - - - name: Rustfmt - if: contains(matrix.os, 'ubuntu') - run: cargo fmt --all -- --check - - - name: Build - run: cargo build --workspace --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "add" --exclude "ex*" - - - name: Clippy - if: runner.os == 'Linux' - env: - RUSTFLAGS: -Dwarnings - run: cargo clippy --workspace --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "add" --exclude "ex*" + ref: "${{ github.event_name == 'pull_request_target' ? github.event.pull_request.head.sha : github.ref }}" - - name: Check documentation - env: - RUSTDOCFLAGS: -Dwarnings - run: cargo doc --workspace --all-features --document-private-items --no-deps --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "add" --exclude "ex*" + - name: Run common steps + uses: ./.github/actions/common-steps + with: + matrix_os: ${{ matrix.os }} + matrix_cuda: ${{ matrix.cuda }} + matrix_target: ${{ matrix.target }} + matrix_gpu: ${{ matrix.gpu }} + matrix_linux_local_args: ${{ matrix.linux-local-args }} - name: Install python (for CI test runner script) - if: > - runner.os == 'Linux' && - (github.event_name == 'push' || - (github.event_name == 'pull_request' && - github.event.pull_request.head.repo.full_name == github.repository && - contains(github.event.pull_request.labels.*.name, 'DANGER-expose-secrets-and-run-tests'))) + if: runner.os == 'Linux' uses: actions/setup-python@v5 with: python-version: "3.10" - name: Install CI test runner deps - if: > - runner.os == 'Linux' && - (github.event_name == 'push' || - (github.event_name == 'pull_request' && - github.event.pull_request.head.repo.full_name == github.repository && - contains(github.event.pull_request.labels.*.name, 'DANGER-expose-secrets-and-run-tests'))) + if: runner.os == 'Linux' working-directory: .ci/modal.com run: | curl -LsSf https://astral.sh/uv/install.sh | sh uv sync - name: Check CI test runner formatting - if: > - runner.os == 'Linux' && - (github.event_name == 'push' || - (github.event_name == 'pull_request' && - github.event.pull_request.head.repo.full_name == github.repository && - contains(github.event.pull_request.labels.*.name, 'DANGER-expose-secrets-and-run-tests'))) + if: runner.os == 'Linux' working-directory: .ci/modal.com run: uvx black --check . - # Secrets are not available in forked PRs, so we have to manually add a label and - # re-run. - - name: Run integration tests - if: > - runner.os == 'Linux' && - (github.event_name == 'push' || - (github.event_name == 'pull_request' && - github.event.pull_request.head.repo.full_name == github.repository && - contains(github.event.pull_request.labels.*.name, 'DANGER-expose-secrets-and-run-tests'))) + - name: Run tests on the GPU + if: runner.os == 'Linux' env: RUNNER_OS_NAME: ${{ matrix.os }} RUNNER_CUDA_VERSION: ${{ matrix.cuda }} From d56438d8f673bcfa6a03dcbb401050ce61f49220 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Tue, 1 Apr 2025 23:03:01 -0400 Subject: [PATCH 15/19] Pull request should not specify which events --- .github/workflows/rust.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 3d2cb6a5..895df192 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -5,7 +5,6 @@ on: paths-ignore: - "**.md" pull_request: - types: [opened, synchronize, reopened] paths-ignore: - "**.md" pull_request_target: From db6e9f3b51f6d20f12578528720d013f3d01b1da Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Tue, 1 Apr 2025 23:05:11 -0400 Subject: [PATCH 16/19] Fix syntax error by breaking into two steps with ifs --- .github/workflows/rust.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 895df192..971a1954 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -80,10 +80,15 @@ jobs: MODAL_TOKEN_ID: ${{ secrets.MODAL_TOKEN_ID }} MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }} steps: - - name: Checkout repository + - name: Checkout for PR (target) + if: github.event_name == 'pull_request_target' uses: actions/checkout@v2 with: - ref: "${{ github.event_name == 'pull_request_target' ? github.event.pull_request.head.sha : github.ref }}" + ref: ${{ github.event.pull_request.head.sha }} + + - name: Checkout for push + if: github.event_name != 'pull_request_target' + uses: actions/checkout@v2 - name: Run common steps uses: ./.github/actions/common-steps From 7c8746c15d6b0187dcda495a55dc0c5f6559380e Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Tue, 1 Apr 2025 23:07:21 -0400 Subject: [PATCH 17/19] Another syntax error fix --- .github/workflows/rust.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 971a1954..24a97636 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -58,7 +58,7 @@ jobs: integration-tests: name: Integration tests (after manual review) on ${{ matrix.os }} with CUDA ${{ matrix.cuda }} runs-on: ${{ matrix.os }} - if: runner.os == 'Linux' && ( github.event_name == 'push' || (github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'DANGER-expose-secrets-and-run-tests')) ) + if: ( github.event_name == 'push' || (github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'DANGER-expose-secrets-and-run-tests')) ) strategy: fail-fast: false matrix: @@ -85,7 +85,7 @@ jobs: uses: actions/checkout@v2 with: ref: ${{ github.event.pull_request.head.sha }} - + - name: Checkout for push if: github.event_name != 'pull_request_target' uses: actions/checkout@v2 From 609368da7da2889dd625b4dfb9006270ef5b7dbc Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Tue, 1 Apr 2025 23:11:47 -0400 Subject: [PATCH 18/19] Add `shell:` to common steps --- .github/actions/common-steps/action.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/actions/common-steps/action.yml b/.github/actions/common-steps/action.yml index 5da73711..fb67d4d2 100644 --- a/.github/actions/common-steps/action.yml +++ b/.github/actions/common-steps/action.yml @@ -28,10 +28,12 @@ runs: use-local-cache: false - name: Verify CUDA installation + shell: bash run: nvcc --version - name: List CUDA_PATH files (Linux) if: runner.os == 'Linux' + shell: bash run: find "$CUDA_PATH" -type f - name: List CUDA_PATH files (Windows) @@ -40,6 +42,7 @@ runs: run: Get-ChildItem -Path $env:CUDA_PATH -Recurse | ForEach-Object { $_.FullName } - name: Install rust-toolchain + shell: bash run: cargo version - name: Add rustup components @@ -47,6 +50,7 @@ runs: - name: Install dependencies for LLVM 7 if: inputs.matrix_os == 'ubuntu-24.04' + shell: bash run: | wget -O libffi7.deb http://security.ubuntu.com/ubuntu/pool/universe/libf/libffi7/libffi7_3.3-5ubuntu1_amd64.deb sudo apt-get update @@ -54,6 +58,7 @@ runs: - name: Install LLVM 7 if: contains(inputs.matrix_os, 'ubuntu') + shell: bash run: | mkdir -p ~/llvm7 && cd ~/llvm7 wget http://mirrors.kernel.org/ubuntu/pool/universe/l/llvm-toolchain-7/llvm-7_7.0.1-12_amd64.deb \ @@ -71,18 +76,22 @@ runs: - name: Rustfmt if: contains(inputs.matrix_os, 'ubuntu') + shell: bash run: cargo fmt --all -- --check - name: Build + shell: bash run: cargo build --workspace --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "add" --exclude "ex*" - name: Clippy + shell: bash if: runner.os == 'Linux' env: RUSTFLAGS: -Dwarnings run: cargo clippy --workspace --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "add" --exclude "ex*" - name: Check documentation + shell: bash env: RUSTDOCFLAGS: -Dwarnings run: cargo doc --workspace --all-features --document-private-items --no-deps --exclude "optix" --exclude "path_tracer" --exclude "denoiser" --exclude "add" --exclude "ex*" From aaf790dc788fe7f2e556dbadab8d79e51d579a59 Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Tue, 1 Apr 2025 23:12:46 -0400 Subject: [PATCH 19/19] Add another shell --- .github/actions/common-steps/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/common-steps/action.yml b/.github/actions/common-steps/action.yml index fb67d4d2..caf3f0d9 100644 --- a/.github/actions/common-steps/action.yml +++ b/.github/actions/common-steps/action.yml @@ -46,6 +46,7 @@ runs: run: cargo version - name: Add rustup components + shell: bash run: rustup component add rustfmt clippy - name: Install dependencies for LLVM 7