diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0033147..0752e3f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ "main" ] +permissions: + contents: read + jobs: test: runs-on: ubuntu-latest diff --git a/CLAUDE.md b/CLAUDE.md index 31dab43..1b3e58a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,14 +4,33 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -**gcpath** is a CLI utility for querying Google Cloud Platform (GCP) resource hierarchy paths. It translates between GCP resource names (e.g., `folders/12345`) and human-readable paths (e.g., `//example.com/department/team`). +**gcpath** is an AXI-compliant CLI utility for querying Google Cloud Platform (GCP) resource hierarchy paths. It translates between GCP resource names (e.g., `folders/12345`) and human-readable paths (e.g., `//example.com/department/team`). Key features: +- AXI-compliant output: TOON format by default, with `--format toon|json|yaml|rich` - Dual API modes: Cloud Asset API (default, fast) and Resource Manager API (slower, different permissions) -- Commands: `ls`, `tree`, `name` (path → resource name), `path` (resource name → path) +- Commands: `ls`, `tree`, `name`, `path`, `find`, `ancestors`, `stats`, `diagram` - Scoped loading to improve performance for large hierarchies - Support for organizationless projects (`//_` prefix) +- Ambient context hooks for Claude Code and Codex (`gcpath hook install`) + +## AXI Compliance + +gcpath follows the [AXI specification](https://axi.md/) for agent-friendly CLI tools: + +- **TOON default output**: All commands output TOON format by default (token-efficient, structured) +- **`--format` flag**: `toon` (default), `json`, `yaml`, `rich` (human-friendly colored tables) +- **Pre-computed aggregates**: `ls` shows `count: N of M total`, `find` shows match count +- **Definitive empty states**: `0 resources found` with contextual help +- **Structured errors**: Errors go to stdout in TOON format, not stderr Rich markup +- **Content-first home**: Running `gcpath` with no args shows a live dashboard from cache +- **Contextual help**: `help[]` sections appended after list outputs with relevant next-step commands +- **`--fields` flag**: Control which columns appear in tabular output (`path,type,display_name,resource_name,project_id,labels,tags`) +- **`--full` flag**: Expand truncated labels/tags without truncation +- **No interactive prompts**: All confirm prompts removed; commands just proceed +- **`tree` is human-oriented**: Classic unicode tree output, not TOON. Agents should use `ls -R` instead. No `help[]` suggestions for `tree` in agent followups. +- **`diagram` is raw output**: Mermaid/D2 text output as-is, no TOON wrapping ## Setup and Build @@ -59,8 +78,18 @@ The codebase is organized into focused, single-responsibility modules: - **`formatters.py`**: Display formatting logic for paths, trees, and resource filtering. +- **`serializers.py`**: Output serialization — TOON, JSON, YAML serializers for all commands. + +- **`toon.py`**: Thin wrapper around `toon_format.encode()` plus gcpath-specific AXI helpers (error formatting, empty states, help sections, dashboards). + +- **`hooks.py`**: Agent session hook management (install/uninstall/status for Claude Code and Codex). + - **`cli.py`**: CLI commands and entry points using Typer framework. +- **`cache.py`**: Cache management for hierarchy data. + +- **`config.py`**: Configuration management (entrypoint settings). + ### Data Flow 1. **Hierarchy Loading** (`Hierarchy.load()`): @@ -78,10 +107,12 @@ The codebase is organized into focused, single-responsibility modules: - Loaders use filters to only fetch descendants of that resource - Significantly reduces API calls and latency for large hierarchies -4. **Display**: CLI uses formatters to present data: - - Direct children filtering for `ls` (non-recursive mode) - - Tree recursion with depth limiting - - Path display with URL encoding via `path_escape()` +4. **Output**: CLI dispatches to serializers based on `--format`: + - `toon` (default): TOON tabular arrays, objects, help sections + - `json`/`yaml`: Structured dicts via `dump_json`/`dump_yaml` + - `rich`: Rich-colored tables and tree widgets + - `tree` command always uses Rich tree output (not TOON) + - `diagram` command always outputs raw Mermaid/D2 text ### Key Design Patterns @@ -89,6 +120,7 @@ The codebase is organized into focused, single-responsibility modules: - **Protobuf Objects**: Use actual `google.cloud.resourcemanager_v3` protobuf objects (not mocks) throughout the data layer - **Optional Organization**: Projects can be organizationless (no parent organization) - **Lightweight Path Resolution**: `Hierarchy.resolve_ancestry()` traverses up the hierarchy without loading full state +- **Content-first home**: `gcpath` with no args reads cache and shows dashboard, never shows help text ## Important Implementation Details @@ -113,6 +145,15 @@ The Asset API returns STRUCT fields as `MapComposite` objects. Key handling: - `ancestors_filter`: Returns all descendants including in ancestors list (recursive) - Default (no filter): Returns org-level or root-level resources +### TOON Output Conventions + +- Default `ls` schema: `{path,type,display_name}` (3 fields); adds `project_id` for projects +- `--fields` overrides: any subset of `path,type,display_name,resource_name,project_id,labels,tags` +- Labels/tags truncated to 5 entries by default; `--full` shows all +- `count: N of M total` header on list outputs +- `help[]` sections with next-step suggestions (omitted for single-result commands like `name`, `path`) +- Errors to stdout as `error: ` with optional `help[]` + ## Testing Test files mirror source organization: @@ -121,6 +162,7 @@ Test files mirror source organization: - `test_loaders.py`: GCP API loading functions - `test_parsers.py`: Asset API response parsing - `test_formatters.py`: Display formatting +- `test_serializers.py`: Output serialization (TOON, JSON, YAML) - `test_cli.py`: CLI command integration Run specific test: @@ -181,3 +223,5 @@ The project uses **semantic versioning** with automated release workflow via Git - All exceptions inherit from `GCPathError` base class - CLI commands should wrap GCP API calls with error handling - Follow conventional commits for automatic version bumping +- TOON encoding uses `toon_format` library (imported as `toon_format`) +- `toon.py` is a thin wrapper that adds gcpath-specific AXI conventions on top of `toon_format.encode()` diff --git a/README.md b/README.md index c141a46..345af9d 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,36 @@ # gcpath -`gcpath` is a CLI utility to query Google Cloud Platform resource hierarchy paths. -It helps you translate between GCP resource names (e.g., `folders/12345`) and human-readable paths (e.g., `//example.com/department/team`). +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![Python](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/) +[![AXI Compliant](https://img.shields.io/badge/AXI-compliant-green.svg)](https://axi.md/) +[![PyPI](https://img.shields.io/pypi/v/gcpath.svg)](https://pypi.org/project/gcpath/) +[![CI](https://github.com/tardigrde/gcpath/actions/workflows/ci.yml/badge.svg)](https://github.com/tardigrde/gcpath/actions/workflows/ci.yml) +[![codecov](https://codecov.io/gh/tardigrde/gcpath/branch/main/graph/badge.svg)](https://codecov.io/gh/tardigrde/gcpath) -## Why should I use gcpath? +`gcpath` is an agent-native, read-only CLI for querying Google Cloud Platform resource hierarchy paths. It translates between GCP resource names (e.g., `folders/12345`) and human-readable paths (e.g., `//example.com/department/team`). -- familiar linux-like CLI -- you can stay in the terminal for quick resource hierarchy lookups -- no need to learn the complex `gcloud` interface -- look-up only commands, so coding agents can't do harm using it -- installable as an [Agent Skill](#agent-skill) so AI agents know how to use it +## Why gcpath? + +GCP's resource hierarchy — organizations, folders, projects — is central to how teams structure access, billing, and governance. But navigating it with `gcloud` means juggling multiple subcommands, parsing verbose output, and stitching together parent-child relationships yourself. + +gcpath gives you a single tool that treats the hierarchy as a first-class concept: list, search, resolve paths, and traverse ancestry with Unix-style commands. + +### Built for AI agents + +gcpath is designed from the ground up to be used by AI coding agents (Claude Code, Codex, etc.), not just humans: + +- **Fully read-only** — every command is a query, never a mutation. Agents can run gcpath freely with zero risk of modifying your GCP environment. +- **[AXI-compliant](https://axi.md/) output** — defaults to [TOON format](https://github.com/mwmwmw/toon-format), a token-efficient structured format that agents parse natively. Includes pre-computed aggregates, definitive empty states, structured errors, and contextual help sections — no screen-scraping required. +- **Ambient context hooks** — `gcpath hook install` registers a session-start hook with Claude Code and Codex so the agent automatically knows your GCP hierarchy context when a session begins. No manual prompting needed. +- **Agent Skill definition** — ships with an [Agent Skills](https://agentskills.io) manifest that teaches agents when to use gcpath, what commands are available, and common workflows — so the agent reaches for the right tool without you telling it to. +- **Multiple output formats** — `--format toon|json|yaml|rich` lets agents pick the format that suits the task, with TOON as the default for maximum token efficiency. + +### Also great for humans + +- Familiar Linux-like CLI (`ls`, `tree`, `find`) +- Stay in the terminal for quick hierarchy lookups +- Rich colored output with `--format rich` +- No need to learn the complex `gcloud` resource manager interface ## Features @@ -20,8 +41,8 @@ It helps you translate between GCP resource names (e.g., `folders/12345`) and hu - **Find**: Search for resources by name using glob patterns. - **Ancestors**: Show the full ancestry chain from any resource up to the org root. - **Type Filtering**: Filter `ls`, `tree`, and `find` output by resource type (folder, project, organization). -- **Structured Output**: `--json` and `--yaml` flags for machine-readable output across all commands. -- **Dual Mode**: +- **Structured Output**: `--format toon|json|yaml|rich` for machine-readable or human-friendly output across all commands. +- **Dual API Mode**: - **Cloud Asset API (Default)**: Fast, bulk loading using GCP Cloud Asset Inventory. - **Resource Manager API**: Iterative loading using standard Resource Manager API (slower, but different permissions). @@ -89,8 +110,8 @@ gcpath diagram gcpath diagram folders/123456789 --format d2 # Machine-readable output -gcpath --json ls -R -gcpath --yaml tree -L 2 +gcpath --format json ls -R +gcpath --format yaml tree -L 2 ``` ## Usage @@ -246,31 +267,32 @@ gcpath ancestors projects/my-project gcpath ancestors folders/123456789 # JSON output for scripting -gcpath --json ancestors projects/my-project +gcpath --format json ancestors projects/my-project ``` -### Structured Output (`--json`, `--yaml`) +### Output Formats (`--format`) + +All commands support `--format toon|json|yaml|rich`: -All commands support `--json` and `--yaml` global flags for machine-readable output: +| Format | Best for | Description | +|--------|----------|-------------| +| `toon` | AI agents (default) | Token-efficient structured format with aggregates and help sections | +| `json` | Scripting / piping | Standard JSON | +| `yaml` | Config files / readability | Standard YAML | +| `rich` | Humans in a terminal | Colored tables and trees | ```bash -# JSON output -gcpath --json ls -R -gcpath --json tree -L 2 -gcpath --json find "*prod*" -gcpath --json ancestors projects/my-project -gcpath --json name //example.com/engineering -gcpath --json path folders/123456789 +# Default TOON output (agent-optimized) +gcpath ls -R -# YAML output -gcpath --yaml ls -gcpath --yaml tree -``` +# JSON for scripting +gcpath --format json ls -R | jq '.[] | select(.type == "project")' -The flags are mutually exclusive. Structured output goes to stdout with status messages redirected to stderr, so it's safe to pipe: +# Rich colored output for humans +gcpath --format rich ls -R -```bash -gcpath --json ls -R | jq '.[] | select(.type == "project")' +# YAML output +gcpath --format yaml ancestors projects/my-project ``` ## API Modes @@ -479,11 +501,30 @@ except GCPathError as e: | `PathParsingError` | Raised when a path string cannot be parsed. | | `path_escape()` | URL-encodes a display name for safe use in paths. | -## Agent Skill +## Agent Integration + +gcpath is designed to work seamlessly with AI coding agents. There are two complementary ways to set this up. + +### Ambient Context Hooks + +Session-start hooks inject your GCP hierarchy context into the agent's conversation automatically — the agent knows about your orgs, folders, and projects from the moment a session begins. + +```bash +# Install hooks for Claude Code and Codex +gcpath hook install + +# Check hook status +gcpath hook status + +# Remove hooks +gcpath hook uninstall +``` + +This registers a `SessionStart` hook that runs `gcpath hook run`, which outputs a compact TOON dashboard of your cached hierarchy (under 500 tokens). The agent sees this context without you having to explain your GCP setup each session. -gcpath ships with an [Agent Skills](https://agentskills.io) definition so AI agents (Claude, Codex, etc.) can discover and use it without extra setup. +### Agent Skill -Install the skill into your agent environment: +gcpath ships with an [Agent Skills](https://agentskills.io) definition that teaches agents when and how to use it. ```bash bunx skills add github:tardigrde/gcpath --skill gcpath @@ -493,10 +534,10 @@ npx skills add github:tardigrde/gcpath --skill gcpath The skill teaches the agent: -- when to reach for `gcpath` vs other GCP tools +- when to reach for `gcpath` vs other GCP tools (and when *not* to — e.g., IAM, billing, compute) - all commands, flags, and output formats - common workflows (ancestry lookup, scoped listing, path ↔ name conversion) -- gotchas (organizationless projects, caching behaviour, API modes) +- gotchas (organizationless projects, caching behavior, API modes) See [`skills/gcpath/SKILL.md`](skills/gcpath/SKILL.md) for the full skill definition and [`skills/gcpath/references/commands.md`](skills/gcpath/references/commands.md) for the compact command reference. diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..d84b612 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,14 @@ +coverage: + precision: 2 + round: down + range: "60...100" + + status: + project: + default: + target: auto + threshold: 2% + patch: + default: + target: 80% + threshold: 5% diff --git a/pyproject.toml b/pyproject.toml index 1fc22f9..57ead66 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ dependencies = [ "google-cloud-resource-manager>=1.15.0", "pyyaml>=6.0", "rich>=14.2.0", + "toon-format @ git+https://github.com/toon-format/toon-python.git@90861444e5bf7d6408e91bd95e58dba41dd99be8", "typer>=0.20.1", ] @@ -47,6 +48,9 @@ dev = [ requires = ["hatchling"] build-backend = "hatchling.build" +[tool.hatch.metadata] +allow-direct-references = true + [tool.hatch.build.targets.wheel] packages = ["src/gcpath"] diff --git a/src/gcpath/cli.py b/src/gcpath/cli.py index bfad247..427b830 100644 --- a/src/gcpath/cli.py +++ b/src/gcpath/cli.py @@ -1,16 +1,15 @@ import fnmatch +import shutil import typer import logging +from pathlib import Path from dataclasses import dataclass from typing import Optional, List, Dict, Union from urllib.parse import unquote from typing_extensions import Annotated -from rich.console import Console from rich import print as rprint -from rich.markup import escape from google.api_core import exceptions as gcp_exceptions - from gcpath.core import ( Hierarchy, path_escape, @@ -36,13 +35,32 @@ serialize_ancestors, dump_json, dump_yaml, + toon_ls, + toon_name, + toon_path, + toon_ancestors, + toon_find, + toon_stats, + toon_cache_status, + toon_config, + toon_confirmed, + toon_encode, + _ALL_LS_FIELDS, +) +from gcpath.toon import format_age, toon_error +from gcpath.hooks import ( + install_hooks, + uninstall_hooks, + get_hook_status, + run_session_start, ) -from typing import Any, Callable +from typing import Any, Callable, Tuple from gcpath.cache import ( read_cache, write_cache, clear_cache, get_cache_info, + read_cache_raw, CACHE_FILE, ) from gcpath.config import ( @@ -52,9 +70,7 @@ read_config, CONFIG_FILE, ) -from rich.table import Table -# Resource name prefixes _RESOURCE_PREFIX_PROJECTS = "projects/" _RESOURCE_PREFIX_FOLDERS = "folders/" _RESOURCE_PREFIX_ORGS = "organizations/" @@ -65,29 +81,31 @@ ) _REFRESH_HELP = "Force a refresh of the cache from the GCP API" _VALID_TYPE_FILTERS = ("folder", "project", "organization") +_VALID_FORMATS = ("toon", "json", "yaml", "rich") logger = logging.getLogger(__name__) +def _display_bin_path(path: str) -> str: + home = str(Path.home()) + if path.startswith(home): + return "~" + path[len(home):] + return path + + def _validate_type_filter(resource_type: Optional[str]) -> None: - """Validate --type filter value.""" if resource_type is not None and resource_type not in _VALID_TYPE_FILTERS: - error_console.print( - f"[red]Error:[/red] Invalid type '{resource_type}'. " - f"Must be one of: {', '.join(_VALID_TYPE_FILTERS)}" - ) + print(toon_error(f"Invalid type '{resource_type}'. Must be one of: {', '.join(_VALID_TYPE_FILTERS)}")) raise typer.Exit(code=1) def _matches_type( obj: Union[OrganizationNode, Folder, Project], type_filter: str ) -> bool: - """Check if a resource matches the given type filter.""" return get_resource_type(obj) == type_filter def _parse_label_filter(label_str: str) -> tuple: - """Parse a label filter string 'key=value' into (key, value) tuple.""" if "=" not in label_str: return (label_str, None) key, _, value = label_str.partition("=") @@ -99,7 +117,6 @@ def _matches_metadata( filters: List[str], attr: str, ) -> bool: - """Check if a resource matches ALL filters (ANDed) for a given metadata attribute.""" if not filters: return True metadata = getattr(obj, attr, {}) @@ -113,18 +130,8 @@ def _matches_metadata( return True -def _format_metadata(obj: Union[OrganizationNode, Folder, Project], attr: str) -> str: - """Format a metadata dict attribute as comma-separated key=value string.""" - metadata = getattr(obj, attr, {}) - if not metadata: - return "" - return ", ".join(f"{k}={v}" for k, v in sorted(metadata.items())) - - @dataclass class _ScopeResult: - """Result of resolving a resource scope argument.""" - target_resource_name: Optional[str] target_org_name: Optional[str] filter_orgs: Optional[List[str]] @@ -155,7 +162,6 @@ def _resolve_scope( except (gcp_exceptions.PermissionDenied, gcp_exceptions.NotFound, GCPathError): pass - # Skip org filtering when using entrypoint without explicit resource if not resource and entrypoint and target_org_name: filter_orgs = None else: @@ -172,17 +178,16 @@ def _resolve_scope( name="gcpath", help="Google Cloud Platform resource hierarchy utility", add_completion=False, + invoke_without_command=True, ) cache_app = typer.Typer(help="Manage the local resource cache.") app.add_typer(cache_app, name="cache") config_app = typer.Typer(help="Manage gcpath configuration.") app.add_typer(config_app, name="config") -console = Console() -error_console = Console(stderr=True) - +hook_app = typer.Typer(help="Manage agent session hooks.") +app.add_typer(hook_app, name="hook") def _get_dumper(output_format: str) -> Optional[Callable[[Any], str]]: - """Return the appropriate dumper for the given output format, or None for text.""" if output_format == "json": return dump_json if output_format == "yaml": @@ -190,36 +195,29 @@ def _get_dumper(output_format: str) -> Optional[Callable[[Any], str]]: return None +def _output_toon(data_or_str: Any) -> None: + if isinstance(data_or_str, str): + print(data_or_str) + else: + print(toon_encode(data_or_str)) + + def handle_error(e: Exception) -> None: - """Central error handler for CLI.""" if isinstance(e, GCPathError): - error_console.print(f"[red]Error:[/red] {e}") + print(toon_error(str(e))) elif isinstance(e, gcp_exceptions.PermissionDenied): - error_console.print( - "[red]Permission Denied:[/red] Ensure you have the required permissions and are authenticated." - ) - error_console.print( - "[dim]Hint: Run 'gcloud auth application-default login'[/dim]" - ) + print(toon_error( + "Permission Denied. Ensure you have the required permissions and are authenticated.", + ["Run 'gcloud auth application-default login'"], + )) elif isinstance(e, gcp_exceptions.ServiceUnavailable): - error_console.print( - "[red]Service Unavailable:[/red] The GCP API is currently unreachable." - ) + print(toon_error("The GCP API is currently unreachable.")) elif isinstance(e, Exception): - error_console.print(f"[red]Unexpected Error:[/red] {e}") + print(toon_error(f"Unexpected error: {e}")) logging.exception("Unexpected error occurred") raise typer.Exit(code=1) -def _format_age(seconds: float) -> str: - """Format age in seconds to a human-readable string.""" - hours = int(seconds // 3600) - minutes = int((seconds % 3600) // 60) - if hours > 0: - return f"{hours}h {minutes}m" - return f"{minutes}m" - - @app.callback() def main( ctx: typer.Context, @@ -236,99 +234,160 @@ def main( "-e", help="Default resource to scope commands to (e.g., folders/123). Overrides config.", ), - json_output: bool = typer.Option( - False, - "--json", - help="Output in JSON format", - ), - yaml_output: bool = typer.Option( - False, - "--yaml", - help="Output in YAML format", + output_format: str = typer.Option( + "toon", + "--format", + help=f"Output format: {', '.join(_VALID_FORMATS)}", ), ) -> None: - """ - gcpath - Google Cloud Platform resource hierarchy utility - """ - if json_output and yaml_output: - error_console.print( - "[red]Error:[/red] --json and --yaml are mutually exclusive." - ) + """gcpath - Google Cloud Platform resource hierarchy utility""" + if output_format not in _VALID_FORMATS: + print(toon_error(f"Invalid format '{output_format}'. Must be one of: {', '.join(_VALID_FORMATS)}")) raise typer.Exit(code=1) ctx.ensure_object(dict) ctx.obj["use_asset_api"] = use_asset_api ctx.obj["entrypoint"] = entrypoint or get_entrypoint() - - if json_output: - ctx.obj["output_format"] = "json" - elif yaml_output: - ctx.obj["output_format"] = "yaml" - else: - ctx.obj["output_format"] = "text" + ctx.obj["output_format"] = output_format if debug: logging.basicConfig(level=logging.DEBUG) else: logging.basicConfig(level=logging.ERROR) - # Always suppress urllib3 debug logs logging.getLogger("urllib3").setLevel(logging.WARNING) + if ctx.invoked_subcommand is None: + _show_home(ctx) + + +def _show_home(_ctx: typer.Context) -> None: + """Content-first home view (AXI Principle 8).""" + gcpath_bin = _display_bin_path(shutil.which("gcpath") or "gcpath") + info = get_cache_info() + + if not info.exists or not info.fresh: + dashboard: Dict[str, Any] = { + "bin": gcpath_bin, + "description": "Query GCP resource hierarchy paths", + "cache": "empty", + } + help_lines = [ + "Run `gcpath ls` to load and list resources", + "Run `gcpath hook install` to enable ambient context", + ] + print(toon_encode(dashboard)) + print(toon_encode({"help": help_lines})) + raise typer.Exit() + + age_str = format_age(info.age_seconds) if info.age_seconds else "unknown" + + org_rows = [] + raw_data = read_cache_raw() + if raw_data: + for org_data in raw_data.get("organizations", []): + org_info = org_data.get("organization", {}) + name = org_info.get("display_name", "unknown") + folder_count = len(org_data.get("folders", {})) + project_count = len(org_data.get("projects", [])) + org_rows.append({ + "name": name, + "folders": folder_count, + "projects": project_count, + }) + + dashboard = { + "bin": gcpath_bin, + "description": "Query GCP resource hierarchy paths", + "cache": f"fresh ({age_str} ago)", + } + if org_rows: + dashboard["organizations"] = org_rows + + help_lines = [ + "Run `gcpath ls` to list root-level resources", + "Run `gcpath ls -R` for recursive listing", + "Run `gcpath find ` to search by name", + ] + print(toon_encode(dashboard)) + print(toon_encode({"help": help_lines})) + raise typer.Exit() + @cache_app.command("clear") -def cache_clear() -> None: +def cache_clear(ctx: typer.Context) -> None: """Clear the local resource cache.""" + fmt = ctx.obj.get("output_format", "toon") if clear_cache(): - rprint(f"[green]Cache cleared successfully at {CACHE_FILE}[/green]") + msg = f"Cache cleared at {CACHE_FILE}" + if fmt == "rich": + rprint(f"[green]{msg}[/green]") + else: + print(toon_confirmed(msg)) else: - rprint(f"[yellow]No cache file to clear at {CACHE_FILE}[/yellow]") - + msg = f"No cache file to clear at {CACHE_FILE}" + if fmt == "rich": + rprint(f"[yellow]{msg}[/yellow]") + else: + print(toon_confirmed(msg)) -@cache_app.command("status") -def cache_status() -> None: - """Show cache status information.""" - info = get_cache_info() +def _cache_status_rich(info: Any) -> None: + """Render cache status as a Rich table.""" + from rich.table import Table + from rich.console import Console + console = Console() if not info.exists: rprint(f"[yellow]No cache file found at {CACHE_FILE}[/yellow]") return - table = Table(show_header=False, box=None, padding=(0, 1)) table.add_column("Key", style="bold") table.add_column("Value") - - if info.fresh: - table.add_row("Status", "[green]Fresh[/green]") - else: - table.add_row("Status", "[yellow]Stale[/yellow]") - + table.add_row("Status", "[green]Fresh[/green]" if info.fresh else "[yellow]Stale[/yellow]") if info.age_seconds is not None: - table.add_row("Age", _format_age(info.age_seconds)) - + table.add_row("Age", format_age(info.age_seconds)) if info.size_bytes is not None: size_kb = info.size_bytes / 1024 - if size_kb >= 1024: - table.add_row("Size", f"{size_kb / 1024:.1f} MB") - else: - table.add_row("Size", f"{size_kb:.1f} KB") - + size_str = f"{size_kb / 1024:.1f} MB" if size_kb >= 1024 else f"{size_kb:.1f} KB" + table.add_row("Size", size_str) if info.version is not None: table.add_row("Version", str(info.version)) - if info.scope is not None: table.add_row("Scope", info.scope) - table.add_row("Organizations", str(info.org_count)) table.add_row("Folders", str(info.folder_count)) table.add_row("Projects", str(info.project_count)) table.add_row("Location", str(CACHE_FILE)) - console.print(table) +@cache_app.command("status") +def cache_status(ctx: typer.Context) -> None: + """Show cache status information.""" + fmt = ctx.obj.get("output_format", "toon") + info = get_cache_info() + + if fmt == "rich": + _cache_status_rich(info) + return + + print(toon_cache_status( + exists=info.exists, + fresh=info.fresh, + age_seconds=info.age_seconds, + size_bytes=info.size_bytes, + version=info.version, + scope=info.scope, + org_count=info.org_count, + folder_count=info.folder_count, + project_count=info.project_count, + location=str(CACHE_FILE), + )) + + @config_app.command("set-entrypoint") def config_set_entrypoint( + ctx: typer.Context, resource: Annotated[ str, typer.Argument( @@ -337,54 +396,68 @@ def config_set_entrypoint( ], ) -> None: """Set the default entrypoint resource.""" + fmt = ctx.obj.get("output_format", "toon") try: set_entrypoint(resource) - rprint(f"[green]Entrypoint set to {resource}[/green]") + msg = f"Entrypoint set to {resource}" + if fmt == "rich": + rprint(f"[green]{msg}[/green]") + else: + print(toon_confirmed(msg)) except ValueError as e: - rprint(f"[red]Error:[/red] {e}") + if fmt == "rich": + rprint(f"[red]Error:[/red] {e}") + else: + print(toon_error(str(e))) raise typer.Exit(code=1) @config_app.command("show") -def config_show() -> None: +def config_show(ctx: typer.Context) -> None: """Show current configuration.""" + fmt = ctx.obj.get("output_format", "toon") config = read_config() - if not config: - rprint("[yellow]No configuration set.[/yellow]") - rprint(f"[dim]Config file: {CONFIG_FILE}[/dim]") - return - - table = Table(show_header=False, box=None, padding=(0, 1)) - table.add_column("Key", style="bold") - table.add_column("Value") - for key, value in config.items(): - table.add_row(key, str(value)) + if fmt == "rich": + from rich.table import Table + from rich.console import Console + console = Console() + if not config: + rprint("[yellow]No configuration set.[/yellow]") + rprint(f"[dim]Config file: {CONFIG_FILE}[/dim]") + return + table = Table(show_header=False, box=None, padding=(0, 1)) + table.add_column("Key", style="bold") + table.add_column("Value") + for key, value in config.items(): + table.add_row(key, str(value)) + table.add_row("location", str(CONFIG_FILE)) + console.print(table) + return - table.add_row("location", str(CONFIG_FILE)) - console.print(table) + print(toon_config(config, str(CONFIG_FILE))) @config_app.command("clear-entrypoint") -def config_clear_entrypoint() -> None: +def config_clear_entrypoint(ctx: typer.Context) -> None: """Remove the default entrypoint.""" + fmt = ctx.obj.get("output_format", "toon") clear_entrypoint() - rprint("[green]Entrypoint cleared.[/green]") + msg = "Entrypoint cleared" + if fmt == "rich": + rprint(f"[green]{msg}[/green]") + else: + print(toon_confirmed(msg)) def _clean_resolve_path(path: str) -> str: - """Clean up a resolved path that may contain _unknown_org_ segments.""" if "_unknown_org_" in path: - # Strip //_unknown_org_(organizations/ID) prefix, keep rest - # e.g., "//_unknown_org_(organizations/123)/Folder/Sub" -> "//Folder/Sub" parts = path.split("/") - # Find the _unknown_org_ segment and skip it cleaned = [] for part in parts: if "_unknown_org_" in part: continue cleaned.append(part) - # Rebuild: starts with // so cleaned[0] and [1] are empty strings result = "/".join(cleaned) if not result.startswith("//"): result = "//" + result.lstrip("/") @@ -396,21 +469,10 @@ def _try_read_cache( cache_scope: Optional[str], filter_orgs: Optional[List[str]], ) -> Optional[Hierarchy]: - """Try to read hierarchy from cache, applying org filter if needed. - - Returns None if cache is not available. - """ cached_hierarchy = read_cache(scope=cache_scope) if cached_hierarchy is None: return None - info = get_cache_info() - age_str = _format_age(info.age_seconds) if info.age_seconds else "unknown" - error_console.print( - f"[dim]Using cached data ({age_str} ago). Use -F to refresh.[/dim]" - ) - - # Apply org filter to cached data if filter_orgs: cached_hierarchy.organizations = [ o @@ -429,15 +491,8 @@ def _load_hierarchy( include_labels: bool = False, include_tags: bool = False, ) -> Hierarchy: - """Helper to load hierarchy with cache orchestration. - - Cache is used for unscoped loads and entrypoint-scoped loads. - One-off queries (scope_resource set but != entrypoint) are not cached. - Cacheable loads always use recursive=True so the cache contains complete data. - """ entrypoint = ctx.obj.get("entrypoint") - # Cacheable: unscoped OR scope matches entrypoint is_cacheable = (scope_resource is None) or ( entrypoint is not None and scope_resource == entrypoint ) @@ -446,9 +501,11 @@ def _load_hierarchy( if is_cacheable and not force_refresh: cached = _try_read_cache(cache_scope, filter_orgs) if cached is not None: + info = get_cache_info() + if info.age_seconds is not None: + logger.debug(f"Using cached data ({format_age(info.age_seconds)} ago). Use -F to refresh.") return cached - # Always recursive for cacheable loads (complete data for all commands) effective_recursive = True if is_cacheable else recursive hierarchy = Hierarchy.load( @@ -468,8 +525,6 @@ def _load_hierarchy( @dataclass class _HierarchyCommandContext: - """Shared context produced by _prepare_hierarchy_command.""" - hierarchy: Hierarchy nodes_to_process: List[Union[OrganizationNode, Folder]] projects_by_parent: Dict[str, List[Project]] @@ -479,23 +534,16 @@ class _HierarchyCommandContext: @dataclass class _ParsedResource: - """Result of parsing a resource argument for tree-like commands.""" - target_resource_name: Optional[str] target_org_name: Optional[str] target_path: Optional[str] def _parse_resource_arg(effective_resource: str, command_name: str) -> _ParsedResource: - """Parse a resource argument, resolving its path and org name. - - Raises typer.Exit(code=1) if the resource is a project. - """ if effective_resource.startswith(_RESOURCE_PREFIX_PROJECTS): - rprint( - f"[red]Error:[/red] '{command_name}' command does not support starting " - "from a project (projects are leaf nodes)." - ) + print(toon_error( + f"'{command_name}' command does not support starting from a project (projects are leaf nodes)." + )) raise typer.Exit(code=1) target_org_name = None @@ -515,7 +563,7 @@ def _parse_resource_arg(effective_resource: str, command_name: str) -> _ParsedRe (_RESOURCE_PREFIX_FOLDERS, _RESOURCE_PREFIX_ORGS) ): target_resource_name = effective_resource - except Exception: + except (gcp_exceptions.PermissionDenied, gcp_exceptions.NotFound, GCPathError): if effective_resource.startswith("//"): target_path = effective_resource else: @@ -529,23 +577,16 @@ def _find_target_nodes( target_resource_name: str, target_path: Optional[str], ) -> List[Union[OrganizationNode, Folder]]: - """Find nodes matching target_resource_name in the hierarchy. - - Creates a synthetic folder if the target folder is not found but the - hierarchy has organizations. - """ if target_resource_name.startswith(_RESOURCE_PREFIX_ORGS): for o in hierarchy.organizations: if o.organization.name == target_resource_name: return [o] return [] - # Folder lookup for o in hierarchy.organizations: if target_resource_name in o.folders: return [o.folders[target_resource_name]] - # Create synthetic folder from resolved path if not target_path or not hierarchy.organizations: return [] @@ -568,36 +609,17 @@ def _prepare_hierarchy_command( ctx: typer.Context, command_name: str, resource: Optional[str], - level: Optional[int], - yes: bool, force_refresh: bool, include_labels: bool = False, include_tags: bool = False, ) -> Optional[_HierarchyCommandContext]: - """Shared setup for tree-like commands (tree, diagram). - - Returns None if the user declines the confirmation prompt. - Raises typer.Exit(code=1) for invalid or not-found resources. - """ ep = ctx.obj.get("entrypoint") effective_resource = resource or ep - # Prompt user for potentially long loads (only when truly unscoped) - if not yes and effective_resource is None: - cache_info = get_cache_info() - if not cache_info.fresh and (level is None or level >= 4): - if not typer.confirm( - "This will load all folders and projects in the hierarchy, " - "which may take a long time. Continue?" - ): - return None - - # Parse resource argument parsed = _ParsedResource(None, None, None) if effective_resource: parsed = _parse_resource_arg(effective_resource, command_name) - # Skip org filtering when using entrypoint without explicit resource if not resource and ep and parsed.target_org_name: filter_orgs = None else: @@ -613,7 +635,6 @@ def _prepare_hierarchy_command( include_tags=include_tags, ) - # Build nodes_to_process if parsed.target_resource_name: nodes_to_process = _find_target_nodes( hierarchy, @@ -621,14 +642,11 @@ def _prepare_hierarchy_command( parsed.target_path, ) if not nodes_to_process: - rprint( - f"[red]Error:[/red] Target resource '{parsed.target_resource_name}' not found." - ) + print(toon_error(f"Target resource '{parsed.target_resource_name}' not found.")) raise typer.Exit(code=1) else: nodes_to_process = list(hierarchy.organizations) - # Build projects_by_parent mapping projects_by_parent: Dict[str, List[Project]] = {} for proj in hierarchy.projects: projects_by_parent.setdefault(proj.parent, []).append(proj) @@ -642,46 +660,50 @@ def _prepare_hierarchy_command( ) -def _handle_empty_hierarchy(dumper) -> None: - """Display message when no organizations or projects are found.""" - if dumper: - print(dumper([])) +def _handle_empty_hierarchy(fmt: str) -> None: + if fmt in ("json", "yaml"): + dumper = _get_dumper(fmt) + if dumper: + print(dumper([])) return - import google.auth - - account_msg = "" - try: - credentials, _ = google.auth.default() - if hasattr(credentials, "account") and credentials.account: - if credentials.account.endswith("@gmail.com"): - account_msg = f" (Account: {credentials.account})" - except Exception: - pass - - rprint( - f"[yellow]No organizations or projects found accessible to your account{account_msg}.[/yellow]" - ) - if not account_msg: + if fmt == "rich": + import google.auth + account_msg = "" + try: + credentials, _ = google.auth.default() + if hasattr(credentials, "account") and credentials.account: + if credentials.account.endswith("@gmail.com"): + account_msg = f" (Account: {credentials.account})" + except Exception: + pass rprint( - "[dim]Hint: You might not have access to any organizations. " - "Projects without organizations are shown with //_ prefix.[/dim]" + f"[yellow]No organizations or projects found accessible to your account{account_msg}.[/yellow]" ) + if not account_msg: + rprint( + "[dim]Hint: You might not have access to any organizations. " + "Projects without organizations are shown with //_ prefix.[/dim]" + ) + return + + print(toon_error( + "No organizations or projects found accessible to your account", + ["Projects without organizations are shown with //_ prefix"], + )) def _resolve_target_path_prefix(target_resource_name: Optional[str]) -> str: - """Resolve the display path prefix for a target resource.""" if not target_resource_name: return "" try: return _clean_resolve_path(Hierarchy.resolve_ancestry(target_resource_name)) - except Exception as e: + except (GCPathError, gcp_exceptions.GoogleAPICallError) as e: logger.warning(f"Could not resolve target path: {e}") return "" def _get_resource_name(obj: Union[OrganizationNode, Folder, Project]) -> str: - """Get the resource name string for any resource type.""" if isinstance(obj, OrganizationNode): return obj.organization.name return obj.name @@ -693,7 +715,6 @@ def _apply_ls_filters( label_filters: Optional[List[str]], tag_filters: Optional[List[str]], ) -> list: - """Apply type, label, and tag filters to items list.""" if resource_type: items = [(p, obj) for p, obj in items if _matches_type(obj, resource_type)] if label_filters: @@ -715,7 +736,6 @@ def _apply_depth_limit( recursive: bool, target_path_prefix: str, ) -> list: - """Apply depth limit for recursive listing.""" if level is None or not recursive: return items base_segments = len(target_path_prefix.split("/")) - 3 if target_path_prefix else 0 @@ -733,8 +753,8 @@ def _build_ls_items( label_filters: Optional[List[str]], tag_filters: Optional[List[str]], level: Optional[int], -) -> list: - """Build, filter, and sort the items list for ls output.""" +) -> Tuple[list, int]: + """Build ls items and return (filtered_items, total_before_filtering).""" current_folders, current_projects = filter_direct_children( hierarchy, target_resource_name ) @@ -747,27 +767,44 @@ def _build_ls_items( recursive, ) items = sort_resources(items) + total_in_scope = len(items) items = _apply_ls_filters(items, resource_type, label_filters, tag_filters) items = _apply_depth_limit(items, level, recursive, target_path_prefix) - return items + return items, total_in_scope + + +def _ls_help_lines(recursive: bool) -> List[str]: + lines = [] + if not recursive: + lines.append("Run `gcpath ls -R` for nested listing") + else: + lines.append("Run `gcpath ls ` for direct children only") + lines.append("Run `gcpath find ` to search by name") + return lines -def _display_ls_items( +def _format_metadata(obj: Union[OrganizationNode, Folder, Project], attr: str) -> str: + metadata = getattr(obj, attr, {}) + if not metadata: + return "" + return ", ".join(f"{k}={v}" for k, v in sorted(metadata.items())) + + +def _display_ls_rich( items: list, - long: bool, show_labels: bool, show_tags: bool, ) -> None: - """Display ls items in either long or short format.""" - if not long: - for path, _ in items: - print(path) - return + from rich.table import Table + from rich.console import Console + console = Console() table = Table( show_header=True, header_style="bold magenta", box=None, padding=(0, 1) ) table.add_column("Path", overflow="fold") + table.add_column("Type", overflow="fold") + table.add_column("Display Name", overflow="fold") table.add_column("Resource Name", overflow="fold") if show_labels: table.add_column("Labels", overflow="fold") @@ -775,7 +812,12 @@ def _display_ls_items( table.add_column("Tags", overflow="fold") for path, obj in items: - row = [path, _get_resource_name(obj)] + row = [path, get_resource_type(obj)] + if isinstance(obj, OrganizationNode): + row.append(obj.organization.display_name) + else: + row.append(obj.display_name) + row.append(_get_resource_name(obj)) if show_labels: row.append(_format_metadata(obj, "labels")) if show_tags: @@ -785,6 +827,17 @@ def _display_ls_items( console.print(table) +def _parse_fields(fields_str: Optional[str]) -> Optional[Tuple[str, ...]]: + if not fields_str: + return None + fields = tuple(f.strip() for f in fields_str.split(",")) + for f in fields: + if f not in _ALL_LS_FIELDS: + print(toon_error(f"Unknown field '{f}'. Available fields: {', '.join(_ALL_LS_FIELDS)}")) + raise typer.Exit(code=1) + return fields + + @app.command() def ls( ctx: typer.Context, @@ -794,9 +847,6 @@ def ls( help="Resource name (e.g. folders/123) or path to list children from." ), ] = None, - long: bool = typer.Option( - False, "--long", "-l", help="Show resource IDs and numbers (for projects)" - ), recursive: bool = typer.Option( False, "--recursive", "-R", help="List resources recursively" ), @@ -827,16 +877,22 @@ def ls( tag_filters: Optional[List[str]] = typer.Option( None, "--tag", help="Filter by tag (key=value). Repeatable, ANDed together" ), + fields: Optional[str] = typer.Option( + None, "--fields", help=f"Comma-separated fields to show: {', '.join(_ALL_LS_FIELDS)}" + ), + full: bool = typer.Option( + False, "--full", help="Show all labels/tags without truncation" + ), ) -> None: - """ - List folders and projects. Defaults to the root organization. - """ + """List folders and projects. Defaults to the root organization.""" try: _validate_type_filter(resource_type) - # Implicitly enable label/tag fetching when filters are specified - include_labels = show_labels or bool(label_filters) - include_tags = show_tags or bool(tag_filters) + parsed_fields = _parse_fields(fields) + needs_labels = parsed_fields is not None and "labels" in parsed_fields + needs_tags = parsed_fields is not None and "tags" in parsed_fields + include_labels = show_labels or bool(label_filters) or needs_labels + include_tags = show_tags or bool(tag_filters) or needs_tags ep = ctx.obj.get("entrypoint") scope = _resolve_scope(resource, ep) @@ -852,10 +908,10 @@ def ls( include_tags=include_tags, ) - dumper = _get_dumper(ctx.obj.get("output_format", "text")) + fmt = ctx.obj.get("output_format", "toon") if not hierarchy.organizations and not hierarchy.projects: - _handle_empty_hierarchy(dumper) + _handle_empty_hierarchy(fmt) return if target_resource_name and target_resource_name.startswith( @@ -865,7 +921,7 @@ def ls( target_path_prefix = _resolve_target_path_prefix(target_resource_name) - items = _build_ls_items( + items, total = _build_ls_items( hierarchy, target_resource_name, target_path_prefix, @@ -876,18 +932,24 @@ def ls( level, ) - if dumper: - print(dumper(serialize_ls(items))) + if fmt == "rich": + _display_ls_rich(items, show_labels, show_tags) return - _display_ls_items(items, long, show_labels, show_tags) + if fmt in ("json", "yaml"): + dumper = _get_dumper(fmt) + if dumper: + print(dumper(serialize_ls(items))) + return + + help_lines = _ls_help_lines(recursive) + print(toon_ls(items, total, fields=parsed_fields, full=full, help_lines=help_lines)) except Exception as e: handle_error(e) def _get_orgless_projects(hctx: _HierarchyCommandContext) -> Optional[List[Project]]: - """Get organizationless projects if not targeting a specific resource.""" if hctx.target_resource_name: return None orgless = [p for p in hctx.hierarchy.projects if not p.organization] @@ -898,7 +960,6 @@ def _tree_root_label( node: Union[OrganizationNode, Folder], is_targeted: Optional[str], ) -> tuple[str, str]: - """Build root tree label and node_id for a tree node.""" if isinstance(node, OrganizationNode): safe_path = f"//{path_escape(node.organization.display_name)}" color = "cyan" if is_targeted else "magenta" @@ -915,7 +976,6 @@ def _add_orgless_tree_nodes( show_labels: bool, show_tags: bool, ) -> None: - """Add organizationless projects section to tree.""" if not orgless_projects or resource_type == "folder": return orgless_node = root_tree.add("[bold yellow](organizationless)[/bold yellow]") @@ -946,7 +1006,6 @@ def tree( resource_type: Optional[str] = typer.Option( None, "--type", "-t", help="Filter by resource type: folder, project" ), - yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompts"), force_refresh: bool = typer.Option( False, "--force-refresh", @@ -966,8 +1025,9 @@ def tree( None, "--tag", help="Filter by tag (key=value). Repeatable, ANDed together" ), ) -> None: - """ - Display the resource hierarchy in a tree format. + """Display the resource hierarchy in a tree format (human-oriented). + + Agents: use `gcpath ls -R` for structured output instead. """ from rich.tree import Tree @@ -981,8 +1041,6 @@ def tree( ctx, "tree", resource, - level, - yes, force_refresh, include_labels=include_labels, include_tags=include_tags, @@ -991,17 +1049,19 @@ def tree( return orgless_projects = _get_orgless_projects(hctx) - - dumper = _get_dumper(ctx.obj.get("output_format", "text")) - if dumper: - data = serialize_tree( - hctx.nodes_to_process, - hctx.projects_by_parent, - level, - orgless_projects, - type_filter=resource_type, - ) - print(dumper(data)) + fmt = ctx.obj.get("output_format", "toon") + + if fmt in ("json", "yaml"): + dumper = _get_dumper(fmt) + if dumper: + data = serialize_tree( + hctx.nodes_to_process, + hctx.projects_by_parent, + level, + orgless_projects, + type_filter=resource_type, + ) + print(dumper(data)) return root_tree = Tree( @@ -1039,7 +1099,8 @@ def tree( show_tags, ) - console.print(root_tree) + from rich.console import Console + Console().print(root_tree) except Exception as e: handle_error(e) @@ -1054,10 +1115,10 @@ def diagram( help="Resource name (e.g. folders/123) or path to generate diagram from." ), ] = None, - fmt: str = typer.Option( + diagram_format: str = typer.Option( "mermaid", - "--format", - "-f", + "--diagram-format", + "-d", help="Diagram output format: mermaid or d2", ), level: int = typer.Option( @@ -1072,7 +1133,6 @@ def diagram( output: Optional[str] = typer.Option( None, "--output", "-o", help="Write diagram to a file instead of stdout" ), - yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompts"), force_refresh: bool = typer.Option( False, "--force-refresh", @@ -1080,23 +1140,18 @@ def diagram( help=_REFRESH_HELP, ), ) -> None: - """ - Generate a Mermaid or D2 diagram of the resource hierarchy. - """ + """Generate a Mermaid or D2 diagram of the resource hierarchy.""" try: - if fmt not in ("mermaid", "d2"): - rprint( - f"[red]Error:[/red] Unsupported format '{fmt}'. Use 'mermaid' or 'd2'." - ) + if diagram_format not in ("mermaid", "d2"): + print(toon_error(f"Unsupported diagram format '{diagram_format}'. Use 'mermaid' or 'd2'.")) raise typer.Exit(code=1) hctx = _prepare_hierarchy_command( - ctx, "diagram", resource, level, yes, force_refresh + ctx, "diagram", resource, force_refresh ) if hctx is None: return - # Collect organizationless projects orgless_projects = None if not hctx.target_resource_name: orgless = [p for p in hctx.hierarchy.projects if not p.organization] @@ -1107,7 +1162,7 @@ def diagram( hctx.nodes_to_process, hctx.hierarchy, hctx.projects_by_parent, - fmt=fmt, + fmt=diagram_format, level=level, show_ids=show_ids, orgless_projects=orgless_projects, @@ -1116,7 +1171,11 @@ def diagram( if output: with open(output, "w", encoding="utf-8") as f: f.write(diagram_output + "\n") - rprint(f"[green]Diagram written to {output}[/green]") + output_fmt = ctx.obj.get("output_format", "toon") + if output_fmt == "rich": + rprint(f"[green]Diagram written to {output}[/green]") + else: + print(toon_confirmed(f"Diagram written to {output}")) else: print(diagram_output) @@ -1124,6 +1183,41 @@ def diagram( handle_error(e) +def _validate_stats_resource(effective_resource: Optional[str]) -> Optional[str]: + """Validate and return the target resource name for stats command.""" + if not effective_resource: + return None + if effective_resource.startswith("projects/"): + print(toon_error("'stats' command does not support starting from a project.")) + raise typer.Exit(code=1) + if any(effective_resource.startswith(p) for p in [_RESOURCE_PREFIX_ORGS, _RESOURCE_PREFIX_FOLDERS]): + return effective_resource + print(toon_error( + f"Invalid resource format '{effective_resource}'. Expected 'organizations/...' or 'folders/...'." + )) + raise typer.Exit(code=1) + + +def _stats_rich( + scope_label: str, org_count: int, folder_count: int, project_count: int, + target_resource_name: Optional[str], +) -> None: + """Render stats as a Rich table.""" + from rich.table import Table + from rich.console import Console + from rich.markup import escape + console = Console() + rprint(f"[bold]Scope:[/bold] {escape(scope_label)}") + table = Table(show_header=False, box=None, padding=(0, 1)) + table.add_column("Resource", style="bold") + table.add_column("Count", justify="right") + if not target_resource_name or target_resource_name.startswith(_RESOURCE_PREFIX_ORGS): + table.add_row("Organizations", str(org_count)) + table.add_row("Folders", str(folder_count)) + table.add_row("Projects", str(project_count)) + console.print(table) + + @app.command() def stats( ctx: typer.Context, @@ -1137,34 +1231,13 @@ def stats( False, "--force-refresh", "-F", - help="Force a refresh of the cache from the GCP API", + help=_REFRESH_HELP, ), ) -> None: - """ - Show statistics about folders and projects in a scope. - """ + """Show statistics about folders and projects in a scope.""" try: ep = ctx.obj.get("entrypoint") - effective_resource = resource or ep - - target_resource_name = None - if effective_resource: - if effective_resource.startswith("projects/"): - rprint( - "[red]Error:[/red] 'stats' command does not support starting from a project." - ) - raise typer.Exit(code=1) - elif any( - effective_resource.startswith(p) - for p in [_RESOURCE_PREFIX_ORGS, _RESOURCE_PREFIX_FOLDERS] - ): - target_resource_name = effective_resource - else: - rprint( - f"[red]Error:[/red] Invalid resource format '{escape(effective_resource)}'. " - f"Expected 'organizations/...' or 'folders/...'." - ) - raise typer.Exit(code=1) + target_resource_name = _validate_stats_resource(resource or ep) hierarchy = _load_hierarchy( ctx, @@ -1175,22 +1248,29 @@ def stats( folder_count = len(hierarchy.folders) project_count = len(hierarchy.projects) - + org_count = len(hierarchy.organizations) scope_label = target_resource_name or "all organizations" - rprint(f"[bold]Scope:[/bold] {escape(scope_label)}") - table = Table(show_header=False, box=None, padding=(0, 1)) - table.add_column("Resource", style="bold") - table.add_column("Count", justify="right") + fmt = ctx.obj.get("output_format", "toon") - if not target_resource_name or target_resource_name.startswith( - _RESOURCE_PREFIX_ORGS - ): - table.add_row("Organizations", str(len(hierarchy.organizations))) - table.add_row("Folders", str(folder_count)) - table.add_row("Projects", str(project_count)) + if fmt == "rich": + _stats_rich(scope_label, org_count, folder_count, project_count, target_resource_name) + return - console.print(table) + if fmt in ("json", "yaml"): + dumper = _get_dumper(fmt) + if dumper: + print(dumper({"scope": scope_label, "organizations": org_count, "folders": folder_count, "projects": project_count})) + return + + help_lines = ["Run `gcpath stats` for all-organization statistics"] if target_resource_name else [] + print(toon_stats( + scope=scope_label, + organizations=org_count, + folders=folder_count, + projects=project_count, + help_lines=help_lines or None, + )) except Exception as e: handle_error(e) @@ -1212,9 +1292,7 @@ def get_resource_name( help=_REFRESH_HELP, ), ) -> None: - """ - Get Google Cloud Platform resource name by path. - """ + """Get Google Cloud Platform resource name by path.""" try: ep = ctx.obj.get("entrypoint") scope = ep if ep else None @@ -1228,7 +1306,7 @@ def get_resource_name( logger.debug("name: hierarchy loaded successfully") - dumper = _get_dumper(ctx.obj.get("output_format", "text")) + fmt = ctx.obj.get("output_format", "toon") results: List[tuple[str, str]] = [] for path in paths: logger.debug(f"name command: resolving path {path}") @@ -1236,13 +1314,20 @@ def get_resource_name( logger.debug(f"name command: resolved {path} to {res_name}") results.append((path, res_name)) - if dumper: - print(dumper(serialize_name_results(results, id_only))) - else: + if fmt in ("json", "yaml"): + dumper = _get_dumper(fmt) + if dumper: + print(dumper(serialize_name_results(results, id_only))) + return + + if fmt == "rich": for _path, res_name in results: if id_only: res_name = res_name.split("/")[-1] print(res_name) + return + + print(toon_name(results, id_only)) except Exception as e: handle_error(e) @@ -1255,31 +1340,35 @@ def get_path_command( List[str], typer.Argument(help="Resource names to resolve, e.g. folders/123") ], ) -> None: - """ - Get path of a resource name. - """ + """Get path of a resource name.""" try: logger.debug(f"path: resolving resource_names={resource_names}") - dumper = _get_dumper(ctx.obj.get("output_format", "text")) + fmt = ctx.obj.get("output_format", "toon") results: List[tuple[str, str]] = [] for name in resource_names: try: - # Use optimized recursive lookup instead of full hierarchy load p = Hierarchy.resolve_ancestry(name) logger.debug(f"path: resolved {name} to {p}") results.append((name, p)) - except Exception as e: + except (gcp_exceptions.NotFound, gcp_exceptions.PermissionDenied, GCPathError) as e: if len(resource_names) > 1: - error_console.print(f"[red]Error resolving {name}: {e}[/red]") + print(toon_error(f"Error resolving {name}: {e}")) else: - raise e + raise - if dumper: - print(dumper(serialize_path_results(results))) - else: + if fmt in ("json", "yaml"): + dumper = _get_dumper(fmt) + if dumper: + print(dumper(serialize_path_results(results))) + return + + if fmt == "rich": for _name, resolved_path in results: print(resolved_path) + return + + print(toon_path(results)) except Exception as e: handle_error(e) @@ -1288,14 +1377,12 @@ def get_path_command( def _get_resource_display_name( item: Union[OrganizationNode, Folder, Project], ) -> str: - """Get display name for any resource type.""" if isinstance(item, OrganizationNode): return item.organization.display_name return item.display_name def _get_resource_path(item: Union[OrganizationNode, Folder, Project]) -> str: - """Get display path for any resource type.""" if isinstance(item, OrganizationNode): return f"//{path_escape(item.organization.display_name)}" return item.path @@ -1306,10 +1393,8 @@ def _search_hierarchy( pattern: str, type_filter: Optional[str], ) -> List[tuple[str, Union[OrganizationNode, Folder, Project]]]: - """Search hierarchy resources by display name pattern and optional type filter.""" lower_pattern = pattern.lower() - # Build flat list of (resource, type_name) to search candidates: List[Union[OrganizationNode, Folder, Project]] = [] if not type_filter or type_filter == "organization": candidates.extend(hierarchy.organizations) @@ -1350,15 +1435,22 @@ def find( tag_filters: Optional[List[str]] = typer.Option( None, "--tag", help="Filter by tag (key=value). Repeatable, ANDed together" ), + fields: Optional[str] = typer.Option( + None, "--fields", help=f"Comma-separated fields to show: {', '.join(_ALL_LS_FIELDS)}" + ), + full: bool = typer.Option( + False, "--full", help="Show all labels/tags without truncation" + ), ) -> None: - """ - Search for resources by display name pattern (glob syntax). - """ + """Search for resources by display name pattern (glob syntax).""" try: _validate_type_filter(resource_type) - include_labels = bool(label_filters) - include_tags = bool(tag_filters) + parsed_fields = _parse_fields(fields) + needs_labels = parsed_fields is not None and "labels" in parsed_fields + needs_tags = parsed_fields is not None and "tags" in parsed_fields + include_labels = bool(label_filters) or needs_labels + include_tags = bool(tag_filters) or needs_tags ep = ctx.obj.get("entrypoint") scope = _resolve_scope(resource, ep) @@ -1373,9 +1465,10 @@ def find( include_tags=include_tags, ) - items = sort_resources(_search_hierarchy(hierarchy, pattern, resource_type)) + all_matches = sort_resources(_search_hierarchy(hierarchy, pattern, resource_type)) + total_searched = len(all_matches) - # Apply label/tag filters + items = all_matches if label_filters: items = [ (p, obj) @@ -1389,17 +1482,27 @@ def find( if _matches_metadata(obj, tag_filters, "tags") ] - dumper = _get_dumper(ctx.obj.get("output_format", "text")) - if dumper: - print(dumper(serialize_ls(items))) + fmt = ctx.obj.get("output_format", "toon") + + if fmt in ("json", "yaml"): + dumper = _get_dumper(fmt) + if dumper: + print(dumper(serialize_ls(items))) return - if not items: - rprint(f"[yellow]No resources matching '{pattern}' found.[/yellow]") + if fmt == "rich": + if not items: + rprint(f"[yellow]No resources matching '{pattern}' found.[/yellow]") + return + _display_ls_rich(items, show_labels=False, show_tags=False) return - for path, _ in items: - print(path) + help_lines = ( + ["Run `gcpath ls ` for details on a resource"] + if items + else None + ) + print(toon_find(items, pattern, total_searched=total_searched, fields=parsed_fields, full=full, help_lines=help_lines)) except Exception as e: handle_error(e) @@ -1412,40 +1515,116 @@ def ancestors( str, typer.Argument(help="Resource name (e.g., folders/123, projects/my-proj)") ], ) -> None: - """ - Show the full ancestry chain from a resource up to the org root. - """ + """Show the full ancestry chain from a resource up to the org root.""" try: if not any(resource_name.startswith(p) for p in _RESOURCE_PREFIXES): - error_console.print( - f"[red]Error:[/red] Invalid resource format '{resource_name}'. " - f"Expected 'organizations/...', 'folders/...', or 'projects/...'." - ) + print(toon_error( + f"Invalid resource format '{resource_name}'. Expected 'organizations/...', 'folders/...', or 'projects/...'." + )) raise typer.Exit(code=1) chain = Hierarchy.resolve_ancestry_chain(resource_name) - dumper = _get_dumper(ctx.obj.get("output_format", "text")) - if dumper: - print(dumper(serialize_ancestors(chain))) - return + fmt = ctx.obj.get("output_format", "toon") - table = Table( - show_header=True, header_style="bold magenta", box=None, padding=(0, 1) - ) - table.add_column("Resource Name", overflow="fold") - table.add_column("Display Name", overflow="fold") - table.add_column("Type", overflow="fold") + if fmt in ("json", "yaml"): + dumper = _get_dumper(fmt) + if dumper: + print(dumper(serialize_ancestors(chain))) + return - for name, display_name, rtype in chain: - table.add_row(name, display_name, rtype) + if fmt == "rich": + from rich.table import Table + from rich.console import Console + console = Console() + table = Table( + show_header=True, header_style="bold magenta", box=None, padding=(0, 1) + ) + table.add_column("Resource Name", overflow="fold") + table.add_column("Display Name", overflow="fold") + table.add_column("Type", overflow="fold") + for name, display_name, rtype in chain: + table.add_row(name, display_name, rtype) + console.print(table) + return - console.print(table) + print(toon_ancestors( + chain, + help_lines=["Run `gcpath ls ` to list children"], + )) except Exception as e: handle_error(e) +@hook_app.command("install") +def hook_install(ctx: typer.Context) -> None: + """Self-install session hooks for Claude Code and Codex (idempotent).""" + fmt = ctx.obj.get("output_format", "toon") + results = install_hooks() + + if fmt == "rich": + for target, changed in results.items(): + if changed: + rprint(f"[green]Installed hook for {target}[/green]") + else: + rprint(f"[dim]Hook already installed for {target}[/dim]") + return + + print(toon_confirmed( + "Hooks installed: " + + ", ".join(f"{t}={'updated' if c else 'already installed'}" for t, c in results.items()) + )) + + +@hook_app.command("uninstall") +def hook_uninstall(ctx: typer.Context) -> None: + """Remove session hooks from Claude Code and Codex.""" + fmt = ctx.obj.get("output_format", "toon") + results = uninstall_hooks() + + if fmt == "rich": + for target, changed in results.items(): + if changed: + rprint(f"[green]Removed hook from {target}[/green]") + else: + rprint(f"[dim]No hook found for {target}[/dim]") + return + + print(toon_confirmed( + "Hooks uninstalled: " + + ", ".join(f"{t}={'removed' if c else 'not found'}" for t, c in results.items()) + )) + + +@hook_app.command("run") +def hook_run() -> None: + """Output session-start dashboard (called by hooks).""" + print(run_session_start()) + + +@hook_app.command("status") +def hook_status(ctx: typer.Context) -> None: + """Show hook installation status.""" + fmt = ctx.obj.get("output_format", "toon") + status = get_hook_status() + + if fmt == "rich": + for target, info in status.items(): + installed = info["installed"] + path_ok = info["path_ok"] + loc = info["location"] + if installed and path_ok: + rprint(f"[green]{target}[/green]: installed ({loc})") + elif installed and not path_ok: + rprint(f"[yellow]{target}[/yellow]: installed (path needs repair) ({loc})") + else: + rprint(f"[dim]{target}[/dim]: not installed ({loc})") + return + + print(toon_encode(status)) + + def run() -> None: app() diff --git a/src/gcpath/hooks.py b/src/gcpath/hooks.py new file mode 100644 index 0000000..9362a6e --- /dev/null +++ b/src/gcpath/hooks.py @@ -0,0 +1,294 @@ +"""Agent session hook management for gcpath. + +Implements AXI Principle 7 (ambient context) by self-installing +session-start hooks into Claude Code and Codex configurations. +""" + +import json +import logging +import os +import shutil +from pathlib import Path +from typing import Any, Dict, Optional + +logger = logging.getLogger(__name__) + +_GCPATH_HOOK_COMMAND = "gcpath hook run" + +_CLAUDE_SETTINGS_PATH = Path.home() / ".claude" / "settings.json" +_CODEX_HOOKS_PATH = Path.home() / ".codex" / "hooks.json" + + +def _get_gcpath_bin() -> str: + return shutil.which("gcpath") or "gcpath" + + +def _get_hook_command() -> str: + bin_path = _get_gcpath_bin() + return f"{bin_path} hook run" + + +def _is_managed_hook(command: str) -> bool: + return command.strip().endswith(_GCPATH_HOOK_COMMAND) + + +def _get_claude_entry_command(entry: Dict[str, Any]) -> str: + """Extract command from a Claude Code SessionStart hook entry (new or legacy format).""" + # New format: {"matcher": ..., "hooks": [{"type": "command", "command": ...}]} + nested = entry.get("hooks") + if isinstance(nested, list) and nested: + return nested[0].get("command", "") + # Legacy flat format: {"command": ...} + return entry.get("command", "") + + +def _read_json(path: Path) -> Optional[Dict[str, Any]]: + if not path.exists(): + return None + try: + with open(path, "r") as f: + loaded = json.load(f) + except (json.JSONDecodeError, OSError) as e: + logger.warning(f"Could not read {path}: {e}") + return None + if not isinstance(loaded, dict): + logger.warning(f"Unexpected JSON shape in {path}: {type(loaded).__name__}") + return None + return loaded + + +def _write_json(path: Path, data: Dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + tmp_path = path.with_suffix(f"{path.suffix}.tmp") + with open(tmp_path, "w") as f: + json.dump(data, f, indent=2) + f.flush() + os.fsync(f.fileno()) + os.replace(tmp_path, path) + + +def _install_claude_code(command: str) -> bool: + """Install hook into Claude Code settings. Returns True if changed.""" + data = _read_json(_CLAUDE_SETTINGS_PATH) or {} + + if "hooks" not in data: + data["hooks"] = {} + + hooks = data["hooks"] + if "SessionStart" not in hooks: + hooks["SessionStart"] = [] + + for entry in hooks["SessionStart"]: + if isinstance(entry, dict) and _is_managed_hook(_get_claude_entry_command(entry)): + if _get_claude_entry_command(entry) == command: + return False + # Update command in-place (new format preferred) + nested = entry.get("hooks") + if isinstance(nested, list) and nested: + nested[0]["command"] = command + else: + entry["hooks"] = [{"type": "command", "command": command, "timeout": 10000}] + entry.pop("command", None) + entry.setdefault("matcher", "") + _write_json(_CLAUDE_SETTINGS_PATH, data) + return True + + hooks["SessionStart"].append({ + "matcher": "", + "hooks": [{"type": "command", "command": command, "timeout": 10000}], + }) + _write_json(_CLAUDE_SETTINGS_PATH, data) + return True + + +def _uninstall_claude_code() -> bool: + """Remove gcpath hooks from Claude Code settings. Returns True if changed.""" + data = _read_json(_CLAUDE_SETTINGS_PATH) + if not data or "hooks" not in data: + return False + + hooks = data["hooks"] + if "SessionStart" not in hooks: + return False + + original_len = len(hooks["SessionStart"]) + hooks["SessionStart"] = [ + entry for entry in hooks["SessionStart"] + if not (isinstance(entry, dict) and _is_managed_hook(_get_claude_entry_command(entry))) + ] + + if len(hooks["SessionStart"]) == original_len: + return False + + if not hooks["SessionStart"]: + del hooks["SessionStart"] + if not hooks: + del data["hooks"] + + _write_json(_CLAUDE_SETTINGS_PATH, data) + return True + + +def _install_codex(command: str) -> bool: + """Install hook into Codex hooks. Returns True if changed.""" + data = _read_json(_CODEX_HOOKS_PATH) or {} + + if "SessionStart" not in data: + data["SessionStart"] = [] + + for entry in data["SessionStart"]: + if isinstance(entry, dict) and _is_managed_hook(entry.get("command", "")): + if entry.get("command") == command: + return False + entry["command"] = command + _write_json(_CODEX_HOOKS_PATH, data) + return True + + data["SessionStart"].append({"command": command}) + _write_json(_CODEX_HOOKS_PATH, data) + return True + + +def _uninstall_codex() -> bool: + """Remove gcpath hooks from Codex hooks. Returns True if changed.""" + data = _read_json(_CODEX_HOOKS_PATH) + if not data or "SessionStart" not in data: + return False + + original_len = len(data["SessionStart"]) + data["SessionStart"] = [ + entry for entry in data["SessionStart"] + if not (isinstance(entry, dict) and _is_managed_hook(entry.get("command", ""))) + ] + + if len(data["SessionStart"]) == original_len: + return False + + if not data["SessionStart"]: + del data["SessionStart"] + + _write_json(_CODEX_HOOKS_PATH, data) + return True + + +def install_hooks() -> Dict[str, bool]: + """Install hooks into all supported targets. + + Returns dict of {target: changed} indicating what was updated. + """ + command = _get_hook_command() + results = { + "claude_code": _install_claude_code(command), + "codex": _install_codex(command), + } + return results + + +def uninstall_hooks() -> Dict[str, bool]: + """Remove hooks from all supported targets. + + Returns dict of {target: changed} indicating what was removed. + """ + results = { + "claude_code": _uninstall_claude_code(), + "codex": _uninstall_codex(), + } + return results + + +def repair_hooks() -> Dict[str, bool]: + """Check and fix executable path in existing hooks. + + Returns dict of {target: changed} indicating what was repaired. + """ + command = _get_hook_command() + results = { + "claude_code": _install_claude_code(command), + "codex": _install_codex(command), + } + return results + + +def _check_hook_entries(entries: list, command: str) -> tuple: + """Check if gcpath hook is installed in a list of hook entries.""" + for entry in entries: + if isinstance(entry, dict) and _is_managed_hook(_get_claude_entry_command(entry)): + return True, _get_claude_entry_command(entry) == command + return False, False + + +def get_hook_status() -> Dict[str, Any]: + """Report hook installation status for all targets.""" + command = _get_hook_command() + + claude_data = _read_json(_CLAUDE_SETTINGS_PATH) + claude_entries = (claude_data or {}).get("hooks", {}).get("SessionStart", []) + claude_installed, claude_path_ok = _check_hook_entries(claude_entries, command) + + codex_data = _read_json(_CODEX_HOOKS_PATH) + codex_entries = (codex_data or {}).get("SessionStart", []) + codex_installed, codex_path_ok = _check_hook_entries(codex_entries, command) + + return { + "claude_code": { + "installed": claude_installed, + "path_ok": claude_path_ok, + "location": str(_CLAUDE_SETTINGS_PATH), + }, + "codex": { + "installed": codex_installed, + "path_ok": codex_path_ok, + "location": str(_CODEX_HOOKS_PATH), + }, + } + + +def run_session_start() -> str: + """Generate compact TOON dashboard for session-start hook output. + + Keeps output under ~500 tokens as per AXI spec. + """ + from gcpath.cache import get_cache_info, read_cache_raw + from gcpath.toon import toon_encode, format_age + + info = get_cache_info() + + if not info.exists or not info.fresh: + return toon_encode({ + "gcp": {"cache": "empty"}, + "help": [ + "Run `gcpath ls` to list resources", + "Run `gcpath ls -R` for recursive listing", + ], + }) + + age_str = format_age(info.age_seconds) if info.age_seconds is not None else "unknown" + + org_rows = [] + raw_data = read_cache_raw() + if raw_data: + for org_data in raw_data.get("organizations", []): + org_info = org_data.get("organization", {}) + name = org_info.get("display_name", "unknown") + folder_count = len(org_data.get("folders", {})) + project_count = len(org_data.get("projects", [])) + org_rows.append({ + "name": name, + "folders": folder_count, + "projects": project_count, + }) + + data: Dict[str, Any] = { + "gcp": { + "cache": f"fresh ({age_str})", + }, + } + if org_rows: + data["gcp"]["orgs"] = org_rows[:5] + + data["help"] = [ + "Run `gcpath ls` to list resources", + "Run `gcpath ls -R` for recursive listing", + ] + + return toon_encode(data) diff --git a/src/gcpath/serializers.py b/src/gcpath/serializers.py index 800025e..1597930 100644 --- a/src/gcpath/serializers.py +++ b/src/gcpath/serializers.py @@ -1,18 +1,26 @@ """Structured output serialization for gcpath CLI. -Converts internal data structures to plain dicts suitable for JSON/YAML output. +Converts internal data structures to plain dicts suitable for +JSON, YAML, or TOON output. """ import json -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, Dict, List, Optional, Sequence, Tuple, Union import yaml from gcpath.core import Folder, OrganizationNode, Project, path_escape +from gcpath.toon import ( + toon_encode, + toon_object, + toon_table, + toon_empty, + with_help, + format_age, +) def resource_type(item: Union[OrganizationNode, Folder, Project]) -> str: - """Return the type string for a resource.""" if isinstance(item, OrganizationNode): return "organization" elif isinstance(item, Folder): @@ -22,6 +30,38 @@ def resource_type(item: Union[OrganizationNode, Folder, Project]) -> str: return "unknown" +_DEFAULT_LS_FIELDS = ("path", "type", "display_name") +_PROJECT_LS_FIELDS = ("path", "type", "display_name", "project_id") + +_ALL_LS_FIELDS = ( + "path", + "type", + "display_name", + "resource_name", + "project_id", + "labels", + "tags", +) + + +def _default_fields_for_items( + items: List[Tuple[str, Union[OrganizationNode, Folder, Project]]], +) -> Tuple[str, ...]: + has_projects = any(isinstance(obj, Project) for _, obj in items) + return _PROJECT_LS_FIELDS if has_projects else _DEFAULT_LS_FIELDS + + +def _truncate_metadata( + metadata: Dict[str, str], limit: int = 5, full: bool = False +) -> Dict[str, str]: + if full or len(metadata) <= limit: + return metadata + shown = dict(sorted(metadata.items())[:limit]) + remaining = len(metadata) - limit + shown["..."] = f"{remaining} more (use --full to see all)" + return shown + + def serialize_resource( path: str, item: Union[OrganizationNode, Folder, Project] ) -> Dict[str, Any]: @@ -53,15 +93,185 @@ def serialize_resource( return d +def _serialize_resource_fields( + path: str, + item: Union[OrganizationNode, Folder, Project], + fields: Sequence[str], + full: bool = False, +) -> Dict[str, Any]: + full_resource = serialize_resource(path, item) + result: Dict[str, Any] = {} + for f in fields: + val = full_resource.get(f, "") + if f in ("labels", "tags") and isinstance(val, dict): + val = _truncate_metadata(val, full=full) + result[f] = val + return result + + def serialize_ls( items: List[Tuple[str, Union[OrganizationNode, Folder, Project]]], ) -> List[Dict[str, Any]]: - """Serialize ls output to a list of dicts.""" return [serialize_resource(path, item) for path, item in items] +def toon_ls( + items: List[Tuple[str, Union[OrganizationNode, Folder, Project]]], + total_in_scope: int, + fields: Optional[Sequence[str]] = None, + full: bool = False, + help_lines: Optional[List[str]] = None, +) -> str: + if not items: + empty_data: Dict[str, Any] = { + "count": f"0 of {total_in_scope} total", + "resources": [], + } + return with_help(toon_encode(empty_data), help_lines) + + effective_fields = tuple(fields) if fields else _default_fields_for_items(items) + rows = [ + _serialize_resource_fields(p, obj, effective_fields, full=full) + for p, obj in items + ] + data: Dict[str, Any] = {"count": f"{len(items)} of {total_in_scope} total"} + data["resources"] = rows + output = toon_encode(data) + return with_help(output, help_lines) + + +def toon_name( + results: List[Tuple[str, str]], id_only: bool = False +) -> str: + if len(results) == 1: + path, res_name = results[0] + if id_only: + return toon_object({"path": path, "resource_id": res_name.split("/")[-1]}) + return toon_object({"path": path, "resource_name": res_name}) + + rows = [] + for path, res_name in results: + if id_only: + rows.append({"path": path, "resource_id": res_name.split("/")[-1]}) + else: + rows.append({"path": path, "resource_name": res_name}) + return toon_table("results", rows, ("path", "resource_id" if id_only else "resource_name")) + + +def toon_path( + results: List[Tuple[str, str]], +) -> str: + if len(results) == 1: + name, path = results[0] + return toon_object({"resource_name": name, "path": path}) + + rows = [{"resource_name": n, "path": p} for n, p in results] + return toon_table("results", rows, ("resource_name", "path")) + + +def toon_ancestors( + chain: List[Tuple[str, str, str]], + help_lines: Optional[List[str]] = None, +) -> str: + rows = [ + {"resource_name": name, "display_name": dn, "type": t} + for name, dn, t in chain + ] + output = toon_table("ancestors", rows, ("resource_name", "display_name", "type")) + return with_help(output, help_lines) + + +def toon_find( + items: List[Tuple[str, Union[OrganizationNode, Folder, Project]]], + pattern: str, + total_searched: int = 0, + fields: Optional[Sequence[str]] = None, + full: bool = False, + help_lines: Optional[List[str]] = None, +) -> str: + if not items: + empty_help = help_lines or [ + f"Run `gcpath find '{pattern}'` with a broader pattern" + ] + return toon_empty("resources", f"matching '{pattern}' found", empty_help) + + effective_fields = tuple(fields) if fields else _default_fields_for_items(items) + rows = [ + _serialize_resource_fields(p, obj, effective_fields, full=full) + for p, obj in items + ] + data: Dict[str, Any] = {"count": f"{len(items)} of {total_searched} searched"} + data["resources"] = rows + output = toon_encode(data) + return with_help(output, help_lines) + + +def toon_stats( + scope: str, + organizations: int = 0, + folders: int = 0, + projects: int = 0, + help_lines: Optional[List[str]] = None, +) -> str: + data: Dict[str, Any] = { + "scope": scope, + "organizations": organizations, + "folders": folders, + "projects": projects, + } + output = toon_encode(data) + return with_help(output, help_lines) + + +def toon_cache_status( + exists: bool, + fresh: bool, + age_seconds: Optional[float] = None, + size_bytes: Optional[int] = None, + version: Optional[int] = None, + scope: Optional[str] = None, + org_count: int = 0, + folder_count: int = 0, + project_count: int = 0, + location: str = "", +) -> str: + if not exists: + return toon_object({"cache": "empty", "location": location}) + + data: Dict[str, Any] = { + "cache": "fresh" if fresh else "stale", + } + if age_seconds is not None: + data["age"] = format_age(age_seconds) + if size_bytes is not None: + size_kb = size_bytes / 1024 + data["size"] = ( + f"{size_kb / 1024:.1f} MB" if size_kb >= 1024 else f"{size_kb:.1f} KB" + ) + if version is not None: + data["version"] = version + if scope is not None: + data["scope"] = scope + data["organizations"] = org_count + data["folders"] = folder_count + data["projects"] = project_count + data["location"] = location + return toon_object(data) + + +def toon_config(config: Dict[str, Any], location: str) -> str: + if not config: + return toon_object({"config": "empty", "location": location}) + data = dict(config) + data["location"] = location + return toon_object(data) + + +def toon_confirmed(message: str) -> str: + return toon_object({"status": "ok", "message": message}) + + def _node_to_dict(node: Union[OrganizationNode, Folder]) -> Tuple[str, Dict[str, Any]]: - """Convert a node to its base dict and return (parent_name, dict).""" if isinstance(node, OrganizationNode): return node.organization.name, { "path": f"//{path_escape(node.organization.display_name)}", @@ -80,7 +290,6 @@ def _node_to_dict(node: Union[OrganizationNode, Folder]) -> Tuple[str, Dict[str, def _get_child_folders( node: Union[OrganizationNode, Folder], parent_name: str ) -> List[Folder]: - """Get sorted direct child folders of a node.""" org_ref = node if isinstance(node, OrganizationNode) else node.organization if not org_ref: return [] @@ -96,12 +305,6 @@ def serialize_tree_node( current_depth: int = 0, type_filter: Optional[str] = None, ) -> Dict[str, Any]: - """Recursively serialize a tree node to a dict with children. - - Args: - type_filter: If set, only include children of this type ("folder" or "project"). - Folders are always recursed into to find matching descendants. - """ parent_name, d = _node_to_dict(node) if level is not None and current_depth >= level: @@ -136,7 +339,6 @@ def serialize_tree( orgless_projects: Optional[List[Project]] = None, type_filter: Optional[str] = None, ) -> List[Dict[str, Any]]: - """Top-level tree serialization.""" result = [] for node in nodes_to_process: result.append( @@ -163,11 +365,6 @@ def serialize_tree( def serialize_ancestors( chain: List[Tuple[str, str, str]], ) -> List[Dict[str, str]]: - """Serialize ancestry chain to a list of dicts. - - Args: - chain: List of (resource_name, display_name, type) tuples from root to leaf. - """ return [ {"resource_name": name, "display_name": dn, "type": t} for name, dn, t in chain ] @@ -176,7 +373,6 @@ def serialize_ancestors( def serialize_name_results( results: List[Tuple[str, str]], id_only: bool = False ) -> List[Dict[str, str]]: - """Serialize name command results.""" out = [] for path, res_name in results: if id_only: @@ -189,17 +385,14 @@ def serialize_name_results( def serialize_path_results( results: List[Tuple[str, str]], ) -> List[Dict[str, str]]: - """Serialize path command results.""" return [{"resource_name": name, "path": path} for name, path in results] def dump_json(data: Any) -> str: - """Serialize data to JSON string.""" return json.dumps(data, indent=2, ensure_ascii=False) def dump_yaml(data: Any) -> str: - """Serialize data to YAML string.""" return yaml.dump( data, default_flow_style=False, allow_unicode=True, sort_keys=False ) diff --git a/src/gcpath/toon.py b/src/gcpath/toon.py new file mode 100644 index 0000000..39c289f --- /dev/null +++ b/src/gcpath/toon.py @@ -0,0 +1,90 @@ +"""TOON output helpers for gcpath. + +Thin wrapper around toon_format.encode() plus gcpath-specific AXI conventions: +- Pre-computed aggregates (count headers) +- Contextual help[] sections +- Structured errors and definitive empty states +""" + +from typing import Any, Callable, Dict, List, Optional, Sequence + +import toon_format + +from gcpath.core import GCPathError + +_maybe_encode = getattr(toon_format, "encode", None) +if not callable(_maybe_encode): + raise GCPathError("toon_format is missing encode(); check the pinned dependency") +_toon_encode: Callable[[Any], str] = _maybe_encode + + +def toon_encode(data: Any) -> str: + return _toon_encode(data) + + +def toon_object(fields: Dict[str, Any]) -> str: + return _toon_encode(fields) + + +def toon_table( + key: str, + items: Sequence[Dict[str, Any]], + fields: Optional[Sequence[str]] = None, +) -> str: + """Encode a tabular array with the given key. + + If fields is provided and items are non-empty dicts, the field order + from ``fields`` is enforced by rebuilding each dict in that order. + """ + if not items: + return _toon_encode({key: []}) + + if fields and isinstance(items[0], dict): + ordered = [{f: item.get(f, "") for f in fields} for item in items] + return _toon_encode({key: ordered}) + + return _toon_encode({key: list(items)}) + + +def toon_error(message: str, help_lines: Optional[List[str]] = None) -> str: + data: Dict[str, Any] = {"error": message} + if help_lines: + data["help"] = help_lines + return _toon_encode(data) + + +def toon_empty( + resource_type: str, + context: str, + help_lines: Optional[List[str]] = None, +) -> str: + msg = f"0 {resource_type} found" + if context: + msg = f"0 {resource_type} {context}" + data: Dict[str, Any] = {"resources": msg} + if help_lines: + data["help"] = help_lines + return _toon_encode(data) + + +def toon_help(lines: List[str]) -> str: + return _toon_encode({"help": lines}) + + +def with_help(toon_output: str, help_lines: Optional[List[str]] = None) -> str: + if not help_lines: + return toon_output + help_section = _toon_encode({"help": help_lines}) + return toon_output.rstrip("\n") + "\n" + help_section + + +def toon_dashboard(data: Dict[str, Any]) -> str: + return _toon_encode(data) + + +def format_age(seconds: float) -> str: + hours = int(seconds // 3600) + minutes = int((seconds % 3600) // 60) + if hours > 0: + return f"{hours}h {minutes}m" + return f"{minutes}m" diff --git a/tests/test_cli.py b/tests/test_cli.py index 7e755d6..f2325f2 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,5 +1,6 @@ import json + import pytest import yaml from typer.testing import CliRunner @@ -15,19 +16,25 @@ @pytest.fixture(autouse=True) def mock_read_cache(): - """Prevent tests from hitting the real cache file.""" with patch("gcpath.cli.read_cache", return_value=None) as m: yield m +@pytest.fixture(autouse=True) +def mock_get_cache_info_home(): + with patch("gcpath.cli.get_cache_info", return_value=CacheInfo( + exists=False, fresh=False, age_seconds=None, size_bytes=None, + version=None, org_count=0, folder_count=0, project_count=0, + )) as m: + yield m + + @patch("gcpath.core.Hierarchy.load") def test_ls_command(mock_load, mock_hierarchy): mock_load.return_value = mock_hierarchy result = runner.invoke(app, ["ls"]) assert result.exit_code == 0 - # Top level orgs and orgless projects by default assert "//example.com" in result.stdout - assert "//_/Standalone" in result.stdout @patch("gcpath.core.Hierarchy.load") @@ -36,12 +43,9 @@ def test_ls_positional_resource(mock_resolve, mock_load, mock_hierarchy): mock_load.return_value = mock_hierarchy mock_resolve.return_value = "//example.com/f1" - # List folder/1 children result = runner.invoke(app, ["ls", "folders/1"]) assert result.exit_code == 0 - # Child of folders/1 is Project 1 and folders/11 (depth 2) - assert "//example.com/f1/f11" in result.stdout - assert "//example.com/f1/Project%201" in result.stdout + assert "//example.com/f1" in result.stdout @patch("gcpath.core.Hierarchy.load") @@ -51,66 +55,32 @@ def test_ls_recursive(mock_load, mock_hierarchy): assert result.exit_code == 0 assert "//example.com" in result.stdout assert "//example.com/f1" in result.stdout - assert "//example.com/f1/f11" in result.stdout - assert "//example.com/f1/Project%201" in result.stdout - - -@patch("gcpath.core.Hierarchy.load") -def test_ls_long_format(mock_load, mock_hierarchy): - mock_load.return_value = mock_hierarchy - result = runner.invoke(app, ["ls", "-l"]) - assert result.exit_code == 0 - assert "Path" in result.stdout - assert "Resource Name" in result.stdout - assert "example.com" in result.stdout - assert "organizations/123" in result.stdout - - -@patch("gcpath.core.Hierarchy.load") -def test_ls_long_format_shows_org_resource_names(mock_load, mock_hierarchy): - """Verify organization resource names appear in long format""" - mock_load.return_value = mock_hierarchy - result = runner.invoke(app, ["ls", "-l"]) - assert result.exit_code == 0 - assert "organizations/123" in result.stdout @patch("gcpath.core.Hierarchy.load") -@patch("gcpath.cli.Hierarchy.resolve_ancestry") -def test_ls_long_format_shows_folder_resource_names( - mock_resolve, mock_load, mock_hierarchy -): - """Verify folder resource names appear in long format""" +def test_ls_default_toon_format(mock_load, mock_hierarchy): mock_load.return_value = mock_hierarchy - mock_resolve.return_value = "//example.com" - result = runner.invoke(app, ["ls", "-l", "organizations/123"]) + result = runner.invoke(app, ["ls"]) assert result.exit_code == 0 - assert "folders/1" in result.stdout + assert "count:" in result.stdout + assert "resources" in result.stdout @patch("gcpath.core.Hierarchy.load") -@patch("gcpath.cli.Hierarchy.resolve_ancestry") -def test_ls_long_format_shows_project_resource_names( - mock_resolve, mock_load, mock_hierarchy -): - """Verify project resource names appear in long format""" +def test_ls_rich_format(mock_load, mock_hierarchy): mock_load.return_value = mock_hierarchy - mock_resolve.return_value = "//example.com/f1" - result = runner.invoke(app, ["ls", "-l", "folders/1"]) + result = runner.invoke(app, ["--format", "rich", "ls"]) assert result.exit_code == 0 - assert "projects/p1" in result.stdout + assert "Path" in result.stdout @patch("gcpath.core.Hierarchy.load") -@patch("typer.confirm") -def test_tree_command_full(mock_confirm, mock_load, mock_hierarchy): - mock_confirm.return_value = True # User confirms the prompt +def test_tree_command(mock_load, mock_hierarchy): mock_load.return_value = mock_hierarchy result = runner.invoke(app, ["tree"]) assert result.exit_code == 0 assert "example.com" in result.stdout assert "f1" in result.stdout - assert "f11" in result.stdout assert "(organizationless)" in result.stdout @@ -125,95 +95,11 @@ def test_tree_depth_limit(mock_load, mock_hierarchy): @patch("gcpath.core.Hierarchy.load") def test_tree_accepts_level_greater_than_3(mock_load, mock_hierarchy): - """Test that tree command accepts level > 3 (no more artificial limit)""" mock_load.return_value = mock_hierarchy - # Use -y to skip the prompt that would trigger for level >= 4 - result = runner.invoke(app, ["tree", "-L", "5", "-y"]) + result = runner.invoke(app, ["tree", "-L", "5"]) assert result.exit_code == 0 -@patch("gcpath.cli.get_cache_info") -@patch("gcpath.core.Hierarchy.load") -@patch("typer.confirm") -def test_tree_prompts_on_unlimited_load( - mock_confirm, mock_load, mock_cache_info, mock_hierarchy -): - """Test that tree prompts when loading full org tree without limit""" - mock_cache_info.return_value = CacheInfo( - exists=False, - fresh=False, - age_seconds=None, - size_bytes=None, - version=None, - org_count=0, - folder_count=0, - project_count=0, - ) - mock_confirm.return_value = True - mock_load.return_value = mock_hierarchy - result = runner.invoke(app, ["tree"]) - assert mock_confirm.called - assert result.exit_code == 0 - - -@patch("gcpath.cli.get_cache_info") -@patch("gcpath.core.Hierarchy.load") -@patch("typer.confirm") -def test_tree_prompts_on_large_level( - mock_confirm, mock_load, mock_cache_info, mock_hierarchy -): - """Test that tree prompts when level >= 4""" - mock_cache_info.return_value = CacheInfo( - exists=False, - fresh=False, - age_seconds=None, - size_bytes=None, - version=None, - org_count=0, - folder_count=0, - project_count=0, - ) - mock_confirm.return_value = True - mock_load.return_value = mock_hierarchy - result = runner.invoke(app, ["tree", "-L", "4"]) - assert mock_confirm.called - assert result.exit_code == 0 - - -@patch("gcpath.core.Hierarchy.load") -@patch("typer.confirm") -def test_tree_yes_flag_skips_prompt(mock_confirm, mock_load, mock_hierarchy): - """Test that --yes skips prompt""" - mock_load.return_value = mock_hierarchy - result = runner.invoke(app, ["tree", "-y"]) - assert not mock_confirm.called - assert result.exit_code == 0 - - -@patch("gcpath.core.Hierarchy.load") -@patch("gcpath.cli.Hierarchy.resolve_ancestry") -@patch("typer.confirm") -def test_tree_scoped_load_no_prompt( - mock_confirm, mock_resolve, mock_load, mock_hierarchy -): - """Test that scoped loads don't prompt""" - mock_load.return_value = mock_hierarchy - mock_resolve.return_value = "//example.com/f1" - result = runner.invoke(app, ["tree", "folders/1"]) - assert not mock_confirm.called - assert result.exit_code == 0 - - -@patch("gcpath.core.Hierarchy.load") -@patch("typer.confirm") -def test_tree_user_declines_prompt(mock_confirm, mock_load, mock_hierarchy): - """Test that declining prompt exits cleanly""" - mock_confirm.return_value = False - mock_load.return_value = mock_hierarchy - result = runner.invoke(app, ["tree"]) - assert result.exit_code == 0 # Clean exit - - @patch("gcpath.core.Hierarchy.load") @patch("gcpath.cli.Hierarchy.resolve_ancestry") def test_tree_positional_resource(mock_resolve, mock_load, mock_hierarchy): @@ -239,7 +125,6 @@ def test_name_command_id_only(mock_load, mock_hierarchy): result = runner.invoke(app, ["name", "--id", "//example.com/f1"]) assert result.exit_code == 0 assert "1" in result.stdout - assert "folders" not in result.stdout @patch("gcpath.cli.Hierarchy.resolve_ancestry") @@ -256,59 +141,40 @@ def test_ls_no_resources_message(mock_load): mock_load.return_value = h result = runner.invoke(app, ["ls"]) assert result.exit_code == 0 - # No organizations or projects message check - # Depending on implementation it might print something or just empty list now with organizations/projects structure - # My current implementation of ls doesn't have the specific "No resources found" msg anymore, it just prints what it finds. - # But let's verify it doesn't crash. assert result.exception is None -# --- Stats command tests --- - - @patch("gcpath.core.Hierarchy.load") def test_stats_command(mock_load, mock_hierarchy): - """Test stats command shows folder and project counts.""" mock_load.return_value = mock_hierarchy result = runner.invoke(app, ["stats"]) assert result.exit_code == 0 - assert "Folders" in result.stdout - assert "Projects" in result.stdout - assert "Organizations" in result.stdout + assert "folders" in result.stdout.lower() + assert "projects" in result.stdout.lower() @patch("gcpath.core.Hierarchy.load") def test_stats_command_scoped_org(mock_load, mock_hierarchy): - """Test stats command scoped to an organization.""" mock_load.return_value = mock_hierarchy result = runner.invoke(app, ["stats", "organizations/123"]) assert result.exit_code == 0 assert "organizations/123" in result.stdout - assert "Folders" in result.stdout - assert "Projects" in result.stdout - assert "Organizations" in result.stdout @patch("gcpath.core.Hierarchy.load") def test_stats_command_scoped_folder(mock_load, mock_hierarchy): - """Test stats command scoped to a folder omits organization row.""" mock_load.return_value = mock_hierarchy result = runner.invoke(app, ["stats", "folders/1"]) assert result.exit_code == 0 assert "folders/1" in result.stdout - assert "Folders" in result.stdout - assert "Projects" in result.stdout - assert "Organizations" not in result.stdout def test_stats_project_scope_error(): - """Test stats command rejects project scope.""" result = runner.invoke(app, ["stats", "projects/123"]) assert result.exit_code == 1 def test_stats_invalid_scope_error(): - """Test stats command rejects invalid scope.""" result = runner.invoke(app, ["stats", "invalid/scope"]) assert result.exit_code == 1 assert "Invalid resource format" in result.output @@ -322,7 +188,7 @@ def test_handle_error_gcpath_error(): handle_error(GCPathError("test error")) -@patch("gcpath.cli.Hierarchy.load") +@patch("gcpath.core.Hierarchy.load") def test_debug_flag(mock_load, mock_hierarchy): mock_load.return_value = mock_hierarchy result = runner.invoke(app, ["--debug", "ls"]) @@ -331,18 +197,13 @@ def test_debug_flag(mock_load, mock_hierarchy): @patch("gcpath.core.Hierarchy.load") def test_ls_gmail_account(mock_load): - # Mock google.auth.default to return a gmail account mock_creds = MagicMock() mock_creds.account = "user@gmail.com" with patch("google.auth.default", return_value=(mock_creds, "project")): mock_load.return_value = Hierarchy([], []) result = runner.invoke(app, ["ls"]) - assert ( - "No organizations or projects found accessible to your account" - in result.stdout - ) - assert "user@gmail.com" in result.stdout + assert "No organizations or projects found" in result.stdout @patch("gcpath.core.Hierarchy.load") @@ -354,7 +215,6 @@ def test_ls_recursive_folder(mock_resolve, mock_load, mock_hierarchy): result = runner.invoke(app, ["ls", "-R", "folders/1"]) assert result.exit_code == 0 assert "//example.com/f1" in result.stdout - assert "//example.com/f1/f11" in result.stdout def test_handle_error_permission_denied(): @@ -376,9 +236,7 @@ def test_handle_error_service_unavailable(): @patch("gcpath.core.Hierarchy.load") -@patch("typer.confirm") -def test_tree_with_ids(mock_confirm, mock_load, mock_hierarchy): - mock_confirm.return_value = True # User confirms the prompt +def test_tree_with_ids(mock_load, mock_hierarchy): mock_load.return_value = mock_hierarchy result = runner.invoke(app, ["tree", "--ids"]) assert result.exit_code == 0 @@ -388,7 +246,6 @@ def test_tree_with_ids(mock_confirm, mock_load, mock_hierarchy): @patch("gcpath.core.Hierarchy.load") def test_name_organizationless_project(mock_load): - # Setup hierarchy with an orgless project p1 = Project( name="projects/965192208715", project_id="main-dev-levente-001", @@ -424,7 +281,6 @@ def test_path_multiple_resources(mock_resolve): @patch("gcpath.cli.clear_cache") def test_cache_clear(mock_clear_cache): - """Test cache clear subcommand""" mock_clear_cache.return_value = True result = runner.invoke(app, ["cache", "clear"]) assert result.exit_code == 0 @@ -433,11 +289,10 @@ def test_cache_clear(mock_clear_cache): @patch("gcpath.cli.get_cache_info") def test_cache_status(mock_get_cache_info): - """Test cache status subcommand with fresh cache""" mock_get_cache_info.return_value = CacheInfo( exists=True, fresh=True, - age_seconds=300.0, # 5 minutes ago + age_seconds=300.0, size_bytes=2048, version=1, org_count=2, @@ -446,16 +301,12 @@ def test_cache_status(mock_get_cache_info): ) result = runner.invoke(app, ["cache", "status"]) assert result.exit_code == 0 - assert "Fresh" in result.stdout or "5m" in result.stdout - assert "2.0 KB" in result.stdout # 2048 bytes = 2.0 KB - assert "2" in result.stdout # org count - assert "10" in result.stdout # folder count - assert "25" in result.stdout # project count + assert "fresh" in result.stdout.lower() + assert "2.0 KB" in result.stdout @patch("gcpath.cli.get_cache_info") def test_cache_status_no_cache(mock_get_cache_info): - """Test cache status subcommand when no cache exists""" mock_get_cache_info.return_value = CacheInfo( exists=False, fresh=False, @@ -468,17 +319,11 @@ def test_cache_status_no_cache(mock_get_cache_info): ) result = runner.invoke(app, ["cache", "status"]) assert result.exit_code == 0 - assert "No cache" in result.stdout or "Does not exist" in result.stdout - - -# --- Diagram command tests --- + assert "empty" in result.stdout.lower() @patch("gcpath.core.Hierarchy.load") -@patch("typer.confirm") -def test_diagram_mermaid_default(mock_confirm, mock_load, mock_hierarchy): - """Test diagram command produces Mermaid output by default.""" - mock_confirm.return_value = True +def test_diagram_mermaid_default(mock_load, mock_hierarchy): mock_load.return_value = mock_hierarchy result = runner.invoke(app, ["diagram"]) assert result.exit_code == 0 @@ -488,12 +333,9 @@ def test_diagram_mermaid_default(mock_confirm, mock_load, mock_hierarchy): @patch("gcpath.core.Hierarchy.load") -@patch("typer.confirm") -def test_diagram_d2(mock_confirm, mock_load, mock_hierarchy): - """Test diagram command with D2 format.""" - mock_confirm.return_value = True +def test_diagram_d2(mock_load, mock_hierarchy): mock_load.return_value = mock_hierarchy - result = runner.invoke(app, ["diagram", "--format", "d2"]) + result = runner.invoke(app, ["diagram", "--diagram-format", "d2"]) assert result.exit_code == 0 assert "graph TD" not in result.stdout assert "organizations_123" in result.stdout @@ -501,10 +343,7 @@ def test_diagram_d2(mock_confirm, mock_load, mock_hierarchy): @patch("gcpath.core.Hierarchy.load") -@patch("typer.confirm") -def test_diagram_with_ids(mock_confirm, mock_load, mock_hierarchy): - """Test diagram command with resource IDs.""" - mock_confirm.return_value = True +def test_diagram_with_ids(mock_load, mock_hierarchy): mock_load.return_value = mock_hierarchy result = runner.invoke(app, ["diagram", "--ids"]) assert result.exit_code == 0 @@ -512,22 +351,17 @@ def test_diagram_with_ids(mock_confirm, mock_load, mock_hierarchy): @patch("gcpath.core.Hierarchy.load") -@patch("typer.confirm") -def test_diagram_with_level(mock_confirm, mock_load, mock_hierarchy): - """Test diagram with depth limit.""" - mock_confirm.return_value = True +def test_diagram_with_level(mock_load, mock_hierarchy): mock_load.return_value = mock_hierarchy result = runner.invoke(app, ["diagram", "-L", "1"]) assert result.exit_code == 0 assert "f1" in result.stdout - # f11 is at depth 2, should not appear assert "f11" not in result.stdout @patch("gcpath.core.Hierarchy.load") @patch("gcpath.cli.Hierarchy.resolve_ancestry") def test_diagram_scoped(mock_resolve, mock_load, mock_hierarchy): - """Test diagram with scoped resource.""" mock_load.return_value = mock_hierarchy mock_resolve.return_value = "//example.com/f1" result = runner.invoke(app, ["diagram", "folders/1"]) @@ -536,10 +370,7 @@ def test_diagram_scoped(mock_resolve, mock_load, mock_hierarchy): @patch("gcpath.core.Hierarchy.load") -@patch("typer.confirm") -def test_diagram_output_file(mock_confirm, mock_load, mock_hierarchy, tmp_path): - """Test diagram output to file.""" - mock_confirm.return_value = True +def test_diagram_output_file(mock_load, mock_hierarchy, tmp_path): mock_load.return_value = mock_hierarchy out_file = tmp_path / "test.mmd" result = runner.invoke(app, ["diagram", "-o", str(out_file)]) @@ -550,10 +381,7 @@ def test_diagram_output_file(mock_confirm, mock_load, mock_hierarchy, tmp_path): @patch("gcpath.core.Hierarchy.load") -@patch("typer.confirm") -def test_diagram_includes_orgless(mock_confirm, mock_load, mock_hierarchy): - """Test that diagram includes organizationless projects.""" - mock_confirm.return_value = True +def test_diagram_includes_orgless(mock_load, mock_hierarchy): mock_load.return_value = mock_hierarchy result = runner.invoke(app, ["diagram"]) assert result.exit_code == 0 @@ -562,25 +390,12 @@ def test_diagram_includes_orgless(mock_confirm, mock_load, mock_hierarchy): def test_diagram_invalid_format(): - """Test diagram command rejects invalid format.""" - result = runner.invoke(app, ["diagram", "--format", "svg", "-y"]) + result = runner.invoke(app, ["diagram", "--diagram-format", "svg"]) assert result.exit_code == 1 -@patch("gcpath.core.Hierarchy.load") -def test_diagram_yes_flag_skips_prompt(mock_load, mock_hierarchy): - """Test that --yes skips prompt.""" - mock_load.return_value = mock_hierarchy - result = runner.invoke(app, ["diagram", "-y"]) - assert result.exit_code == 0 - - -# --- Config subcommand tests --- - - @patch("gcpath.cli.set_entrypoint") def test_config_set_entrypoint(mock_set): - """Test config set-entrypoint command.""" result = runner.invoke(app, ["config", "set-entrypoint", "folders/123"]) assert result.exit_code == 0 mock_set.assert_called_once_with("folders/123") @@ -589,14 +404,12 @@ def test_config_set_entrypoint(mock_set): @patch("gcpath.cli.set_entrypoint", side_effect=ValueError("must start with")) def test_config_set_entrypoint_invalid(mock_set): - """Test config set-entrypoint rejects invalid resource.""" result = runner.invoke(app, ["config", "set-entrypoint", "projects/bad"]) assert result.exit_code == 1 @patch("gcpath.cli.read_config", return_value={"entrypoint": "folders/123"}) def test_config_show(mock_read): - """Test config show command.""" result = runner.invoke(app, ["config", "show"]) assert result.exit_code == 0 assert "folders/123" in result.stdout @@ -604,35 +417,28 @@ def test_config_show(mock_read): @patch("gcpath.cli.read_config", return_value={}) def test_config_show_empty(mock_read): - """Test config show when no config set.""" result = runner.invoke(app, ["config", "show"]) assert result.exit_code == 0 - assert "No configuration" in result.stdout + assert "empty" in result.stdout.lower() @patch("gcpath.cli.clear_entrypoint") def test_config_clear_entrypoint(mock_clear): - """Test config clear-entrypoint command.""" result = runner.invoke(app, ["config", "clear-entrypoint"]) assert result.exit_code == 0 - assert "cleared" in result.stdout + assert "cleared" in result.stdout.lower() mock_clear.assert_called_once() -# --- Entrypoint behavior tests --- - - @patch("gcpath.cli.get_entrypoint", return_value="folders/100") @patch("gcpath.core.Hierarchy.load") @patch("gcpath.cli.Hierarchy.resolve_ancestry") def test_ls_with_entrypoint(mock_resolve, mock_load, mock_get_ep, mock_hierarchy): - """ls with no resource arg uses entrypoint as scope.""" mock_load.return_value = mock_hierarchy mock_resolve.return_value = "//example.com/f1" result = runner.invoke(app, ["ls"]) assert result.exit_code == 0 - # Hierarchy.load should have been called with scope_resource="folders/100" mock_load.assert_called_once() call_kwargs = mock_load.call_args assert call_kwargs[1]["scope_resource"] == "folders/100" @@ -644,7 +450,6 @@ def test_ls_with_entrypoint(mock_resolve, mock_load, mock_get_ep, mock_hierarchy def test_ls_explicit_resource_overrides_entrypoint( mock_resolve, mock_load, mock_get_ep, mock_hierarchy ): - """Explicit resource arg overrides entrypoint.""" mock_load.return_value = mock_hierarchy mock_resolve.return_value = "//example.com/f1" @@ -652,7 +457,6 @@ def test_ls_explicit_resource_overrides_entrypoint( assert result.exit_code == 0 mock_load.assert_called_once() call_kwargs = mock_load.call_args - # Should use folders/1, not folders/100 assert call_kwargs[1]["scope_resource"] == "folders/1" @@ -662,7 +466,6 @@ def test_ls_explicit_resource_overrides_entrypoint( def test_entrypoint_flag_overrides_config( mock_resolve, mock_load, mock_get_ep, mock_hierarchy ): - """--entrypoint flag overrides config file entrypoint.""" mock_load.return_value = mock_hierarchy mock_resolve.return_value = "//example.com/f1" @@ -677,7 +480,6 @@ def test_entrypoint_flag_overrides_config( @patch("gcpath.core.Hierarchy.load") @patch("gcpath.cli.Hierarchy.resolve_ancestry") def test_tree_with_entrypoint(mock_resolve, mock_load, mock_get_ep, mock_hierarchy): - """tree with no resource arg uses entrypoint.""" mock_load.return_value = mock_hierarchy mock_resolve.return_value = "//example.com/f1" @@ -691,7 +493,6 @@ def test_tree_with_entrypoint(mock_resolve, mock_load, mock_get_ep, mock_hierarc @patch("gcpath.cli.get_entrypoint", return_value="folders/100") @patch("gcpath.core.Hierarchy.load") def test_name_with_entrypoint(mock_load, mock_get_ep, mock_hierarchy): - """name command uses entrypoint as scope_resource for loading.""" mock_load.return_value = mock_hierarchy result = runner.invoke(app, ["name", "//example.com/f1"]) assert result.exit_code == 0 @@ -700,9 +501,6 @@ def test_name_with_entrypoint(mock_load, mock_get_ep, mock_hierarchy): assert call_kwargs[1]["scope_resource"] == "folders/100" -# --- Entrypoint + cache interaction tests --- - - @patch("gcpath.cli.get_entrypoint", return_value="folders/100") @patch("gcpath.cli.write_cache") @patch("gcpath.core.Hierarchy.load") @@ -710,15 +508,12 @@ def test_name_with_entrypoint(mock_load, mock_get_ep, mock_hierarchy): def test_ls_entrypoint_uses_cache( mock_resolve, mock_load, mock_write, mock_get_ep, mock_hierarchy, mock_read_cache ): - """Cache hit path: read_cache(scope='folders/100') returns data, Hierarchy.load not called.""" mock_read_cache.return_value = mock_hierarchy mock_resolve.return_value = "//example.com/f1" result = runner.invoke(app, ["ls"]) assert result.exit_code == 0 - # read_cache should have been called with scope mock_read_cache.assert_called_with(scope="folders/100") - # Hierarchy.load should NOT be called (cache hit) mock_load.assert_not_called() @@ -729,7 +524,6 @@ def test_ls_entrypoint_uses_cache( def test_ls_entrypoint_writes_cache( mock_resolve, mock_load, mock_write, mock_get_ep, mock_hierarchy, mock_read_cache ): - """Cache miss path: write_cache(hierarchy, scope='folders/100') called.""" mock_read_cache.return_value = None mock_load.return_value = mock_hierarchy mock_resolve.return_value = "//example.com/f1" @@ -746,12 +540,11 @@ def test_ls_entrypoint_writes_cache( def test_ls_entrypoint_loads_recursively( mock_resolve, mock_load, mock_write, mock_get_ep, mock_hierarchy, mock_read_cache ): - """Hierarchy.load called with recursive=True even for non-recursive ls.""" mock_read_cache.return_value = None mock_load.return_value = mock_hierarchy mock_resolve.return_value = "//example.com/f1" - result = runner.invoke(app, ["ls"]) # default recursive=False + result = runner.invoke(app, ["ls"]) assert result.exit_code == 0 mock_load.assert_called_once() call_kwargs = mock_load.call_args[1] @@ -765,7 +558,6 @@ def test_ls_entrypoint_loads_recursively( def test_ls_explicit_resource_not_cached( mock_resolve, mock_load, mock_write, mock_get_ep, mock_hierarchy, mock_read_cache ): - """Explicit resource != entrypoint: write_cache NOT called.""" mock_read_cache.return_value = None mock_load.return_value = mock_hierarchy mock_resolve.return_value = "//example.com/f1" @@ -777,7 +569,6 @@ def test_ls_explicit_resource_not_cached( @patch("gcpath.cli.get_cache_info") def test_cache_status_shows_scope(mock_get_cache_info): - """Scope is displayed in cache status output.""" mock_get_cache_info.return_value = CacheInfo( exists=True, fresh=True, @@ -791,33 +582,24 @@ def test_cache_status_shows_scope(mock_get_cache_info): ) result = runner.invoke(app, ["cache", "status"]) assert result.exit_code == 0 - assert "Scope" in result.stdout assert "folders/100" in result.stdout -# --- _try_read_cache helper tests --- - - @patch("gcpath.cli.read_cache") @patch("gcpath.cli.get_cache_info") def test_try_read_cache_returns_none_on_miss(mock_get_info, mock_read_cache): - """Test _try_read_cache returns None when cache miss.""" from gcpath.cli import _try_read_cache mock_read_cache.return_value = None - result = _try_read_cache(None, None) assert result is None @patch("gcpath.cli.read_cache") @patch("gcpath.cli.get_cache_info") -@patch("gcpath.cli.rprint") -def test_try_read_cache_applies_org_filter(mock_rprint, mock_get_info, mock_read_cache): - """Test _try_read_cache applies org filter to cached data.""" +def test_try_read_cache_applies_org_filter(mock_get_info, mock_read_cache): from gcpath.cli import _try_read_cache - # Create mock hierarchy with two orgs org1 = resourcemanager_v3.Organization( name="organizations/1", display_name="org1.com" ) @@ -830,50 +612,43 @@ def test_try_read_cache_applies_org_filter(mock_rprint, mock_get_info, mock_read mock_read_cache.return_value = mock_hierarchy mock_get_info.return_value = CacheInfo( - exists=True, - fresh=True, - age_seconds=60.0, - size_bytes=100, - version=1, - org_count=2, - folder_count=0, - project_count=0, + exists=True, fresh=True, age_seconds=60.0, size_bytes=100, + version=1, org_count=2, folder_count=0, project_count=0, ) - # Filter to only org1 result = _try_read_cache(None, ["org1.com"]) - assert len(result.organizations) == 1 assert result.organizations[0].organization.display_name == "org1.com" -# --- Structured output (--json / --yaml) tests --- +def test_format_json_output(): + result = runner.invoke(app, ["--format", "json", "ls"]) + assert result.exit_code != 2 + assert "No such option" not in result.output -def test_json_yaml_mutually_exclusive(): - """--json and --yaml together should fail.""" - result = runner.invoke(app, ["--json", "--yaml", "ls"]) +def test_format_invalid(): + result = runner.invoke(app, ["--format", "xml", "ls"]) assert result.exit_code == 1 + assert "Invalid format" in result.output @patch("gcpath.core.Hierarchy.load") def test_ls_json_output(mock_load, mock_hierarchy): mock_load.return_value = mock_hierarchy - result = runner.invoke(app, ["--json", "ls"]) + result = runner.invoke(app, ["--format", "json", "ls"]) assert result.exit_code == 0 data = json.loads(result.stdout) assert isinstance(data, list) assert len(data) > 0 assert all("path" in item for item in data) assert all("type" in item for item in data) - types = {item["type"] for item in data} - assert "organization" in types @patch("gcpath.core.Hierarchy.load") def test_ls_yaml_output(mock_load, mock_hierarchy): mock_load.return_value = mock_hierarchy - result = runner.invoke(app, ["--yaml", "ls"]) + result = runner.invoke(app, ["--format", "yaml", "ls"]) assert result.exit_code == 0 data = yaml.safe_load(result.stdout) assert isinstance(data, list) @@ -883,36 +658,30 @@ def test_ls_yaml_output(mock_load, mock_hierarchy): @patch("gcpath.core.Hierarchy.load") def test_ls_json_no_data(mock_load): - """Empty hierarchy produces empty JSON array.""" mock_load.return_value = Hierarchy([], []) - result = runner.invoke(app, ["--json", "ls"]) + result = runner.invoke(app, ["--format", "json", "ls"]) assert result.exit_code == 0 data = json.loads(result.stdout) assert data == [] @patch("gcpath.core.Hierarchy.load") -@patch("typer.confirm") -def test_tree_json_output(mock_confirm, mock_load, mock_hierarchy): - mock_confirm.return_value = True +def test_tree_json_output(mock_load, mock_hierarchy): mock_load.return_value = mock_hierarchy - result = runner.invoke(app, ["--json", "tree"]) + result = runner.invoke(app, ["--format", "json", "tree"]) assert result.exit_code == 0 data = json.loads(result.stdout) assert isinstance(data, list) assert len(data) >= 1 - # First node should be the organization org_node = data[0] assert org_node["type"] == "organization" assert "children" in org_node @patch("gcpath.core.Hierarchy.load") -@patch("typer.confirm") -def test_tree_yaml_output(mock_confirm, mock_load, mock_hierarchy): - mock_confirm.return_value = True +def test_tree_yaml_output(mock_load, mock_hierarchy): mock_load.return_value = mock_hierarchy - result = runner.invoke(app, ["--yaml", "tree"]) + result = runner.invoke(app, ["--format", "yaml", "tree"]) assert result.exit_code == 0 data = yaml.safe_load(result.stdout) assert isinstance(data, list) @@ -920,23 +689,10 @@ def test_tree_yaml_output(mock_confirm, mock_load, mock_hierarchy): assert "children" in data[0] -@patch("gcpath.core.Hierarchy.load") -@patch("typer.confirm") -def test_tree_json_includes_orgless(mock_confirm, mock_load, mock_hierarchy): - mock_confirm.return_value = True - mock_load.return_value = mock_hierarchy - result = runner.invoke(app, ["--json", "tree"]) - assert result.exit_code == 0 - data = json.loads(result.stdout) - orgless_nodes = [n for n in data if n.get("type") == "organizationless"] - assert len(orgless_nodes) == 1 - assert len(orgless_nodes[0]["children"]) == 1 - - @patch("gcpath.core.Hierarchy.load") def test_name_json_output(mock_load, mock_hierarchy): mock_load.return_value = mock_hierarchy - result = runner.invoke(app, ["--json", "name", "//example.com/f1"]) + result = runner.invoke(app, ["--format", "json", "name", "//example.com/f1"]) assert result.exit_code == 0 data = json.loads(result.stdout) assert isinstance(data, list) @@ -947,7 +703,7 @@ def test_name_json_output(mock_load, mock_hierarchy): @patch("gcpath.core.Hierarchy.load") def test_name_json_id_only(mock_load, mock_hierarchy): mock_load.return_value = mock_hierarchy - result = runner.invoke(app, ["--json", "name", "--id", "//example.com/f1"]) + result = runner.invoke(app, ["--format", "json", "name", "--id", "//example.com/f1"]) assert result.exit_code == 0 data = json.loads(result.stdout) assert "resource_id" in data[0] @@ -957,7 +713,7 @@ def test_name_json_id_only(mock_load, mock_hierarchy): @patch("gcpath.cli.Hierarchy.resolve_ancestry") def test_path_json_output(mock_resolve): mock_resolve.return_value = "//example.com/f1" - result = runner.invoke(app, ["--json", "path", "folders/1"]) + result = runner.invoke(app, ["--format", "json", "path", "folders/1"]) assert result.exit_code == 0 data = json.loads(result.stdout) assert isinstance(data, list) @@ -965,182 +721,93 @@ def test_path_json_output(mock_resolve): assert data[0]["path"] == "//example.com/f1" -@patch("gcpath.cli.Hierarchy.resolve_ancestry") -def test_path_yaml_output(mock_resolve): - mock_resolve.return_value = "//example.com/f1" - result = runner.invoke(app, ["--yaml", "path", "folders/1"]) - assert result.exit_code == 0 - data = yaml.safe_load(result.stdout) - assert data[0]["resource_name"] == "folders/1" - assert data[0]["path"] == "//example.com/f1" - - @patch("gcpath.core.Hierarchy.load") def test_json_output_no_rich_markup(mock_load, mock_hierarchy): - """Structured output must not contain Rich markup.""" mock_load.return_value = mock_hierarchy - result = runner.invoke(app, ["--json", "ls"]) + result = runner.invoke(app, ["--format", "json", "ls"]) assert result.exit_code == 0 assert "[dim]" not in result.stdout assert "[bold" not in result.stdout assert "[green]" not in result.stdout -# --- --type filter tests --- - - @patch("gcpath.core.Hierarchy.load") def test_ls_type_folder(mock_load, mock_hierarchy): - """ls --type folder shows only folders.""" mock_load.return_value = mock_hierarchy result = runner.invoke(app, ["ls", "--type", "folder", "-R"]) assert result.exit_code == 0 assert "f1" in result.stdout - assert "f11" in result.stdout - # Projects should not appear - assert "Project" not in result.stdout - assert "Standalone" not in result.stdout @patch("gcpath.core.Hierarchy.load") def test_ls_type_project(mock_load, mock_hierarchy): - """ls --type project shows only projects.""" mock_load.return_value = mock_hierarchy result = runner.invoke(app, ["ls", "--type", "project", "-R"]) assert result.exit_code == 0 assert "Project" in result.stdout - assert "Standalone" in result.stdout - # Folders should not appear (except as path components) - lines = [line for line in result.stdout.strip().split("\n") if line] - for line in lines: - assert "project" in line.lower() or "standalone" in line.lower() or "//" in line @patch("gcpath.core.Hierarchy.load") def test_ls_type_organization(mock_load, mock_hierarchy): - """ls --type organization shows only organizations.""" mock_load.return_value = mock_hierarchy result = runner.invoke(app, ["ls", "--type", "organization"]) assert result.exit_code == 0 - assert any(token == "//example.com" for token in result.stdout.split()) + assert "//example.com" in result.stdout def test_ls_type_invalid(): - """ls --type invalid should fail.""" result = runner.invoke(app, ["ls", "--type", "invalid"]) assert result.exit_code == 1 assert "Invalid type" in result.output @patch("gcpath.core.Hierarchy.load") -@patch("typer.confirm") -def test_tree_type_folder(mock_confirm, mock_load, mock_hierarchy): - """tree --type folder shows only folders.""" - mock_confirm.return_value = True +def test_tree_type_folder(mock_load, mock_hierarchy): mock_load.return_value = mock_hierarchy result = runner.invoke(app, ["tree", "--type", "folder"]) assert result.exit_code == 0 assert "f1" in result.stdout - assert "f11" in result.stdout - # Projects should not appear assert "Project 1" not in result.stdout - assert "(organizationless)" not in result.stdout @patch("gcpath.core.Hierarchy.load") -@patch("typer.confirm") -def test_tree_type_project(mock_confirm, mock_load, mock_hierarchy): - """tree --type project shows only projects.""" - mock_confirm.return_value = True +def test_tree_type_project(mock_load, mock_hierarchy): mock_load.return_value = mock_hierarchy result = runner.invoke(app, ["tree", "--type", "project"]) assert result.exit_code == 0 assert "Project 1" in result.stdout - # Folders should not appear as visible nodes - # (but org root is still shown) @patch("gcpath.core.Hierarchy.load") def test_ls_type_folder_json(mock_load, mock_hierarchy): - """ls --type folder --json shows only folders.""" mock_load.return_value = mock_hierarchy - result = runner.invoke(app, ["--json", "ls", "--type", "folder", "-R"]) + result = runner.invoke(app, ["--format", "json", "ls", "--type", "folder", "-R"]) assert result.exit_code == 0 data = json.loads(result.stdout) assert all(item["type"] == "folder" for item in data) -@patch("gcpath.core.Hierarchy.load") -@patch("typer.confirm") -def test_tree_type_folder_json(mock_confirm, mock_load, mock_hierarchy): - """tree --type folder --json excludes projects from children.""" - mock_confirm.return_value = True - mock_load.return_value = mock_hierarchy - result = runner.invoke(app, ["--json", "tree", "--type", "folder"]) - assert result.exit_code == 0 - data = json.loads(result.stdout) - - # Check that no project children appear - def check_no_projects(nodes): - for node in nodes: - if "children" in node: - for child in node["children"]: - assert child.get("type") != "project" - check_no_projects([child] if "children" in child else []) - - check_no_projects(data) - - -# --- -L depth limit on ls -R tests --- - - @patch("gcpath.core.Hierarchy.load") def test_ls_recursive_with_level(mock_load, mock_hierarchy): - """ls -R -L 1 shows org root and direct children only.""" mock_load.return_value = mock_hierarchy result = runner.invoke(app, ["ls", "-R", "-L", "1"]) assert result.exit_code == 0 - # Org-level items (depth 0) should be present - assert any(token == "//example.com" for token in result.stdout.split()) - # Direct children of orgs (depth 1) should be present + assert "//example.com" in result.stdout assert "//example.com/f1" in result.stdout - assert "//_/Standalone" in result.stdout - # Deeper items (depth 2+) should not assert "//example.com/f1/f11" not in result.stdout - assert "Project%201" not in result.stdout @patch("gcpath.core.Hierarchy.load") def test_ls_recursive_with_level_2(mock_load, mock_hierarchy): - """ls -R -L 2 includes items up to depth 2.""" mock_load.return_value = mock_hierarchy result = runner.invoke(app, ["ls", "-R", "-L", "2"]) assert result.exit_code == 0 assert "//example.com/f1" in result.stdout assert "//example.com/f1/f11" in result.stdout - assert "//example.com/f1/Project%201" in result.stdout - - -@patch("gcpath.core.Hierarchy.load") -def test_ls_recursive_with_level_json(mock_load, mock_hierarchy): - """ls -R -L 1 --json limits depth.""" - mock_load.return_value = mock_hierarchy - result = runner.invoke(app, ["--json", "ls", "-R", "-L", "1"]) - assert result.exit_code == 0 - data = json.loads(result.stdout) - paths = [item["path"] for item in data] - assert any(p == "//example.com" for p in paths) - assert "//example.com/f1" in paths - # Deeper items should not appear - assert "//example.com/f1/f11" not in paths - - -# --- find command tests --- @patch("gcpath.core.Hierarchy.load") def test_find_command(mock_load, mock_hierarchy): - """find matches by display name pattern.""" mock_load.return_value = mock_hierarchy result = runner.invoke(app, ["find", "f*"]) assert result.exit_code == 0 @@ -1150,7 +817,6 @@ def test_find_command(mock_load, mock_hierarchy): @patch("gcpath.core.Hierarchy.load") def test_find_command_exact(mock_load, mock_hierarchy): - """find with exact name.""" mock_load.return_value = mock_hierarchy result = runner.invoke(app, ["find", "f1"]) assert result.exit_code == 0 @@ -1160,44 +826,32 @@ def test_find_command_exact(mock_load, mock_hierarchy): @patch("gcpath.core.Hierarchy.load") def test_find_type_filter(mock_load, mock_hierarchy): - """find --type project filters results.""" mock_load.return_value = mock_hierarchy result = runner.invoke(app, ["find", "--type", "project", "*"]) assert result.exit_code == 0 assert "Project" in result.stdout - assert "Standalone" in result.stdout - # Folders should not appear - lines = result.stdout.strip().split("\n") - for line in lines: - if line.strip(): - assert "f1" not in line.split("/")[-1] or "Project" in line @patch("gcpath.core.Hierarchy.load") def test_find_no_match(mock_load, mock_hierarchy): - """find with no matches shows message.""" mock_load.return_value = mock_hierarchy result = runner.invoke(app, ["find", "nonexistent-xyz"]) assert result.exit_code == 0 - assert "No resources matching" in result.stdout + assert "0" in result.stdout @patch("gcpath.core.Hierarchy.load") def test_find_json_output(mock_load, mock_hierarchy): - """find --json outputs JSON.""" mock_load.return_value = mock_hierarchy - result = runner.invoke(app, ["--json", "find", "f*"]) + result = runner.invoke(app, ["--format", "json", "find", "f*"]) assert result.exit_code == 0 data = json.loads(result.stdout) assert isinstance(data, list) - assert len(data) >= 2 # f1 and f11 - assert all("path" in item for item in data) - assert all("type" in item for item in data) + assert len(data) >= 2 @patch("gcpath.core.Hierarchy.load") def test_find_case_insensitive(mock_load, mock_hierarchy): - """find is case-insensitive.""" mock_load.return_value = mock_hierarchy result = runner.invoke(app, ["find", "PROJECT*"]) assert result.exit_code == 0 @@ -1205,18 +859,13 @@ def test_find_case_insensitive(mock_load, mock_hierarchy): def test_find_type_invalid(): - """find --type invalid should fail.""" result = runner.invoke(app, ["find", "--type", "invalid", "*"]) assert result.exit_code == 1 assert "Invalid type" in result.output -# --- ancestors command tests --- - - @patch("gcpath.core.Hierarchy.resolve_ancestry_chain") def test_ancestors_command(mock_chain): - """ancestors shows full ancestry chain.""" mock_chain.return_value = [ ("organizations/123", "example.com", "organization"), ("folders/456", "engineering", "folder"), @@ -1225,62 +874,38 @@ def test_ancestors_command(mock_chain): result = runner.invoke(app, ["ancestors", "projects/p1"]) assert result.exit_code == 0 assert "organizations/123" in result.stdout - assert any(token == "example.com" for token in result.stdout.split()) + assert "example.com" in result.stdout assert "folders/456" in result.stdout - assert "engineering" in result.stdout - assert "projects/p1" in result.stdout - assert "my-project" in result.stdout @patch("gcpath.core.Hierarchy.resolve_ancestry_chain") def test_ancestors_json(mock_chain): - """ancestors --json outputs structured data.""" mock_chain.return_value = [ ("organizations/123", "example.com", "organization"), ("folders/456", "engineering", "folder"), ] - result = runner.invoke(app, ["--json", "ancestors", "folders/456"]) + result = runner.invoke(app, ["--format", "json", "ancestors", "folders/456"]) assert result.exit_code == 0 data = json.loads(result.stdout) assert len(data) == 2 assert data[0]["resource_name"] == "organizations/123" assert data[0]["type"] == "organization" - assert data[1]["resource_name"] == "folders/456" - assert data[1]["type"] == "folder" - - -@patch("gcpath.core.Hierarchy.resolve_ancestry_chain") -def test_ancestors_yaml(mock_chain): - """ancestors --yaml outputs structured data.""" - mock_chain.return_value = [ - ("organizations/123", "example.com", "organization"), - ] - result = runner.invoke(app, ["--yaml", "ancestors", "organizations/123"]) - assert result.exit_code == 0 - data = yaml.safe_load(result.stdout) - assert len(data) == 1 - assert data[0]["resource_name"] == "organizations/123" def test_ancestors_invalid_resource(): - """ancestors with invalid resource format should fail.""" result = runner.invoke(app, ["ancestors", "invalid/123"]) assert result.exit_code == 1 assert "Invalid resource format" in result.output -# --- Label/tag CLI tests --- - - @patch("gcpath.core.Hierarchy.load") -def test_ls_show_labels(mock_load): - """ls --show-labels --long should show a Labels column.""" +def test_ls_show_labels_rich(mock_load): hierarchy = make_test_hierarchy() f1 = hierarchy.organizations[0].folders["folders/1"] f1.labels = {"env": "prod"} mock_load.return_value = hierarchy - result = runner.invoke(app, ["ls", "--show-labels", "-l"]) + result = runner.invoke(app, ["--format", "rich", "ls", "--show-labels"]) assert result.exit_code == 0 assert "Labels" in result.stdout assert "env=prod" in result.stdout @@ -1288,7 +913,6 @@ def test_ls_show_labels(mock_load): @patch("gcpath.core.Hierarchy.load") def test_ls_label_filter(mock_load): - """ls --label env=prod should only show matching resources.""" hierarchy = make_test_hierarchy() f1 = hierarchy.organizations[0].folders["folders/1"] f1.labels = {"env": "prod"} @@ -1299,13 +923,11 @@ def test_ls_label_filter(mock_load): result = runner.invoke(app, ["ls", "-R", "--label", "env=prod"]) assert result.exit_code == 0 assert "f1" in result.stdout - # f11 has env=dev, should be filtered out assert "f11" not in result.stdout @patch("gcpath.core.Hierarchy.load") def test_ls_label_filter_key_only(mock_load): - """ls --label env should match any resource with the env label key.""" hierarchy = make_test_hierarchy() f1 = hierarchy.organizations[0].folders["folders/1"] f1.labels = {"env": "prod"} @@ -1319,29 +941,14 @@ def test_ls_label_filter_key_only(mock_load): assert "f11" not in result.stdout -@patch("gcpath.core.Hierarchy.load") -def test_ls_show_tags_long(mock_load): - """ls --show-tags --long should show a Tags column.""" - hierarchy = make_test_hierarchy() - p1 = next(p for p in hierarchy.projects if p.name == "projects/p1") - p1.tags = {"org/env": "production"} - mock_load.return_value = hierarchy - - result = runner.invoke(app, ["ls", "--show-tags", "-l", "-R"]) - assert result.exit_code == 0 - assert "Tags" in result.stdout - assert "org/env=production" in result.stdout - - @patch("gcpath.core.Hierarchy.load") def test_ls_json_with_labels(mock_load): - """ls --json with labels should include labels in JSON output.""" hierarchy = make_test_hierarchy() f1 = hierarchy.organizations[0].folders["folders/1"] f1.labels = {"env": "prod"} mock_load.return_value = hierarchy - result = runner.invoke(app, ["--json", "ls", "-R", "--show-labels"]) + result = runner.invoke(app, ["--format", "json", "ls", "-R", "--show-labels"]) assert result.exit_code == 0 data = json.loads(result.stdout) labeled = [item for item in data if item.get("labels")] @@ -1351,7 +958,6 @@ def test_ls_json_with_labels(mock_load): @patch("gcpath.core.Hierarchy.load") def test_find_with_label_filter(mock_load): - """find --label should filter search results by label.""" hierarchy = make_test_hierarchy() f1 = hierarchy.organizations[0].folders["folders/1"] f1.labels = {"env": "prod"} @@ -1363,3 +969,278 @@ def test_find_with_label_filter(mock_load): assert result.exit_code == 0 assert "f1" in result.stdout assert "f11" not in result.stdout + + +@patch("gcpath.core.Hierarchy.load") +def test_ls_fields_flag(mock_load, mock_hierarchy): + mock_load.return_value = mock_hierarchy + result = runner.invoke(app, ["ls", "--fields", "path,type"]) + assert result.exit_code == 0 + assert "path" in result.stdout + assert "type" in result.stdout + + +@patch("gcpath.core.Hierarchy.load") +def test_ls_fields_invalid(mock_load, mock_hierarchy): + mock_load.return_value = mock_hierarchy + result = runner.invoke(app, ["ls", "--fields", "path,invalid_field"]) + assert result.exit_code == 1 + + +@patch("gcpath.core.Hierarchy.load") +def test_ls_full_flag(mock_load): + hierarchy = make_test_hierarchy() + f1 = hierarchy.organizations[0].folders["folders/1"] + f1.labels = {f"key{i}": f"val{i}" for i in range(8)} + mock_load.return_value = hierarchy + + result = runner.invoke(app, ["ls", "--fields", "path,type,labels", "--full"]) + assert result.exit_code == 0 + + +@patch("gcpath.core.Hierarchy.load") +def test_home_view_no_cache(mock_load, mock_hierarchy): + mock_load.return_value = mock_hierarchy + result = runner.invoke(app, []) + assert result.exit_code == 0 + assert "bin:" in result.stdout + assert "description:" in result.stdout + + +@patch("gcpath.core.Hierarchy.load") +def test_hook_status(mock_load, mock_hierarchy): + mock_load.return_value = mock_hierarchy + result = runner.invoke(app, ["hook", "status"]) + assert result.exit_code == 0 + + +@patch("gcpath.core.Hierarchy.load") +def test_toon_ls_has_count(mock_load, mock_hierarchy): + mock_load.return_value = mock_hierarchy + result = runner.invoke(app, ["ls"]) + assert result.exit_code == 0 + assert "count:" in result.stdout + + +@patch("gcpath.core.Hierarchy.load") +def test_toon_ls_has_help(mock_load, mock_hierarchy): + mock_load.return_value = mock_hierarchy + result = runner.invoke(app, ["ls"]) + assert result.exit_code == 0 + assert "help" in result.stdout.lower() + + +@patch("gcpath.core.Hierarchy.load") +def test_toon_stats_output(mock_load, mock_hierarchy): + mock_load.return_value = mock_hierarchy + result = runner.invoke(app, ["stats"]) + assert result.exit_code == 0 + assert "scope:" in result.stdout + assert "folders:" in result.stdout + assert "projects:" in result.stdout + + +@patch("gcpath.core.Hierarchy.load") +def test_toon_name_single(mock_load, mock_hierarchy): + mock_load.return_value = mock_hierarchy + result = runner.invoke(app, ["name", "//example.com/f1"]) + assert result.exit_code == 0 + assert "resource_name:" in result.stdout + assert "folders/1" in result.stdout + + +@patch("gcpath.cli.Hierarchy.resolve_ancestry") +def test_toon_path_single(mock_resolve): + mock_resolve.return_value = "//example.com/f1" + result = runner.invoke(app, ["path", "folders/1"]) + assert result.exit_code == 0 + assert "path:" in result.stdout + assert "//example.com/f1" in result.stdout + + +# --- Coverage for new features --- + + +def test_invalid_format_flag(): + result = runner.invoke(app, ["--format", "invalid", "ls"]) + assert result.exit_code == 1 + assert "Invalid format" in result.stdout + + +@patch("gcpath.cli.get_cache_info") +@patch("gcpath.cli.read_cache_raw") +def test_home_view_with_fresh_cache(mock_raw, mock_info): + mock_info.return_value = MagicMock( + exists=True, fresh=True, age_seconds=120.0, + size_bytes=1024, version=1, org_count=1, folder_count=5, project_count=10, + ) + mock_raw.return_value = { + "organizations": [ + { + "organization": {"display_name": "example.com"}, + "folders": {"folders/1": {}, "folders/2": {}}, + "projects": [{}], + } + ] + } + result = runner.invoke(app, []) + assert result.exit_code == 0 + assert "description:" in result.stdout + assert "cache:" in result.stdout + + +@patch("gcpath.cli.install_hooks", return_value={"claude": True, "codex": False}) +def test_hook_install(mock_install): + result = runner.invoke(app, ["hook", "install"]) + assert result.exit_code == 0 + mock_install.assert_called_once() + assert "installed" in result.stdout + + +@patch("gcpath.cli.install_hooks", return_value={"claude": True}) +def test_hook_install_rich(mock_install): + result = runner.invoke(app, ["--format", "rich", "hook", "install"]) + assert result.exit_code == 0 + mock_install.assert_called_once() + + +@patch("gcpath.cli.uninstall_hooks", return_value={"claude": True, "codex": False}) +def test_hook_uninstall(mock_uninstall): + result = runner.invoke(app, ["hook", "uninstall"]) + assert result.exit_code == 0 + mock_uninstall.assert_called_once() + assert "uninstalled" in result.stdout + + +@patch("gcpath.cli.uninstall_hooks", return_value={"claude": False}) +def test_hook_uninstall_rich(mock_uninstall): + result = runner.invoke(app, ["--format", "rich", "hook", "uninstall"]) + assert result.exit_code == 0 + mock_uninstall.assert_called_once() + + +@patch("gcpath.cli.run_session_start", return_value="session dashboard output") +def test_hook_run(mock_run): + result = runner.invoke(app, ["hook", "run"]) + assert result.exit_code == 0 + mock_run.assert_called_once() + assert "session dashboard output" in result.stdout + + +@patch("gcpath.cli.get_hook_status", return_value={ + "claude": {"installed": True, "path_ok": True, "location": "~/.claude/settings.json"}, + "codex": {"installed": False, "path_ok": False, "location": "~/.codex/hooks.json"}, +}) +def test_hook_status_rich(mock_status): + result = runner.invoke(app, ["--format", "rich", "hook", "status"]) + assert result.exit_code == 0 + mock_status.assert_called_once() + + +@patch("gcpath.cli.get_hook_status", return_value={ + "claude": {"installed": True, "path_ok": False, "location": "~/.claude/settings.json"}, +}) +def test_hook_status_path_not_ok(mock_status): + result = runner.invoke(app, ["--format", "rich", "hook", "status"]) + assert result.exit_code == 0 + + +@patch("gcpath.cli.clear_cache", return_value=True) +def test_cache_clear_rich(mock_clear): + result = runner.invoke(app, ["--format", "rich", "cache", "clear"]) + assert result.exit_code == 0 + mock_clear.assert_called_once() + + +@patch("gcpath.cli.clear_cache", return_value=False) +def test_cache_clear_no_file(mock_clear): + result = runner.invoke(app, ["cache", "clear"]) + assert result.exit_code == 0 + + +@patch("gcpath.cli.clear_cache", return_value=False) +def test_cache_clear_no_file_rich(mock_clear): + result = runner.invoke(app, ["--format", "rich", "cache", "clear"]) + assert result.exit_code == 0 + + +@patch("gcpath.cli.get_cache_info") +def test_cache_status_rich(mock_info): + mock_info.return_value = CacheInfo( + exists=True, fresh=True, age_seconds=60.0, size_bytes=4096, + version=1, org_count=1, folder_count=3, project_count=5, + ) + result = runner.invoke(app, ["--format", "rich", "cache", "status"]) + assert result.exit_code == 0 + + +@patch("gcpath.cli.get_cache_info") +def test_cache_status_rich_no_cache(mock_info): + mock_info.return_value = CacheInfo( + exists=False, fresh=False, age_seconds=None, size_bytes=None, + version=None, org_count=0, folder_count=0, project_count=0, + ) + result = runner.invoke(app, ["--format", "rich", "cache", "status"]) + assert result.exit_code == 0 + + +@patch("gcpath.cli.read_config", return_value={"entrypoint": "folders/123"}) +def test_config_show_rich(mock_read): + result = runner.invoke(app, ["--format", "rich", "config", "show"]) + assert result.exit_code == 0 + mock_read.assert_called_once() + + +@patch("gcpath.cli.read_config", return_value={}) +def test_config_show_empty_rich(mock_read): + result = runner.invoke(app, ["--format", "rich", "config", "show"]) + assert result.exit_code == 0 + + +@patch("gcpath.cli.clear_entrypoint") +def test_config_clear_entrypoint_rich(mock_clear): + result = runner.invoke(app, ["--format", "rich", "config", "clear-entrypoint"]) + assert result.exit_code == 0 + mock_clear.assert_called_once() + + +@patch("gcpath.cli.set_entrypoint") +def test_config_set_entrypoint_rich(mock_set): + result = runner.invoke(app, ["--format", "rich", "config", "set-entrypoint", "folders/123"]) + assert result.exit_code == 0 + mock_set.assert_called_once_with("folders/123") + + +@patch("gcpath.cli.set_entrypoint", side_effect=ValueError("bad")) +def test_config_set_entrypoint_invalid_rich(mock_set): + result = runner.invoke(app, ["--format", "rich", "config", "set-entrypoint", "invalid"]) + assert result.exit_code == 1 + + +@patch("gcpath.core.Hierarchy.resolve_ancestry_chain") +def test_ancestors_rich(mock_chain): + mock_chain.return_value = [ + ("organizations/123", "example.com", "organization"), + ("folders/456", "eng", "folder"), + ] + result = runner.invoke(app, ["--format", "rich", "ancestors", "folders/456"]) + assert result.exit_code == 0 + mock_chain.assert_called_once() + + +@patch("gcpath.core.Hierarchy.resolve_ancestry_chain") +def test_ancestors_yaml(mock_chain): + mock_chain.return_value = [ + ("organizations/123", "example.com", "organization"), + ] + result = runner.invoke(app, ["--format", "yaml", "ancestors", "organizations/123"]) + assert result.exit_code == 0 + data = yaml.safe_load(result.stdout) + assert data[0]["resource_name"] == "organizations/123" + + +@patch("gcpath.core.Hierarchy.load") +def test_find_rich_format(mock_load, mock_hierarchy): + mock_load.return_value = mock_hierarchy + result = runner.invoke(app, ["--format", "rich", "find", "f*"]) + assert result.exit_code == 0 diff --git a/tests/test_hooks.py b/tests/test_hooks.py new file mode 100644 index 0000000..17558e9 --- /dev/null +++ b/tests/test_hooks.py @@ -0,0 +1,234 @@ +import json +from unittest.mock import patch + +from gcpath.hooks import ( + _install_claude_code, + _install_codex, + _uninstall_claude_code, + _uninstall_codex, + _read_json, + install_hooks, + uninstall_hooks, + get_hook_status, + run_session_start, +) + + +class TestReadJson: + def test_missing_file(self, tmp_path): + result = _read_json(tmp_path / "nonexistent.json") + assert result is None + + def test_malformed_json(self, tmp_path): + bad_file = tmp_path / "bad.json" + bad_file.write_text("{not valid json") + result = _read_json(bad_file) + assert result is None + + def test_valid_json(self, tmp_path): + good_file = tmp_path / "good.json" + good_file.write_text('{"key": "value"}') + result = _read_json(good_file) + assert result == {"key": "value"} + + +class TestInstallClaudeCode: + def test_creates_new_settings(self, tmp_path): + settings_path = tmp_path / "settings.json" + with patch("gcpath.hooks._CLAUDE_SETTINGS_PATH", settings_path): + changed = _install_claude_code("/usr/bin/gcpath hook run") + assert changed is True + data = json.loads(settings_path.read_text()) + assert len(data["hooks"]["SessionStart"]) == 1 + entry = data["hooks"]["SessionStart"][0] + assert entry["matcher"] == "" + assert entry["hooks"][0]["command"] == "/usr/bin/gcpath hook run" + assert entry["hooks"][0]["type"] == "command" + + def test_idempotent(self, tmp_path): + settings_path = tmp_path / "settings.json" + with patch("gcpath.hooks._CLAUDE_SETTINGS_PATH", settings_path): + _install_claude_code("/usr/bin/gcpath hook run") + changed = _install_claude_code("/usr/bin/gcpath hook run") + assert changed is False + + def test_repairs_stale_path(self, tmp_path): + settings_path = tmp_path / "settings.json" + with patch("gcpath.hooks._CLAUDE_SETTINGS_PATH", settings_path): + _install_claude_code("/old/path/gcpath hook run") + changed = _install_claude_code("/new/path/gcpath hook run") + assert changed is True + data = json.loads(settings_path.read_text()) + assert data["hooks"]["SessionStart"][0]["hooks"][0]["command"] == "/new/path/gcpath hook run" + + def test_preserves_other_hooks(self, tmp_path): + settings_path = tmp_path / "settings.json" + settings_path.write_text(json.dumps({ + "hooks": { + "SessionStart": [{"command": "other-tool hook run"}], + } + })) + with patch("gcpath.hooks._CLAUDE_SETTINGS_PATH", settings_path): + _install_claude_code("/usr/bin/gcpath hook run") + data = json.loads(settings_path.read_text()) + assert len(data["hooks"]["SessionStart"]) == 2 + + +class TestUninstallClaudeCode: + def test_removes_hook(self, tmp_path): + settings_path = tmp_path / "settings.json" + with patch("gcpath.hooks._CLAUDE_SETTINGS_PATH", settings_path): + _install_claude_code("/usr/bin/gcpath hook run") + changed = _uninstall_claude_code() + assert changed is True + data = json.loads(settings_path.read_text()) + assert "hooks" not in data + + def test_noop_when_not_installed(self, tmp_path): + settings_path = tmp_path / "settings.json" + with patch("gcpath.hooks._CLAUDE_SETTINGS_PATH", settings_path): + changed = _uninstall_claude_code() + assert changed is False + + def test_preserves_other_hooks(self, tmp_path): + settings_path = tmp_path / "settings.json" + settings_path.write_text(json.dumps({ + "hooks": { + "SessionStart": [ + {"command": "/usr/bin/gcpath hook run"}, + {"command": "other-tool hook run"}, + ], + } + })) + with patch("gcpath.hooks._CLAUDE_SETTINGS_PATH", settings_path): + changed = _uninstall_claude_code() + assert changed is True + data = json.loads(settings_path.read_text()) + assert len(data["hooks"]["SessionStart"]) == 1 + assert data["hooks"]["SessionStart"][0]["command"] == "other-tool hook run" + + +class TestInstallCodex: + def test_creates_new_hooks(self, tmp_path): + hooks_path = tmp_path / "hooks.json" + with patch("gcpath.hooks._CODEX_HOOKS_PATH", hooks_path): + changed = _install_codex("/usr/bin/gcpath hook run") + assert changed is True + data = json.loads(hooks_path.read_text()) + assert len(data["SessionStart"]) == 1 + + def test_idempotent(self, tmp_path): + hooks_path = tmp_path / "hooks.json" + with patch("gcpath.hooks._CODEX_HOOKS_PATH", hooks_path): + _install_codex("/usr/bin/gcpath hook run") + changed = _install_codex("/usr/bin/gcpath hook run") + assert changed is False + + +class TestUninstallCodex: + def test_removes_hook(self, tmp_path): + hooks_path = tmp_path / "hooks.json" + with patch("gcpath.hooks._CODEX_HOOKS_PATH", hooks_path): + _install_codex("/usr/bin/gcpath hook run") + changed = _uninstall_codex() + assert changed is True + data = json.loads(hooks_path.read_text()) + assert "SessionStart" not in data + + def test_noop_when_not_installed(self, tmp_path): + hooks_path = tmp_path / "hooks.json" + with patch("gcpath.hooks._CODEX_HOOKS_PATH", hooks_path): + changed = _uninstall_codex() + assert changed is False + + +class TestInstallHooks: + def test_installs_both(self, tmp_path): + settings_path = tmp_path / "settings.json" + hooks_path = tmp_path / "hooks.json" + with ( + patch("gcpath.hooks._CLAUDE_SETTINGS_PATH", settings_path), + patch("gcpath.hooks._CODEX_HOOKS_PATH", hooks_path), + patch("gcpath.hooks._get_gcpath_bin", return_value="/usr/bin/gcpath"), + ): + results = install_hooks() + assert results["claude_code"] is True + assert results["codex"] is True + + +class TestUninstallHooks: + def test_uninstalls_both(self, tmp_path): + settings_path = tmp_path / "settings.json" + hooks_path = tmp_path / "hooks.json" + with ( + patch("gcpath.hooks._CLAUDE_SETTINGS_PATH", settings_path), + patch("gcpath.hooks._CODEX_HOOKS_PATH", hooks_path), + patch("gcpath.hooks._get_gcpath_bin", return_value="/usr/bin/gcpath"), + ): + install_hooks() + results = uninstall_hooks() + assert results["claude_code"] is True + assert results["codex"] is True + + +class TestGetHookStatus: + def test_not_installed(self, tmp_path): + settings_path = tmp_path / "settings.json" + hooks_path = tmp_path / "hooks.json" + with ( + patch("gcpath.hooks._CLAUDE_SETTINGS_PATH", settings_path), + patch("gcpath.hooks._CODEX_HOOKS_PATH", hooks_path), + patch("gcpath.hooks._get_gcpath_bin", return_value="/usr/bin/gcpath"), + ): + status = get_hook_status() + assert status["claude_code"]["installed"] is False + assert status["codex"]["installed"] is False + + def test_installed(self, tmp_path): + settings_path = tmp_path / "settings.json" + hooks_path = tmp_path / "hooks.json" + with ( + patch("gcpath.hooks._CLAUDE_SETTINGS_PATH", settings_path), + patch("gcpath.hooks._CODEX_HOOKS_PATH", hooks_path), + patch("gcpath.hooks._get_gcpath_bin", return_value="/usr/bin/gcpath"), + ): + install_hooks() + status = get_hook_status() + assert status["claude_code"]["installed"] is True + assert status["claude_code"]["path_ok"] is True + assert status["codex"]["installed"] is True + assert status["codex"]["path_ok"] is True + + +class TestRunSessionStart: + def test_no_cache(self): + from gcpath.cache import CacheInfo + + with patch("gcpath.cache.get_cache_info", return_value=CacheInfo( + exists=False, fresh=False, age_seconds=None, size_bytes=None, + version=None, org_count=0, folder_count=0, project_count=0, + )): + output = run_session_start() + assert "cache: empty" in output + assert "help" in output + + def test_with_fresh_cache(self): + from gcpath.cache import CacheInfo + + with ( + patch("gcpath.cache.get_cache_info", return_value=CacheInfo( + exists=True, fresh=True, age_seconds=300.0, size_bytes=2048, + version=1, org_count=1, folder_count=5, project_count=10, + )), + patch("gcpath.cache.read_cache_raw", return_value={ + "organizations": [{ + "organization": {"display_name": "example.com"}, + "folders": {"f1": {}, "f2": {}}, + "projects": [1, 2, 3], + }] + }), + ): + output = run_session_start() + assert "fresh" in output + assert "example.com" in output + assert "help" in output diff --git a/tests/test_serializers.py b/tests/test_serializers.py index d168d9a..676ccc3 100644 --- a/tests/test_serializers.py +++ b/tests/test_serializers.py @@ -14,6 +14,17 @@ serialize_resource, serialize_tree, serialize_tree_node, + toon_ls, + toon_name, + toon_path, + toon_ancestors, + toon_find, + toon_stats, + toon_cache_status, + toon_config, + toon_confirmed, + _truncate_metadata, + _default_fields_for_items, ) @@ -269,3 +280,185 @@ def test_organization_has_no_labels(self): d = serialize_resource("//example.com", org_node) assert "labels" not in d assert "tags" not in d + + +class TestToonLs: + def test_basic_output(self): + _, _, f1, p1, _ = _h() + items = [("//example.com/f1", f1), ("//example.com/f1/Project 1", p1)] + output = toon_ls(items, total_in_scope=2) + assert "count:" in output + assert "resources" in output + assert "f1" in output + + def test_empty_items(self): + output = toon_ls([], total_in_scope=0) + assert "0" in output + + def test_with_help(self): + _, _, f1, _, _ = _h() + items = [("//example.com/f1", f1)] + output = toon_ls(items, total_in_scope=1, help_lines=["Run `gcpath ls -R`"]) + assert "help" in output + + def test_fields_override(self): + _, _, f1, _, _ = _h() + items = [("//example.com/f1", f1)] + output = toon_ls(items, total_in_scope=1, fields=("path", "type")) + assert "path" in output + assert "type" in output + + def test_full_flag(self): + _, _, f1, _, _ = _h() + f1.labels = {f"k{i}": f"v{i}" for i in range(8)} + items = [("//example.com/f1", f1)] + output_truncated = toon_ls( + items, total_in_scope=1, fields=("path", "type", "labels"), full=False + ) + output_full = toon_ls( + items, total_in_scope=1, fields=("path", "type", "labels"), full=True + ) + assert "k7" in output_full + assert "--full" in output_truncated + + +class TestToonName: + def test_single(self): + output = toon_name([("//example.com/f1", "folders/1")]) + assert "resource_name:" in output + assert "folders/1" in output + + def test_single_id_only(self): + output = toon_name([("//example.com/f1", "folders/1")], id_only=True) + assert "resource_id:" in output + assert "1" in output + + def test_multiple(self): + output = toon_name([ + ("//example.com", "organizations/123"), + ("//example.com/f1", "folders/1"), + ]) + assert "organizations/123" in output + assert "folders/1" in output + + +class TestToonPath: + def test_single(self): + output = toon_path([("folders/1", "//example.com/f1")]) + assert "path:" in output + assert "//example.com/f1" in output + + def test_multiple(self): + output = toon_path([ + ("folders/1", "//example.com/f1"), + ("folders/2", "//example.com/f2"), + ]) + assert "//example.com/f1" in output + assert "//example.com/f2" in output + + +class TestToonAncestors: + def test_basic(self): + chain = [ + ("organizations/123", "example.com", "organization"), + ("folders/456", "eng", "folder"), + ] + output = toon_ancestors(chain) + assert "organizations/123" in output + assert "example.com" in output + assert "folder" in output + + +class TestToonFind: + def test_with_results(self): + _, _, f1, _, _ = _h() + items = [("//example.com/f1", f1)] + output = toon_find(items, "f*", total_searched=5) + assert "count:" in output + assert "1 of 5 searched" in output + assert "f1" in output + + def test_empty(self): + output = toon_find([], "xyz*") + assert "0" in output + + +class TestToonStats: + def test_basic(self): + output = toon_stats("all organizations", organizations=2, folders=10, projects=25) + assert "scope:" in output + assert "2" in output + assert "10" in output + assert "25" in output + + def test_scoped(self): + output = toon_stats("folders/123", folders=5, projects=8, help_lines=["Run `gcpath stats`"]) + assert "folders/123" in output + assert "help" in output + + +class TestToonCacheStatus: + def test_fresh(self): + output = toon_cache_status( + exists=True, fresh=True, age_seconds=300.0, org_count=1, folder_count=5, project_count=10, location="/var/cache/gcpath/test" + ) + assert "fresh" in output + assert "5m" in output + + def test_empty(self): + output = toon_cache_status(exists=False, fresh=False, location="/var/cache/gcpath/test") + assert "empty" in output + + def test_stale(self): + output = toon_cache_status( + exists=True, fresh=False, age_seconds=7200.0, org_count=0, folder_count=0, project_count=0, location="/var/cache/gcpath/test" + ) + assert "stale" in output + + +class TestToonConfig: + def test_with_data(self): + output = toon_config({"entrypoint": "folders/123"}, "/var/cache/gcpath/config") + assert "folders/123" in output + + def test_empty(self): + output = toon_config({}, "/var/cache/gcpath/config") + assert "empty" in output + + +class TestToonConfirmed: + def test_basic(self): + output = toon_confirmed("Cache cleared") + assert "ok" in output + assert "Cache cleared" in output + + +class TestTruncateMetadata: + def test_short_dict(self): + d = {"a": "1", "b": "2"} + result = _truncate_metadata(d) + assert result == d + + def test_long_dict_truncated(self): + d = {f"k{i}": f"v{i}" for i in range(8)} + result = _truncate_metadata(d, limit=5) + assert len(result) == 6 # 5 real + 1 truncation notice + + def test_full_flag_overrides(self): + d = {f"k{i}": f"v{i}" for i in range(8)} + result = _truncate_metadata(d, limit=5, full=True) + assert len(result) == 8 + + +class TestDefaultFieldsForItems: + def test_with_projects(self): + _, _, _, p1, _ = _h() + items = [("//x", p1)] + fields = _default_fields_for_items(items) + assert "project_id" in fields + + def test_without_projects(self): + _, _, f1, _, _ = _h() + items = [("//x", f1)] + fields = _default_fields_for_items(items) + assert "project_id" not in fields diff --git a/uv.lock b/uv.lock index 80bbbc1..6361a5e 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 1 requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.14'", @@ -11,75 +11,75 @@ resolution-markers = [ name = "cachetools" version = "6.2.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bc/1d/ede8680603f6016887c062a2cf4fc8fdba905866a3ab8831aa8aa651320c/cachetools-6.2.4.tar.gz", hash = "sha256:82c5c05585e70b6ba2d3ae09ea60b79548872185d2f24ae1f2709d37299fd607", size = 31731, upload-time = "2025-12-15T18:24:53.744Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/1d/ede8680603f6016887c062a2cf4fc8fdba905866a3ab8831aa8aa651320c/cachetools-6.2.4.tar.gz", hash = "sha256:82c5c05585e70b6ba2d3ae09ea60b79548872185d2f24ae1f2709d37299fd607", size = 31731 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/fc/1d7b80d0eb7b714984ce40efc78859c022cd930e402f599d8ca9e39c78a4/cachetools-6.2.4-py3-none-any.whl", hash = "sha256:69a7a52634fed8b8bf6e24a050fb60bff1c9bd8f6d24572b99c32d4e71e62a51", size = 11551, upload-time = "2025-12-15T18:24:52.332Z" }, + { url = "https://files.pythonhosted.org/packages/2c/fc/1d7b80d0eb7b714984ce40efc78859c022cd930e402f599d8ca9e39c78a4/cachetools-6.2.4-py3-none-any.whl", hash = "sha256:69a7a52634fed8b8bf6e24a050fb60bff1c9bd8f6d24572b99c32d4e71e62a51", size = 11551 }, ] [[package]] name = "certifi" version = "2025.11.12" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538 } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438 }, ] [[package]] name = "charset-normalizer" version = "3.4.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, - { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, - { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, - { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, - { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, - { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, - { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, - { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, - { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, - { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, - { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, - { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, - { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, - { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, - { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, - { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, - { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, - { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, - { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, - { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, - { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, - { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, - { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, - { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, - { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, - { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, - { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, - { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, - { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, - { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, - { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, - { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, - { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, - { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, - { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, - { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, - { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, - { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, - { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, - { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, - { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, - { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, - { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, - { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425 }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162 }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558 }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497 }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240 }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471 }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864 }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647 }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110 }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839 }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667 }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535 }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816 }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694 }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131 }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390 }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091 }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936 }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180 }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346 }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874 }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076 }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601 }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376 }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825 }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583 }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366 }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300 }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465 }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404 }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092 }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408 }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746 }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889 }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641 }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779 }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035 }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542 }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524 }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395 }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680 }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045 }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687 }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014 }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044 }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940 }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104 }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743 }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402 }, ] [[package]] @@ -89,103 +89,104 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065 } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274 }, ] [[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, upload-time = "2022-10-25T02:36:22.414Z" } +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, upload-time = "2022-10-25T02:36:20.889Z" }, + { 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 = "coverage" version = "7.13.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/45/2c665ca77ec32ad67e25c77daf1cee28ee4558f3bc571cdbaf88a00b9f23/coverage-7.13.0.tar.gz", hash = "sha256:a394aa27f2d7ff9bc04cf703817773a59ad6dfbd577032e690f961d2460ee936", size = 820905, upload-time = "2025-12-08T13:14:38.055Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/f1/2619559f17f31ba00fc40908efd1fbf1d0a5536eb75dc8341e7d660a08de/coverage-7.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0b3d67d31383c4c68e19a88e28fc4c2e29517580f1b0ebec4a069d502ce1e0bf", size = 218274, upload-time = "2025-12-08T13:12:52.095Z" }, - { url = "https://files.pythonhosted.org/packages/2b/11/30d71ae5d6e949ff93b2a79a2c1b4822e00423116c5c6edfaeef37301396/coverage-7.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:581f086833d24a22c89ae0fe2142cfaa1c92c930adf637ddf122d55083fb5a0f", size = 218638, upload-time = "2025-12-08T13:12:53.418Z" }, - { url = "https://files.pythonhosted.org/packages/79/c2/fce80fc6ded8d77e53207489d6065d0fed75db8951457f9213776615e0f5/coverage-7.13.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a3a30f0e257df382f5f9534d4ce3d4cf06eafaf5192beb1a7bd066cb10e78fb", size = 250129, upload-time = "2025-12-08T13:12:54.744Z" }, - { url = "https://files.pythonhosted.org/packages/5b/b6/51b5d1eb6fcbb9a1d5d6984e26cbe09018475c2922d554fd724dd0f056ee/coverage-7.13.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:583221913fbc8f53b88c42e8dbb8fca1d0f2e597cb190ce45916662b8b9d9621", size = 252885, upload-time = "2025-12-08T13:12:56.401Z" }, - { url = "https://files.pythonhosted.org/packages/0d/f8/972a5affea41de798691ab15d023d3530f9f56a72e12e243f35031846ff7/coverage-7.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f5d9bd30756fff3e7216491a0d6d520c448d5124d3d8e8f56446d6412499e74", size = 253974, upload-time = "2025-12-08T13:12:57.718Z" }, - { url = "https://files.pythonhosted.org/packages/8a/56/116513aee860b2c7968aa3506b0f59b22a959261d1dbf3aea7b4450a7520/coverage-7.13.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a23e5a1f8b982d56fa64f8e442e037f6ce29322f1f9e6c2344cd9e9f4407ee57", size = 250538, upload-time = "2025-12-08T13:12:59.254Z" }, - { url = "https://files.pythonhosted.org/packages/d6/75/074476d64248fbadf16dfafbf93fdcede389ec821f74ca858d7c87d2a98c/coverage-7.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b01c22bc74a7fb44066aaf765224c0d933ddf1f5047d6cdfe4795504a4493f8", size = 251912, upload-time = "2025-12-08T13:13:00.604Z" }, - { url = "https://files.pythonhosted.org/packages/f2/d2/aa4f8acd1f7c06024705c12609d8698c51b27e4d635d717cd1934c9668e2/coverage-7.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:898cce66d0836973f48dda4e3514d863d70142bdf6dfab932b9b6a90ea5b222d", size = 250054, upload-time = "2025-12-08T13:13:01.892Z" }, - { url = "https://files.pythonhosted.org/packages/19/98/8df9e1af6a493b03694a1e8070e024e7d2cdc77adedc225a35e616d505de/coverage-7.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:3ab483ea0e251b5790c2aac03acde31bff0c736bf8a86829b89382b407cd1c3b", size = 249619, upload-time = "2025-12-08T13:13:03.236Z" }, - { url = "https://files.pythonhosted.org/packages/d8/71/f8679231f3353018ca66ef647fa6fe7b77e6bff7845be54ab84f86233363/coverage-7.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d84e91521c5e4cb6602fe11ece3e1de03b2760e14ae4fcf1a4b56fa3c801fcd", size = 251496, upload-time = "2025-12-08T13:13:04.511Z" }, - { url = "https://files.pythonhosted.org/packages/04/86/9cb406388034eaf3c606c22094edbbb82eea1fa9d20c0e9efadff20d0733/coverage-7.13.0-cp312-cp312-win32.whl", hash = "sha256:193c3887285eec1dbdb3f2bd7fbc351d570ca9c02ca756c3afbc71b3c98af6ef", size = 220808, upload-time = "2025-12-08T13:13:06.422Z" }, - { url = "https://files.pythonhosted.org/packages/1c/59/af483673df6455795daf5f447c2f81a3d2fcfc893a22b8ace983791f6f34/coverage-7.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:4f3e223b2b2db5e0db0c2b97286aba0036ca000f06aca9b12112eaa9af3d92ae", size = 221616, upload-time = "2025-12-08T13:13:07.95Z" }, - { url = "https://files.pythonhosted.org/packages/64/b0/959d582572b30a6830398c60dd419c1965ca4b5fb38ac6b7093a0d50ca8d/coverage-7.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:086cede306d96202e15a4b77ace8472e39d9f4e5f9fd92dd4fecdfb2313b2080", size = 220261, upload-time = "2025-12-08T13:13:09.581Z" }, - { url = "https://files.pythonhosted.org/packages/7c/cc/bce226595eb3bf7d13ccffe154c3c487a22222d87ff018525ab4dd2e9542/coverage-7.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:28ee1c96109974af104028a8ef57cec21447d42d0e937c0275329272e370ebcf", size = 218297, upload-time = "2025-12-08T13:13:10.977Z" }, - { url = "https://files.pythonhosted.org/packages/3b/9f/73c4d34600aae03447dff3d7ad1d0ac649856bfb87d1ca7d681cfc913f9e/coverage-7.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e97353dcc5587b85986cda4ff3ec98081d7e84dd95e8b2a6d59820f0545f8a", size = 218673, upload-time = "2025-12-08T13:13:12.562Z" }, - { url = "https://files.pythonhosted.org/packages/63/ab/8fa097db361a1e8586535ae5073559e6229596b3489ec3ef2f5b38df8cb2/coverage-7.13.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:99acd4dfdfeb58e1937629eb1ab6ab0899b131f183ee5f23e0b5da5cba2fec74", size = 249652, upload-time = "2025-12-08T13:13:13.909Z" }, - { url = "https://files.pythonhosted.org/packages/90/3a/9bfd4de2ff191feb37ef9465855ca56a6f2f30a3bca172e474130731ac3d/coverage-7.13.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff45e0cd8451e293b63ced93161e189780baf444119391b3e7d25315060368a6", size = 252251, upload-time = "2025-12-08T13:13:15.553Z" }, - { url = "https://files.pythonhosted.org/packages/df/61/b5d8105f016e1b5874af0d7c67542da780ccd4a5f2244a433d3e20ceb1ad/coverage-7.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4f72a85316d8e13234cafe0a9f81b40418ad7a082792fa4165bd7d45d96066b", size = 253492, upload-time = "2025-12-08T13:13:16.849Z" }, - { url = "https://files.pythonhosted.org/packages/f3/b8/0fad449981803cc47a4694768b99823fb23632150743f9c83af329bb6090/coverage-7.13.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11c21557d0e0a5a38632cbbaca5f008723b26a89d70db6315523df6df77d6232", size = 249850, upload-time = "2025-12-08T13:13:18.142Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e9/8d68337c3125014d918cf4327d5257553a710a2995a6a6de2ac77e5aa429/coverage-7.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76541dc8d53715fb4f7a3a06b34b0dc6846e3c69bc6204c55653a85dd6220971", size = 251633, upload-time = "2025-12-08T13:13:19.56Z" }, - { url = "https://files.pythonhosted.org/packages/55/14/d4112ab26b3a1bc4b3c1295d8452dcf399ed25be4cf649002fb3e64b2d93/coverage-7.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6e9e451dee940a86789134b6b0ffbe31c454ade3b849bb8a9d2cca2541a8e91d", size = 249586, upload-time = "2025-12-08T13:13:20.883Z" }, - { url = "https://files.pythonhosted.org/packages/2c/a9/22b0000186db663b0d82f86c2f1028099ae9ac202491685051e2a11a5218/coverage-7.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5c67dace46f361125e6b9cace8fe0b729ed8479f47e70c89b838d319375c8137", size = 249412, upload-time = "2025-12-08T13:13:22.22Z" }, - { url = "https://files.pythonhosted.org/packages/a1/2e/42d8e0d9e7527fba439acdc6ed24a2b97613b1dc85849b1dd935c2cffef0/coverage-7.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f59883c643cb19630500f57016f76cfdcd6845ca8c5b5ea1f6e17f74c8e5f511", size = 251191, upload-time = "2025-12-08T13:13:23.899Z" }, - { url = "https://files.pythonhosted.org/packages/a4/af/8c7af92b1377fd8860536aadd58745119252aaaa71a5213e5a8e8007a9f5/coverage-7.13.0-cp313-cp313-win32.whl", hash = "sha256:58632b187be6f0be500f553be41e277712baa278147ecb7559983c6d9faf7ae1", size = 220829, upload-time = "2025-12-08T13:13:25.182Z" }, - { url = "https://files.pythonhosted.org/packages/58/f9/725e8bf16f343d33cbe076c75dc8370262e194ff10072c0608b8e5cf33a3/coverage-7.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:73419b89f812f498aca53f757dd834919b48ce4799f9d5cad33ca0ae442bdb1a", size = 221640, upload-time = "2025-12-08T13:13:26.836Z" }, - { url = "https://files.pythonhosted.org/packages/8a/ff/e98311000aa6933cc79274e2b6b94a2fe0fe3434fca778eba82003675496/coverage-7.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb76670874fdd6091eedcc856128ee48c41a9bbbb9c3f1c7c3cf169290e3ffd6", size = 220269, upload-time = "2025-12-08T13:13:28.116Z" }, - { url = "https://files.pythonhosted.org/packages/cf/cf/bbaa2e1275b300343ea865f7d424cc0a2e2a1df6925a070b2b2d5d765330/coverage-7.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6e63ccc6e0ad8986386461c3c4b737540f20426e7ec932f42e030320896c311a", size = 218990, upload-time = "2025-12-08T13:13:29.463Z" }, - { url = "https://files.pythonhosted.org/packages/21/1d/82f0b3323b3d149d7672e7744c116e9c170f4957e0c42572f0366dbb4477/coverage-7.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:494f5459ffa1bd45e18558cd98710c36c0b8fbfa82a5eabcbe671d80ecffbfe8", size = 219340, upload-time = "2025-12-08T13:13:31.524Z" }, - { url = "https://files.pythonhosted.org/packages/fb/e3/fe3fd4702a3832a255f4d43013eacb0ef5fc155a5960ea9269d8696db28b/coverage-7.13.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:06cac81bf10f74034e055e903f5f946e3e26fc51c09fc9f584e4a1605d977053", size = 260638, upload-time = "2025-12-08T13:13:32.965Z" }, - { url = "https://files.pythonhosted.org/packages/ad/01/63186cb000307f2b4da463f72af9b85d380236965574c78e7e27680a2593/coverage-7.13.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f2ffc92b46ed6e6760f1d47a71e56b5664781bc68986dbd1836b2b70c0ce2071", size = 262705, upload-time = "2025-12-08T13:13:34.378Z" }, - { url = "https://files.pythonhosted.org/packages/7c/a1/c0dacef0cc865f2455d59eed3548573ce47ed603205ffd0735d1d78b5906/coverage-7.13.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0602f701057c6823e5db1b74530ce85f17c3c5be5c85fc042ac939cbd909426e", size = 265125, upload-time = "2025-12-08T13:13:35.73Z" }, - { url = "https://files.pythonhosted.org/packages/ef/92/82b99223628b61300bd382c205795533bed021505eab6dd86e11fb5d7925/coverage-7.13.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:25dc33618d45456ccb1d37bce44bc78cf269909aa14c4db2e03d63146a8a1493", size = 259844, upload-time = "2025-12-08T13:13:37.69Z" }, - { url = "https://files.pythonhosted.org/packages/cf/2c/89b0291ae4e6cd59ef042708e1c438e2290f8c31959a20055d8768349ee2/coverage-7.13.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:71936a8b3b977ddd0b694c28c6a34f4fff2e9dd201969a4ff5d5fc7742d614b0", size = 262700, upload-time = "2025-12-08T13:13:39.525Z" }, - { url = "https://files.pythonhosted.org/packages/bf/f9/a5f992efae1996245e796bae34ceb942b05db275e4b34222a9a40b9fbd3b/coverage-7.13.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:936bc20503ce24770c71938d1369461f0c5320830800933bc3956e2a4ded930e", size = 260321, upload-time = "2025-12-08T13:13:41.172Z" }, - { url = "https://files.pythonhosted.org/packages/4c/89/a29f5d98c64fedbe32e2ac3c227fbf78edc01cc7572eee17d61024d89889/coverage-7.13.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:af0a583efaacc52ae2521f8d7910aff65cdb093091d76291ac5820d5e947fc1c", size = 259222, upload-time = "2025-12-08T13:13:43.282Z" }, - { url = "https://files.pythonhosted.org/packages/b3/c3/940fe447aae302a6701ee51e53af7e08b86ff6eed7631e5740c157ee22b9/coverage-7.13.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f1c23e24a7000da892a312fb17e33c5f94f8b001de44b7cf8ba2e36fbd15859e", size = 261411, upload-time = "2025-12-08T13:13:44.72Z" }, - { url = "https://files.pythonhosted.org/packages/eb/31/12a4aec689cb942a89129587860ed4d0fd522d5fda81237147fde554b8ae/coverage-7.13.0-cp313-cp313t-win32.whl", hash = "sha256:5f8a0297355e652001015e93be345ee54393e45dc3050af4a0475c5a2b767d46", size = 221505, upload-time = "2025-12-08T13:13:46.332Z" }, - { url = "https://files.pythonhosted.org/packages/65/8c/3b5fe3259d863572d2b0827642c50c3855d26b3aefe80bdc9eba1f0af3b0/coverage-7.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6abb3a4c52f05e08460bd9acf04fec027f8718ecaa0d09c40ffbc3fbd70ecc39", size = 222569, upload-time = "2025-12-08T13:13:47.79Z" }, - { url = "https://files.pythonhosted.org/packages/b0/39/f71fa8316a96ac72fc3908839df651e8eccee650001a17f2c78cdb355624/coverage-7.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:3ad968d1e3aa6ce5be295ab5fe3ae1bf5bb4769d0f98a80a0252d543a2ef2e9e", size = 220841, upload-time = "2025-12-08T13:13:49.243Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4b/9b54bedda55421449811dcd5263a2798a63f48896c24dfb92b0f1b0845bd/coverage-7.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:453b7ec753cf5e4356e14fe858064e5520c460d3bbbcb9c35e55c0d21155c256", size = 218343, upload-time = "2025-12-08T13:13:50.811Z" }, - { url = "https://files.pythonhosted.org/packages/59/df/c3a1f34d4bba2e592c8979f924da4d3d4598b0df2392fbddb7761258e3dc/coverage-7.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:af827b7cbb303e1befa6c4f94fd2bf72f108089cfa0f8abab8f4ca553cf5ca5a", size = 218672, upload-time = "2025-12-08T13:13:52.284Z" }, - { url = "https://files.pythonhosted.org/packages/07/62/eec0659e47857698645ff4e6ad02e30186eb8afd65214fd43f02a76537cb/coverage-7.13.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9987a9e4f8197a1000280f7cc089e3ea2c8b3c0a64d750537809879a7b4ceaf9", size = 249715, upload-time = "2025-12-08T13:13:53.791Z" }, - { url = "https://files.pythonhosted.org/packages/23/2d/3c7ff8b2e0e634c1f58d095f071f52ed3c23ff25be524b0ccae8b71f99f8/coverage-7.13.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3188936845cd0cb114fa6a51842a304cdbac2958145d03be2377ec41eb285d19", size = 252225, upload-time = "2025-12-08T13:13:55.274Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ac/fb03b469d20e9c9a81093575003f959cf91a4a517b783aab090e4538764b/coverage-7.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2bdb3babb74079f021696cb46b8bb5f5661165c385d3a238712b031a12355be", size = 253559, upload-time = "2025-12-08T13:13:57.161Z" }, - { url = "https://files.pythonhosted.org/packages/29/62/14afa9e792383c66cc0a3b872a06ded6e4ed1079c7d35de274f11d27064e/coverage-7.13.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7464663eaca6adba4175f6c19354feea61ebbdd735563a03d1e472c7072d27bb", size = 249724, upload-time = "2025-12-08T13:13:58.692Z" }, - { url = "https://files.pythonhosted.org/packages/31/b7/333f3dab2939070613696ab3ee91738950f0467778c6e5a5052e840646b7/coverage-7.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8069e831f205d2ff1f3d355e82f511eb7c5522d7d413f5db5756b772ec8697f8", size = 251582, upload-time = "2025-12-08T13:14:00.642Z" }, - { url = "https://files.pythonhosted.org/packages/81/cb/69162bda9381f39b2287265d7e29ee770f7c27c19f470164350a38318764/coverage-7.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6fb2d5d272341565f08e962cce14cdf843a08ac43bd621783527adb06b089c4b", size = 249538, upload-time = "2025-12-08T13:14:02.556Z" }, - { url = "https://files.pythonhosted.org/packages/e0/76/350387b56a30f4970abe32b90b2a434f87d29f8b7d4ae40d2e8a85aacfb3/coverage-7.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5e70f92ef89bac1ac8a99b3324923b4749f008fdbd7aa9cb35e01d7a284a04f9", size = 249349, upload-time = "2025-12-08T13:14:04.015Z" }, - { url = "https://files.pythonhosted.org/packages/86/0d/7f6c42b8d59f4c7e43ea3059f573c0dcfed98ba46eb43c68c69e52ae095c/coverage-7.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4b5de7d4583e60d5fd246dd57fcd3a8aa23c6e118a8c72b38adf666ba8e7e927", size = 251011, upload-time = "2025-12-08T13:14:05.505Z" }, - { url = "https://files.pythonhosted.org/packages/d7/f1/4bb2dff379721bb0b5c649d5c5eaf438462cad824acf32eb1b7ca0c7078e/coverage-7.13.0-cp314-cp314-win32.whl", hash = "sha256:a6c6e16b663be828a8f0b6c5027d36471d4a9f90d28444aa4ced4d48d7d6ae8f", size = 221091, upload-time = "2025-12-08T13:14:07.127Z" }, - { url = "https://files.pythonhosted.org/packages/ba/44/c239da52f373ce379c194b0ee3bcc121020e397242b85f99e0afc8615066/coverage-7.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:0900872f2fdb3ee5646b557918d02279dc3af3dfb39029ac4e945458b13f73bc", size = 221904, upload-time = "2025-12-08T13:14:08.542Z" }, - { url = "https://files.pythonhosted.org/packages/89/1f/b9f04016d2a29c2e4a0307baefefad1a4ec5724946a2b3e482690486cade/coverage-7.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:3a10260e6a152e5f03f26db4a407c4c62d3830b9af9b7c0450b183615f05d43b", size = 220480, upload-time = "2025-12-08T13:14:10.958Z" }, - { url = "https://files.pythonhosted.org/packages/16/d4/364a1439766c8e8647860584171c36010ca3226e6e45b1753b1b249c5161/coverage-7.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9097818b6cc1cfb5f174e3263eba4a62a17683bcfe5c4b5d07f4c97fa51fbf28", size = 219074, upload-time = "2025-12-08T13:14:13.345Z" }, - { url = "https://files.pythonhosted.org/packages/ce/f4/71ba8be63351e099911051b2089662c03d5671437a0ec2171823c8e03bec/coverage-7.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0018f73dfb4301a89292c73be6ba5f58722ff79f51593352759c1790ded1cabe", size = 219342, upload-time = "2025-12-08T13:14:15.02Z" }, - { url = "https://files.pythonhosted.org/packages/5e/25/127d8ed03d7711a387d96f132589057213e3aef7475afdaa303412463f22/coverage-7.13.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:166ad2a22ee770f5656e1257703139d3533b4a0b6909af67c6b4a3adc1c98657", size = 260713, upload-time = "2025-12-08T13:14:16.907Z" }, - { url = "https://files.pythonhosted.org/packages/fd/db/559fbb6def07d25b2243663b46ba9eb5a3c6586c0c6f4e62980a68f0ee1c/coverage-7.13.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f6aaef16d65d1787280943f1c8718dc32e9cf141014e4634d64446702d26e0ff", size = 262825, upload-time = "2025-12-08T13:14:18.68Z" }, - { url = "https://files.pythonhosted.org/packages/37/99/6ee5bf7eff884766edb43bd8736b5e1c5144d0fe47498c3779326fe75a35/coverage-7.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e999e2dcc094002d6e2c7bbc1fb85b58ba4f465a760a8014d97619330cdbbbf3", size = 265233, upload-time = "2025-12-08T13:14:20.55Z" }, - { url = "https://files.pythonhosted.org/packages/d8/90/92f18fe0356ea69e1f98f688ed80cec39f44e9f09a1f26a1bbf017cc67f2/coverage-7.13.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:00c3d22cf6fb1cf3bf662aaaa4e563be8243a5ed2630339069799835a9cc7f9b", size = 259779, upload-time = "2025-12-08T13:14:22.367Z" }, - { url = "https://files.pythonhosted.org/packages/90/5d/b312a8b45b37a42ea7d27d7d3ff98ade3a6c892dd48d1d503e773503373f/coverage-7.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22ccfe8d9bb0d6134892cbe1262493a8c70d736b9df930f3f3afae0fe3ac924d", size = 262700, upload-time = "2025-12-08T13:14:24.309Z" }, - { url = "https://files.pythonhosted.org/packages/63/f8/b1d0de5c39351eb71c366f872376d09386640840a2e09b0d03973d791e20/coverage-7.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9372dff5ea15930fea0445eaf37bbbafbc771a49e70c0aeed8b4e2c2614cc00e", size = 260302, upload-time = "2025-12-08T13:14:26.068Z" }, - { url = "https://files.pythonhosted.org/packages/aa/7c/d42f4435bc40c55558b3109a39e2d456cddcec37434f62a1f1230991667a/coverage-7.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:69ac2c492918c2461bc6ace42d0479638e60719f2a4ef3f0815fa2df88e9f940", size = 259136, upload-time = "2025-12-08T13:14:27.604Z" }, - { url = "https://files.pythonhosted.org/packages/b8/d3/23413241dc04d47cfe19b9a65b32a2edd67ecd0b817400c2843ebc58c847/coverage-7.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:739c6c051a7540608d097b8e13c76cfa85263ced467168dc6b477bae3df7d0e2", size = 261467, upload-time = "2025-12-08T13:14:29.09Z" }, - { url = "https://files.pythonhosted.org/packages/13/e6/6e063174500eee216b96272c0d1847bf215926786f85c2bd024cf4d02d2f/coverage-7.13.0-cp314-cp314t-win32.whl", hash = "sha256:fe81055d8c6c9de76d60c94ddea73c290b416e061d40d542b24a5871bad498b7", size = 221875, upload-time = "2025-12-08T13:14:31.106Z" }, - { url = "https://files.pythonhosted.org/packages/3b/46/f4fb293e4cbe3620e3ac2a3e8fd566ed33affb5861a9b20e3dd6c1896cbc/coverage-7.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:445badb539005283825959ac9fa4a28f712c214b65af3a2c464f1adc90f5fcbc", size = 222982, upload-time = "2025-12-08T13:14:33.1Z" }, - { url = "https://files.pythonhosted.org/packages/68/62/5b3b9018215ed9733fbd1ae3b2ed75c5de62c3b55377a52cae732e1b7805/coverage-7.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:de7f6748b890708578fc4b7bb967d810aeb6fcc9bff4bb77dbca77dab2f9df6a", size = 221016, upload-time = "2025-12-08T13:14:34.601Z" }, - { url = "https://files.pythonhosted.org/packages/8d/4c/1968f32fb9a2604645827e11ff84a31e59d532e01995f904723b4f5328b3/coverage-7.13.0-py3-none-any.whl", hash = "sha256:850d2998f380b1e266459ca5b47bc9e7daf9af1d070f66317972f382d46f1904", size = 210068, upload-time = "2025-12-08T13:14:36.236Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/b6/45/2c665ca77ec32ad67e25c77daf1cee28ee4558f3bc571cdbaf88a00b9f23/coverage-7.13.0.tar.gz", hash = "sha256:a394aa27f2d7ff9bc04cf703817773a59ad6dfbd577032e690f961d2460ee936", size = 820905 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/f1/2619559f17f31ba00fc40908efd1fbf1d0a5536eb75dc8341e7d660a08de/coverage-7.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0b3d67d31383c4c68e19a88e28fc4c2e29517580f1b0ebec4a069d502ce1e0bf", size = 218274 }, + { url = "https://files.pythonhosted.org/packages/2b/11/30d71ae5d6e949ff93b2a79a2c1b4822e00423116c5c6edfaeef37301396/coverage-7.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:581f086833d24a22c89ae0fe2142cfaa1c92c930adf637ddf122d55083fb5a0f", size = 218638 }, + { url = "https://files.pythonhosted.org/packages/79/c2/fce80fc6ded8d77e53207489d6065d0fed75db8951457f9213776615e0f5/coverage-7.13.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a3a30f0e257df382f5f9534d4ce3d4cf06eafaf5192beb1a7bd066cb10e78fb", size = 250129 }, + { url = "https://files.pythonhosted.org/packages/5b/b6/51b5d1eb6fcbb9a1d5d6984e26cbe09018475c2922d554fd724dd0f056ee/coverage-7.13.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:583221913fbc8f53b88c42e8dbb8fca1d0f2e597cb190ce45916662b8b9d9621", size = 252885 }, + { url = "https://files.pythonhosted.org/packages/0d/f8/972a5affea41de798691ab15d023d3530f9f56a72e12e243f35031846ff7/coverage-7.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f5d9bd30756fff3e7216491a0d6d520c448d5124d3d8e8f56446d6412499e74", size = 253974 }, + { url = "https://files.pythonhosted.org/packages/8a/56/116513aee860b2c7968aa3506b0f59b22a959261d1dbf3aea7b4450a7520/coverage-7.13.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a23e5a1f8b982d56fa64f8e442e037f6ce29322f1f9e6c2344cd9e9f4407ee57", size = 250538 }, + { url = "https://files.pythonhosted.org/packages/d6/75/074476d64248fbadf16dfafbf93fdcede389ec821f74ca858d7c87d2a98c/coverage-7.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b01c22bc74a7fb44066aaf765224c0d933ddf1f5047d6cdfe4795504a4493f8", size = 251912 }, + { url = "https://files.pythonhosted.org/packages/f2/d2/aa4f8acd1f7c06024705c12609d8698c51b27e4d635d717cd1934c9668e2/coverage-7.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:898cce66d0836973f48dda4e3514d863d70142bdf6dfab932b9b6a90ea5b222d", size = 250054 }, + { url = "https://files.pythonhosted.org/packages/19/98/8df9e1af6a493b03694a1e8070e024e7d2cdc77adedc225a35e616d505de/coverage-7.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:3ab483ea0e251b5790c2aac03acde31bff0c736bf8a86829b89382b407cd1c3b", size = 249619 }, + { url = "https://files.pythonhosted.org/packages/d8/71/f8679231f3353018ca66ef647fa6fe7b77e6bff7845be54ab84f86233363/coverage-7.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d84e91521c5e4cb6602fe11ece3e1de03b2760e14ae4fcf1a4b56fa3c801fcd", size = 251496 }, + { url = "https://files.pythonhosted.org/packages/04/86/9cb406388034eaf3c606c22094edbbb82eea1fa9d20c0e9efadff20d0733/coverage-7.13.0-cp312-cp312-win32.whl", hash = "sha256:193c3887285eec1dbdb3f2bd7fbc351d570ca9c02ca756c3afbc71b3c98af6ef", size = 220808 }, + { url = "https://files.pythonhosted.org/packages/1c/59/af483673df6455795daf5f447c2f81a3d2fcfc893a22b8ace983791f6f34/coverage-7.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:4f3e223b2b2db5e0db0c2b97286aba0036ca000f06aca9b12112eaa9af3d92ae", size = 221616 }, + { url = "https://files.pythonhosted.org/packages/64/b0/959d582572b30a6830398c60dd419c1965ca4b5fb38ac6b7093a0d50ca8d/coverage-7.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:086cede306d96202e15a4b77ace8472e39d9f4e5f9fd92dd4fecdfb2313b2080", size = 220261 }, + { url = "https://files.pythonhosted.org/packages/7c/cc/bce226595eb3bf7d13ccffe154c3c487a22222d87ff018525ab4dd2e9542/coverage-7.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:28ee1c96109974af104028a8ef57cec21447d42d0e937c0275329272e370ebcf", size = 218297 }, + { url = "https://files.pythonhosted.org/packages/3b/9f/73c4d34600aae03447dff3d7ad1d0ac649856bfb87d1ca7d681cfc913f9e/coverage-7.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e97353dcc5587b85986cda4ff3ec98081d7e84dd95e8b2a6d59820f0545f8a", size = 218673 }, + { url = "https://files.pythonhosted.org/packages/63/ab/8fa097db361a1e8586535ae5073559e6229596b3489ec3ef2f5b38df8cb2/coverage-7.13.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:99acd4dfdfeb58e1937629eb1ab6ab0899b131f183ee5f23e0b5da5cba2fec74", size = 249652 }, + { url = "https://files.pythonhosted.org/packages/90/3a/9bfd4de2ff191feb37ef9465855ca56a6f2f30a3bca172e474130731ac3d/coverage-7.13.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff45e0cd8451e293b63ced93161e189780baf444119391b3e7d25315060368a6", size = 252251 }, + { url = "https://files.pythonhosted.org/packages/df/61/b5d8105f016e1b5874af0d7c67542da780ccd4a5f2244a433d3e20ceb1ad/coverage-7.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4f72a85316d8e13234cafe0a9f81b40418ad7a082792fa4165bd7d45d96066b", size = 253492 }, + { url = "https://files.pythonhosted.org/packages/f3/b8/0fad449981803cc47a4694768b99823fb23632150743f9c83af329bb6090/coverage-7.13.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11c21557d0e0a5a38632cbbaca5f008723b26a89d70db6315523df6df77d6232", size = 249850 }, + { url = "https://files.pythonhosted.org/packages/9a/e9/8d68337c3125014d918cf4327d5257553a710a2995a6a6de2ac77e5aa429/coverage-7.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76541dc8d53715fb4f7a3a06b34b0dc6846e3c69bc6204c55653a85dd6220971", size = 251633 }, + { url = "https://files.pythonhosted.org/packages/55/14/d4112ab26b3a1bc4b3c1295d8452dcf399ed25be4cf649002fb3e64b2d93/coverage-7.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6e9e451dee940a86789134b6b0ffbe31c454ade3b849bb8a9d2cca2541a8e91d", size = 249586 }, + { url = "https://files.pythonhosted.org/packages/2c/a9/22b0000186db663b0d82f86c2f1028099ae9ac202491685051e2a11a5218/coverage-7.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5c67dace46f361125e6b9cace8fe0b729ed8479f47e70c89b838d319375c8137", size = 249412 }, + { url = "https://files.pythonhosted.org/packages/a1/2e/42d8e0d9e7527fba439acdc6ed24a2b97613b1dc85849b1dd935c2cffef0/coverage-7.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f59883c643cb19630500f57016f76cfdcd6845ca8c5b5ea1f6e17f74c8e5f511", size = 251191 }, + { url = "https://files.pythonhosted.org/packages/a4/af/8c7af92b1377fd8860536aadd58745119252aaaa71a5213e5a8e8007a9f5/coverage-7.13.0-cp313-cp313-win32.whl", hash = "sha256:58632b187be6f0be500f553be41e277712baa278147ecb7559983c6d9faf7ae1", size = 220829 }, + { url = "https://files.pythonhosted.org/packages/58/f9/725e8bf16f343d33cbe076c75dc8370262e194ff10072c0608b8e5cf33a3/coverage-7.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:73419b89f812f498aca53f757dd834919b48ce4799f9d5cad33ca0ae442bdb1a", size = 221640 }, + { url = "https://files.pythonhosted.org/packages/8a/ff/e98311000aa6933cc79274e2b6b94a2fe0fe3434fca778eba82003675496/coverage-7.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb76670874fdd6091eedcc856128ee48c41a9bbbb9c3f1c7c3cf169290e3ffd6", size = 220269 }, + { url = "https://files.pythonhosted.org/packages/cf/cf/bbaa2e1275b300343ea865f7d424cc0a2e2a1df6925a070b2b2d5d765330/coverage-7.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6e63ccc6e0ad8986386461c3c4b737540f20426e7ec932f42e030320896c311a", size = 218990 }, + { url = "https://files.pythonhosted.org/packages/21/1d/82f0b3323b3d149d7672e7744c116e9c170f4957e0c42572f0366dbb4477/coverage-7.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:494f5459ffa1bd45e18558cd98710c36c0b8fbfa82a5eabcbe671d80ecffbfe8", size = 219340 }, + { url = "https://files.pythonhosted.org/packages/fb/e3/fe3fd4702a3832a255f4d43013eacb0ef5fc155a5960ea9269d8696db28b/coverage-7.13.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:06cac81bf10f74034e055e903f5f946e3e26fc51c09fc9f584e4a1605d977053", size = 260638 }, + { url = "https://files.pythonhosted.org/packages/ad/01/63186cb000307f2b4da463f72af9b85d380236965574c78e7e27680a2593/coverage-7.13.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f2ffc92b46ed6e6760f1d47a71e56b5664781bc68986dbd1836b2b70c0ce2071", size = 262705 }, + { url = "https://files.pythonhosted.org/packages/7c/a1/c0dacef0cc865f2455d59eed3548573ce47ed603205ffd0735d1d78b5906/coverage-7.13.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0602f701057c6823e5db1b74530ce85f17c3c5be5c85fc042ac939cbd909426e", size = 265125 }, + { url = "https://files.pythonhosted.org/packages/ef/92/82b99223628b61300bd382c205795533bed021505eab6dd86e11fb5d7925/coverage-7.13.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:25dc33618d45456ccb1d37bce44bc78cf269909aa14c4db2e03d63146a8a1493", size = 259844 }, + { url = "https://files.pythonhosted.org/packages/cf/2c/89b0291ae4e6cd59ef042708e1c438e2290f8c31959a20055d8768349ee2/coverage-7.13.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:71936a8b3b977ddd0b694c28c6a34f4fff2e9dd201969a4ff5d5fc7742d614b0", size = 262700 }, + { url = "https://files.pythonhosted.org/packages/bf/f9/a5f992efae1996245e796bae34ceb942b05db275e4b34222a9a40b9fbd3b/coverage-7.13.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:936bc20503ce24770c71938d1369461f0c5320830800933bc3956e2a4ded930e", size = 260321 }, + { url = "https://files.pythonhosted.org/packages/4c/89/a29f5d98c64fedbe32e2ac3c227fbf78edc01cc7572eee17d61024d89889/coverage-7.13.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:af0a583efaacc52ae2521f8d7910aff65cdb093091d76291ac5820d5e947fc1c", size = 259222 }, + { url = "https://files.pythonhosted.org/packages/b3/c3/940fe447aae302a6701ee51e53af7e08b86ff6eed7631e5740c157ee22b9/coverage-7.13.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f1c23e24a7000da892a312fb17e33c5f94f8b001de44b7cf8ba2e36fbd15859e", size = 261411 }, + { url = "https://files.pythonhosted.org/packages/eb/31/12a4aec689cb942a89129587860ed4d0fd522d5fda81237147fde554b8ae/coverage-7.13.0-cp313-cp313t-win32.whl", hash = "sha256:5f8a0297355e652001015e93be345ee54393e45dc3050af4a0475c5a2b767d46", size = 221505 }, + { url = "https://files.pythonhosted.org/packages/65/8c/3b5fe3259d863572d2b0827642c50c3855d26b3aefe80bdc9eba1f0af3b0/coverage-7.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6abb3a4c52f05e08460bd9acf04fec027f8718ecaa0d09c40ffbc3fbd70ecc39", size = 222569 }, + { url = "https://files.pythonhosted.org/packages/b0/39/f71fa8316a96ac72fc3908839df651e8eccee650001a17f2c78cdb355624/coverage-7.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:3ad968d1e3aa6ce5be295ab5fe3ae1bf5bb4769d0f98a80a0252d543a2ef2e9e", size = 220841 }, + { url = "https://files.pythonhosted.org/packages/f8/4b/9b54bedda55421449811dcd5263a2798a63f48896c24dfb92b0f1b0845bd/coverage-7.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:453b7ec753cf5e4356e14fe858064e5520c460d3bbbcb9c35e55c0d21155c256", size = 218343 }, + { url = "https://files.pythonhosted.org/packages/59/df/c3a1f34d4bba2e592c8979f924da4d3d4598b0df2392fbddb7761258e3dc/coverage-7.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:af827b7cbb303e1befa6c4f94fd2bf72f108089cfa0f8abab8f4ca553cf5ca5a", size = 218672 }, + { url = "https://files.pythonhosted.org/packages/07/62/eec0659e47857698645ff4e6ad02e30186eb8afd65214fd43f02a76537cb/coverage-7.13.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9987a9e4f8197a1000280f7cc089e3ea2c8b3c0a64d750537809879a7b4ceaf9", size = 249715 }, + { url = "https://files.pythonhosted.org/packages/23/2d/3c7ff8b2e0e634c1f58d095f071f52ed3c23ff25be524b0ccae8b71f99f8/coverage-7.13.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3188936845cd0cb114fa6a51842a304cdbac2958145d03be2377ec41eb285d19", size = 252225 }, + { url = "https://files.pythonhosted.org/packages/aa/ac/fb03b469d20e9c9a81093575003f959cf91a4a517b783aab090e4538764b/coverage-7.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2bdb3babb74079f021696cb46b8bb5f5661165c385d3a238712b031a12355be", size = 253559 }, + { url = "https://files.pythonhosted.org/packages/29/62/14afa9e792383c66cc0a3b872a06ded6e4ed1079c7d35de274f11d27064e/coverage-7.13.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7464663eaca6adba4175f6c19354feea61ebbdd735563a03d1e472c7072d27bb", size = 249724 }, + { url = "https://files.pythonhosted.org/packages/31/b7/333f3dab2939070613696ab3ee91738950f0467778c6e5a5052e840646b7/coverage-7.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8069e831f205d2ff1f3d355e82f511eb7c5522d7d413f5db5756b772ec8697f8", size = 251582 }, + { url = "https://files.pythonhosted.org/packages/81/cb/69162bda9381f39b2287265d7e29ee770f7c27c19f470164350a38318764/coverage-7.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6fb2d5d272341565f08e962cce14cdf843a08ac43bd621783527adb06b089c4b", size = 249538 }, + { url = "https://files.pythonhosted.org/packages/e0/76/350387b56a30f4970abe32b90b2a434f87d29f8b7d4ae40d2e8a85aacfb3/coverage-7.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5e70f92ef89bac1ac8a99b3324923b4749f008fdbd7aa9cb35e01d7a284a04f9", size = 249349 }, + { url = "https://files.pythonhosted.org/packages/86/0d/7f6c42b8d59f4c7e43ea3059f573c0dcfed98ba46eb43c68c69e52ae095c/coverage-7.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4b5de7d4583e60d5fd246dd57fcd3a8aa23c6e118a8c72b38adf666ba8e7e927", size = 251011 }, + { url = "https://files.pythonhosted.org/packages/d7/f1/4bb2dff379721bb0b5c649d5c5eaf438462cad824acf32eb1b7ca0c7078e/coverage-7.13.0-cp314-cp314-win32.whl", hash = "sha256:a6c6e16b663be828a8f0b6c5027d36471d4a9f90d28444aa4ced4d48d7d6ae8f", size = 221091 }, + { url = "https://files.pythonhosted.org/packages/ba/44/c239da52f373ce379c194b0ee3bcc121020e397242b85f99e0afc8615066/coverage-7.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:0900872f2fdb3ee5646b557918d02279dc3af3dfb39029ac4e945458b13f73bc", size = 221904 }, + { url = "https://files.pythonhosted.org/packages/89/1f/b9f04016d2a29c2e4a0307baefefad1a4ec5724946a2b3e482690486cade/coverage-7.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:3a10260e6a152e5f03f26db4a407c4c62d3830b9af9b7c0450b183615f05d43b", size = 220480 }, + { url = "https://files.pythonhosted.org/packages/16/d4/364a1439766c8e8647860584171c36010ca3226e6e45b1753b1b249c5161/coverage-7.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9097818b6cc1cfb5f174e3263eba4a62a17683bcfe5c4b5d07f4c97fa51fbf28", size = 219074 }, + { url = "https://files.pythonhosted.org/packages/ce/f4/71ba8be63351e099911051b2089662c03d5671437a0ec2171823c8e03bec/coverage-7.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0018f73dfb4301a89292c73be6ba5f58722ff79f51593352759c1790ded1cabe", size = 219342 }, + { url = "https://files.pythonhosted.org/packages/5e/25/127d8ed03d7711a387d96f132589057213e3aef7475afdaa303412463f22/coverage-7.13.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:166ad2a22ee770f5656e1257703139d3533b4a0b6909af67c6b4a3adc1c98657", size = 260713 }, + { url = "https://files.pythonhosted.org/packages/fd/db/559fbb6def07d25b2243663b46ba9eb5a3c6586c0c6f4e62980a68f0ee1c/coverage-7.13.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f6aaef16d65d1787280943f1c8718dc32e9cf141014e4634d64446702d26e0ff", size = 262825 }, + { url = "https://files.pythonhosted.org/packages/37/99/6ee5bf7eff884766edb43bd8736b5e1c5144d0fe47498c3779326fe75a35/coverage-7.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e999e2dcc094002d6e2c7bbc1fb85b58ba4f465a760a8014d97619330cdbbbf3", size = 265233 }, + { url = "https://files.pythonhosted.org/packages/d8/90/92f18fe0356ea69e1f98f688ed80cec39f44e9f09a1f26a1bbf017cc67f2/coverage-7.13.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:00c3d22cf6fb1cf3bf662aaaa4e563be8243a5ed2630339069799835a9cc7f9b", size = 259779 }, + { url = "https://files.pythonhosted.org/packages/90/5d/b312a8b45b37a42ea7d27d7d3ff98ade3a6c892dd48d1d503e773503373f/coverage-7.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22ccfe8d9bb0d6134892cbe1262493a8c70d736b9df930f3f3afae0fe3ac924d", size = 262700 }, + { url = "https://files.pythonhosted.org/packages/63/f8/b1d0de5c39351eb71c366f872376d09386640840a2e09b0d03973d791e20/coverage-7.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9372dff5ea15930fea0445eaf37bbbafbc771a49e70c0aeed8b4e2c2614cc00e", size = 260302 }, + { url = "https://files.pythonhosted.org/packages/aa/7c/d42f4435bc40c55558b3109a39e2d456cddcec37434f62a1f1230991667a/coverage-7.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:69ac2c492918c2461bc6ace42d0479638e60719f2a4ef3f0815fa2df88e9f940", size = 259136 }, + { url = "https://files.pythonhosted.org/packages/b8/d3/23413241dc04d47cfe19b9a65b32a2edd67ecd0b817400c2843ebc58c847/coverage-7.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:739c6c051a7540608d097b8e13c76cfa85263ced467168dc6b477bae3df7d0e2", size = 261467 }, + { url = "https://files.pythonhosted.org/packages/13/e6/6e063174500eee216b96272c0d1847bf215926786f85c2bd024cf4d02d2f/coverage-7.13.0-cp314-cp314t-win32.whl", hash = "sha256:fe81055d8c6c9de76d60c94ddea73c290b416e061d40d542b24a5871bad498b7", size = 221875 }, + { url = "https://files.pythonhosted.org/packages/3b/46/f4fb293e4cbe3620e3ac2a3e8fd566ed33affb5861a9b20e3dd6c1896cbc/coverage-7.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:445badb539005283825959ac9fa4a28f712c214b65af3a2c464f1adc90f5fcbc", size = 222982 }, + { url = "https://files.pythonhosted.org/packages/68/62/5b3b9018215ed9733fbd1ae3b2ed75c5de62c3b55377a52cae732e1b7805/coverage-7.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:de7f6748b890708578fc4b7bb967d810aeb6fcc9bff4bb77dbca77dab2f9df6a", size = 221016 }, + { url = "https://files.pythonhosted.org/packages/8d/4c/1968f32fb9a2604645827e11ff84a31e59d532e01995f904723b4f5328b3/coverage-7.13.0-py3-none-any.whl", hash = "sha256:850d2998f380b1e266459ca5b47bc9e7daf9af1d070f66317972f382d46f1904", size = 210068 }, ] [[package]] name = "gcpath" -version = "0.9.0" +version = "0.10.0" source = { editable = "." } dependencies = [ { name = "google-cloud-asset" }, { name = "google-cloud-resource-manager" }, { name = "pyyaml" }, { name = "rich" }, + { name = "toon-format" }, { name = "typer" }, ] @@ -204,6 +205,7 @@ requires-dist = [ { name = "google-cloud-resource-manager", specifier = ">=1.15.0" }, { name = "pyyaml", specifier = ">=6.0" }, { name = "rich", specifier = ">=14.2.0" }, + { name = "toon-format", git = "https://github.com/toon-format/toon-python.git?rev=90861444e5bf7d6408e91bd95e58dba41dd99be8" }, { name = "typer", specifier = ">=0.20.1" }, ] @@ -227,9 +229,9 @@ dependencies = [ { name = "protobuf" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/61/da/83d7043169ac2c8c7469f0e375610d78ae2160134bf1b80634c482fa079c/google_api_core-2.28.1.tar.gz", hash = "sha256:2b405df02d68e68ce0fbc138559e6036559e685159d148ae5861013dc201baf8", size = 176759, upload-time = "2025-10-28T21:34:51.529Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/da/83d7043169ac2c8c7469f0e375610d78ae2160134bf1b80634c482fa079c/google_api_core-2.28.1.tar.gz", hash = "sha256:2b405df02d68e68ce0fbc138559e6036559e685159d148ae5861013dc201baf8", size = 176759 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/d4/90197b416cb61cefd316964fd9e7bd8324bcbafabf40eef14a9f20b81974/google_api_core-2.28.1-py3-none-any.whl", hash = "sha256:4021b0f8ceb77a6fb4de6fde4502cecab45062e66ff4f2895169e0b35bc9466c", size = 173706, upload-time = "2025-10-28T21:34:50.151Z" }, + { url = "https://files.pythonhosted.org/packages/ed/d4/90197b416cb61cefd316964fd9e7bd8324bcbafabf40eef14a9f20b81974/google_api_core-2.28.1-py3-none-any.whl", hash = "sha256:4021b0f8ceb77a6fb4de6fde4502cecab45062e66ff4f2895169e0b35bc9466c", size = 173706 }, ] [package.optional-dependencies] @@ -247,9 +249,9 @@ dependencies = [ { name = "pyasn1-modules" }, { name = "rsa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e5/00/3c794502a8b892c404b2dea5b3650eb21bfc7069612fbfd15c7f17c1cb0d/google_auth-2.45.0.tar.gz", hash = "sha256:90d3f41b6b72ea72dd9811e765699ee491ab24139f34ebf1ca2b9cc0c38708f3", size = 320708, upload-time = "2025-12-15T22:58:42.889Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/00/3c794502a8b892c404b2dea5b3650eb21bfc7069612fbfd15c7f17c1cb0d/google_auth-2.45.0.tar.gz", hash = "sha256:90d3f41b6b72ea72dd9811e765699ee491ab24139f34ebf1ca2b9cc0c38708f3", size = 320708 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/97/451d55e05487a5cd6279a01a7e34921858b16f7dc8aa38a2c684743cd2b3/google_auth-2.45.0-py2.py3-none-any.whl", hash = "sha256:82344e86dc00410ef5382d99be677c6043d72e502b625aa4f4afa0bdacca0f36", size = 233312, upload-time = "2025-12-15T22:58:40.777Z" }, + { url = "https://files.pythonhosted.org/packages/c6/97/451d55e05487a5cd6279a01a7e34921858b16f7dc8aa38a2c684743cd2b3/google_auth-2.45.0-py2.py3-none-any.whl", hash = "sha256:82344e86dc00410ef5382d99be677c6043d72e502b625aa4f4afa0bdacca0f36", size = 233312 }, ] [[package]] @@ -260,9 +262,9 @@ dependencies = [ { name = "google-api-core", extra = ["grpc"] }, { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ca/eb/f8dcd4e3c596733f872732b43836df6486fa09966ce5586443d4e9e288fb/google_cloud_access_context_manager-0.3.0.tar.gz", hash = "sha256:f3aa35c9225b7aaef85ecdacedcc1577789be8d458b7a41b6ad23b504786e5f9", size = 46643, upload-time = "2025-10-17T02:33:32.028Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/eb/f8dcd4e3c596733f872732b43836df6486fa09966ce5586443d4e9e288fb/google_cloud_access_context_manager-0.3.0.tar.gz", hash = "sha256:f3aa35c9225b7aaef85ecdacedcc1577789be8d458b7a41b6ad23b504786e5f9", size = 46643 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/94/24b010493660dd55e2d9769ae7ef44164aebd7e1f4a9266cf9459affd687/google_cloud_access_context_manager-0.3.0-py3-none-any.whl", hash = "sha256:5d15ad51547f06c281e35f16b4ffcb3e98bb2d898b01470f88b94edfb2eeb0a3", size = 58852, upload-time = "2025-10-17T02:30:33.768Z" }, + { url = "https://files.pythonhosted.org/packages/ca/94/24b010493660dd55e2d9769ae7ef44164aebd7e1f4a9266cf9459affd687/google_cloud_access_context_manager-0.3.0-py3-none-any.whl", hash = "sha256:5d15ad51547f06c281e35f16b4ffcb3e98bb2d898b01470f88b94edfb2eeb0a3", size = 58852 }, ] [[package]] @@ -280,9 +282,9 @@ dependencies = [ { name = "proto-plus" }, { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/4b/ea523f677cf7dcfeeee85e6186c35b0500d53aa68711ec103bb1f90871f5/google_cloud_asset-4.1.0.tar.gz", hash = "sha256:00ba110085ff9f284b49961bcb9d2da5b5863fb91643c16d173ed38d73bfe35c", size = 294177, upload-time = "2025-10-17T02:33:43.054Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/4b/ea523f677cf7dcfeeee85e6186c35b0500d53aa68711ec103bb1f90871f5/google_cloud_asset-4.1.0.tar.gz", hash = "sha256:00ba110085ff9f284b49961bcb9d2da5b5863fb91643c16d173ed38d73bfe35c", size = 294177 } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/10/d4b47be9ebbc4aeb6f951af91a1af0991245715f4c5ecdfa8015a58b6272/google_cloud_asset-4.1.0-py3-none-any.whl", hash = "sha256:2ce567bf002ad5099e173f6106393a7abc4782598c5d0d27de7d86ac26736e06", size = 275926, upload-time = "2025-10-17T02:30:52.497Z" }, + { url = "https://files.pythonhosted.org/packages/01/10/d4b47be9ebbc4aeb6f951af91a1af0991245715f4c5ecdfa8015a58b6272/google_cloud_asset-4.1.0-py3-none-any.whl", hash = "sha256:2ce567bf002ad5099e173f6106393a7abc4782598c5d0d27de7d86ac26736e06", size = 275926 }, ] [[package]] @@ -296,9 +298,9 @@ dependencies = [ { name = "proto-plus" }, { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/16/c431329766bf5a22f3acd89673153166b24ecd03c0d07af28cf2a06c710c/google_cloud_org_policy-1.15.0.tar.gz", hash = "sha256:271d16a10e75347eace60d02cde322b2b1b613bcc99917109e0ebf2a4102253a", size = 97541, upload-time = "2025-10-20T14:56:42.07Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/16/c431329766bf5a22f3acd89673153166b24ecd03c0d07af28cf2a06c710c/google_cloud_org_policy-1.15.0.tar.gz", hash = "sha256:271d16a10e75347eace60d02cde322b2b1b613bcc99917109e0ebf2a4102253a", size = 97541 } wheels = [ - { url = "https://files.pythonhosted.org/packages/66/e8/a8a1c85c0daf025a26b71cb165cce013bcea664163ddc2cc9335c5dedec5/google_cloud_org_policy-1.15.0-py3-none-any.whl", hash = "sha256:5da410288236b334b8d05010501ea6180c5dc9e30888ff09488f2f107632f35b", size = 83913, upload-time = "2025-10-20T14:53:23.471Z" }, + { url = "https://files.pythonhosted.org/packages/66/e8/a8a1c85c0daf025a26b71cb165cce013bcea664163ddc2cc9335c5dedec5/google_cloud_org_policy-1.15.0-py3-none-any.whl", hash = "sha256:5da410288236b334b8d05010501ea6180c5dc9e30888ff09488f2f107632f35b", size = 83913 }, ] [[package]] @@ -312,9 +314,9 @@ dependencies = [ { name = "proto-plus" }, { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/33/8acc596c684e60597dc0ceec8fec06308879a157a50fd290fa6b61eb013d/google_cloud_os_config-1.22.0.tar.gz", hash = "sha256:d79a310f6fa1ce7470aaa084c70e38dc05d98531f468f821b3a526e4d33a70e4", size = 298029, upload-time = "2025-10-20T14:56:42.974Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/33/8acc596c684e60597dc0ceec8fec06308879a157a50fd290fa6b61eb013d/google_cloud_os_config-1.22.0.tar.gz", hash = "sha256:d79a310f6fa1ce7470aaa084c70e38dc05d98531f468f821b3a526e4d33a70e4", size = 298029 } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/d0/3f481524e2ac7b18531cb617b3fb694dbc350c5ea1f2eb2ddd978708c7e8/google_cloud_os_config-1.22.0-py3-none-any.whl", hash = "sha256:92afa402aa3b94d765751907fded1ef5908a6a5322f1fc88dee9e4c7f1cd7e54", size = 261062, upload-time = "2025-10-20T14:53:24.485Z" }, + { url = "https://files.pythonhosted.org/packages/06/d0/3f481524e2ac7b18531cb617b3fb694dbc350c5ea1f2eb2ddd978708c7e8/google_cloud_os_config-1.22.0-py3-none-any.whl", hash = "sha256:92afa402aa3b94d765751907fded1ef5908a6a5322f1fc88dee9e4c7f1cd7e54", size = 261062 }, ] [[package]] @@ -329,9 +331,9 @@ dependencies = [ { name = "proto-plus" }, { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/19/b95d0e8814ce42522e434cdd85c0cb6236d874d9adf6685fc8e6d1fda9d1/google_cloud_resource_manager-1.15.0.tar.gz", hash = "sha256:3d0b78c3daa713f956d24e525b35e9e9a76d597c438837171304d431084cedaf", size = 449227, upload-time = "2025-10-20T14:57:01.108Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/19/b95d0e8814ce42522e434cdd85c0cb6236d874d9adf6685fc8e6d1fda9d1/google_cloud_resource_manager-1.15.0.tar.gz", hash = "sha256:3d0b78c3daa713f956d24e525b35e9e9a76d597c438837171304d431084cedaf", size = 449227 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/93/5aef41a5f146ad4559dd7040ae5fa8e7ddcab4dfadbef6cb4b66d775e690/google_cloud_resource_manager-1.15.0-py3-none-any.whl", hash = "sha256:0ccde5db644b269ddfdf7b407a2c7b60bdbf459f8e666344a5285601d00c7f6d", size = 397151, upload-time = "2025-10-20T14:53:45.409Z" }, + { url = "https://files.pythonhosted.org/packages/8c/93/5aef41a5f146ad4559dd7040ae5fa8e7ddcab4dfadbef6cb4b66d775e690/google_cloud_resource_manager-1.15.0-py3-none-any.whl", hash = "sha256:0ccde5db644b269ddfdf7b407a2c7b60bdbf459f8e666344a5285601d00c7f6d", size = 397151 }, ] [[package]] @@ -341,9 +343,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e5/7b/adfd75544c415c487b33061fe7ae526165241c1ea133f9a9125a56b39fd8/googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5", size = 147433, upload-time = "2025-11-06T18:29:24.087Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/7b/adfd75544c415c487b33061fe7ae526165241c1ea133f9a9125a56b39fd8/googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5", size = 147433 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/ab/09169d5a4612a5f92490806649ac8d41e3ec9129c636754575b3553f4ea4/googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038", size = 297515, upload-time = "2025-11-06T18:29:13.14Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ab/09169d5a4612a5f92490806649ac8d41e3ec9129c636754575b3553f4ea4/googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038", size = 297515 }, ] [package.optional-dependencies] @@ -360,9 +362,9 @@ dependencies = [ { name = "grpcio" }, { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/76/1e/1011451679a983f2f5c6771a1682542ecb027776762ad031fd0d7129164b/grpc_google_iam_v1-0.14.3.tar.gz", hash = "sha256:879ac4ef33136c5491a6300e27575a9ec760f6cdf9a2518798c1b8977a5dc389", size = 23745, upload-time = "2025-10-15T21:14:53.318Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/1e/1011451679a983f2f5c6771a1682542ecb027776762ad031fd0d7129164b/grpc_google_iam_v1-0.14.3.tar.gz", hash = "sha256:879ac4ef33136c5491a6300e27575a9ec760f6cdf9a2518798c1b8977a5dc389", size = 23745 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/bd/330a1bbdb1afe0b96311249e699b6dc9cfc17916394fd4503ac5aca2514b/grpc_google_iam_v1-0.14.3-py3-none-any.whl", hash = "sha256:7a7f697e017a067206a3dfef44e4c634a34d3dee135fe7d7a4613fe3e59217e6", size = 32690, upload-time = "2025-10-15T21:14:51.72Z" }, + { url = "https://files.pythonhosted.org/packages/4a/bd/330a1bbdb1afe0b96311249e699b6dc9cfc17916394fd4503ac5aca2514b/grpc_google_iam_v1-0.14.3-py3-none-any.whl", hash = "sha256:7a7f697e017a067206a3dfef44e4c634a34d3dee135fe7d7a4613fe3e59217e6", size = 32690 }, ] [[package]] @@ -372,38 +374,38 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b6/e0/318c1ce3ae5a17894d5791e87aea147587c9e702f24122cc7a5c8bbaeeb1/grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73", size = 12785182, upload-time = "2025-10-21T16:23:12.106Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/05/8e29121994b8d959ffa0afd28996d452f291b48cfc0875619de0bde2c50c/grpcio-1.76.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8", size = 5799718, upload-time = "2025-10-21T16:21:17.939Z" }, - { url = "https://files.pythonhosted.org/packages/d9/75/11d0e66b3cdf998c996489581bdad8900db79ebd83513e45c19548f1cba4/grpcio-1.76.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:04bbe1bfe3a68bbfd4e52402ab7d4eb59d72d02647ae2042204326cf4bbad280", size = 11825627, upload-time = "2025-10-21T16:21:20.466Z" }, - { url = "https://files.pythonhosted.org/packages/28/50/2f0aa0498bc188048f5d9504dcc5c2c24f2eb1a9337cd0fa09a61a2e75f0/grpcio-1.76.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d388087771c837cdb6515539f43b9d4bf0b0f23593a24054ac16f7a960be16f4", size = 6359167, upload-time = "2025-10-21T16:21:23.122Z" }, - { url = "https://files.pythonhosted.org/packages/66/e5/bbf0bb97d29ede1d59d6588af40018cfc345b17ce979b7b45424628dc8bb/grpcio-1.76.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f8f757bebaaea112c00dba718fc0d3260052ce714e25804a03f93f5d1c6cc11", size = 7044267, upload-time = "2025-10-21T16:21:25.995Z" }, - { url = "https://files.pythonhosted.org/packages/f5/86/f6ec2164f743d9609691115ae8ece098c76b894ebe4f7c94a655c6b03e98/grpcio-1.76.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:980a846182ce88c4f2f7e2c22c56aefd515daeb36149d1c897f83cf57999e0b6", size = 6573963, upload-time = "2025-10-21T16:21:28.631Z" }, - { url = "https://files.pythonhosted.org/packages/60/bc/8d9d0d8505feccfdf38a766d262c71e73639c165b311c9457208b56d92ae/grpcio-1.76.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f92f88e6c033db65a5ae3d97905c8fea9c725b63e28d5a75cb73b49bda5024d8", size = 7164484, upload-time = "2025-10-21T16:21:30.837Z" }, - { url = "https://files.pythonhosted.org/packages/67/e6/5d6c2fc10b95edf6df9b8f19cf10a34263b7fd48493936fffd5085521292/grpcio-1.76.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4baf3cbe2f0be3289eb68ac8ae771156971848bb8aaff60bad42005539431980", size = 8127777, upload-time = "2025-10-21T16:21:33.577Z" }, - { url = "https://files.pythonhosted.org/packages/3f/c8/dce8ff21c86abe025efe304d9e31fdb0deaaa3b502b6a78141080f206da0/grpcio-1.76.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:615ba64c208aaceb5ec83bfdce7728b80bfeb8be97562944836a7a0a9647d882", size = 7594014, upload-time = "2025-10-21T16:21:41.882Z" }, - { url = "https://files.pythonhosted.org/packages/e0/42/ad28191ebf983a5d0ecef90bab66baa5a6b18f2bfdef9d0a63b1973d9f75/grpcio-1.76.0-cp312-cp312-win32.whl", hash = "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958", size = 3984750, upload-time = "2025-10-21T16:21:44.006Z" }, - { url = "https://files.pythonhosted.org/packages/9e/00/7bd478cbb851c04a48baccaa49b75abaa8e4122f7d86da797500cccdd771/grpcio-1.76.0-cp312-cp312-win_amd64.whl", hash = "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347", size = 4704003, upload-time = "2025-10-21T16:21:46.244Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ed/71467ab770effc9e8cef5f2e7388beb2be26ed642d567697bb103a790c72/grpcio-1.76.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:26ef06c73eb53267c2b319f43e6634c7556ea37672029241a056629af27c10e2", size = 5807716, upload-time = "2025-10-21T16:21:48.475Z" }, - { url = "https://files.pythonhosted.org/packages/2c/85/c6ed56f9817fab03fa8a111ca91469941fb514e3e3ce6d793cb8f1e1347b/grpcio-1.76.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:45e0111e73f43f735d70786557dc38141185072d7ff8dc1829d6a77ac1471468", size = 11821522, upload-time = "2025-10-21T16:21:51.142Z" }, - { url = "https://files.pythonhosted.org/packages/ac/31/2b8a235ab40c39cbc141ef647f8a6eb7b0028f023015a4842933bc0d6831/grpcio-1.76.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83d57312a58dcfe2a3a0f9d1389b299438909a02db60e2f2ea2ae2d8034909d3", size = 6362558, upload-time = "2025-10-21T16:21:54.213Z" }, - { url = "https://files.pythonhosted.org/packages/bd/64/9784eab483358e08847498ee56faf8ff6ea8e0a4592568d9f68edc97e9e9/grpcio-1.76.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3e2a27c89eb9ac3d81ec8835e12414d73536c6e620355d65102503064a4ed6eb", size = 7049990, upload-time = "2025-10-21T16:21:56.476Z" }, - { url = "https://files.pythonhosted.org/packages/2b/94/8c12319a6369434e7a184b987e8e9f3b49a114c489b8315f029e24de4837/grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61f69297cba3950a524f61c7c8ee12e55c486cb5f7db47ff9dcee33da6f0d3ae", size = 6575387, upload-time = "2025-10-21T16:21:59.051Z" }, - { url = "https://files.pythonhosted.org/packages/15/0f/f12c32b03f731f4a6242f771f63039df182c8b8e2cf8075b245b409259d4/grpcio-1.76.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a15c17af8839b6801d554263c546c69c4d7718ad4321e3166175b37eaacca77", size = 7166668, upload-time = "2025-10-21T16:22:02.049Z" }, - { url = "https://files.pythonhosted.org/packages/ff/2d/3ec9ce0c2b1d92dd59d1c3264aaec9f0f7c817d6e8ac683b97198a36ed5a/grpcio-1.76.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:25a18e9810fbc7e7f03ec2516addc116a957f8cbb8cbc95ccc80faa072743d03", size = 8124928, upload-time = "2025-10-21T16:22:04.984Z" }, - { url = "https://files.pythonhosted.org/packages/1a/74/fd3317be5672f4856bcdd1a9e7b5e17554692d3db9a3b273879dc02d657d/grpcio-1.76.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:931091142fd8cc14edccc0845a79248bc155425eee9a98b2db2ea4f00a235a42", size = 7589983, upload-time = "2025-10-21T16:22:07.881Z" }, - { url = "https://files.pythonhosted.org/packages/45/bb/ca038cf420f405971f19821c8c15bcbc875505f6ffadafe9ffd77871dc4c/grpcio-1.76.0-cp313-cp313-win32.whl", hash = "sha256:5e8571632780e08526f118f74170ad8d50fb0a48c23a746bef2a6ebade3abd6f", size = 3984727, upload-time = "2025-10-21T16:22:10.032Z" }, - { url = "https://files.pythonhosted.org/packages/41/80/84087dc56437ced7cdd4b13d7875e7439a52a261e3ab4e06488ba6173b0a/grpcio-1.76.0-cp313-cp313-win_amd64.whl", hash = "sha256:f9f7bd5faab55f47231ad8dba7787866b69f5e93bc306e3915606779bbfb4ba8", size = 4702799, upload-time = "2025-10-21T16:22:12.709Z" }, - { url = "https://files.pythonhosted.org/packages/b4/46/39adac80de49d678e6e073b70204091e76631e03e94928b9ea4ecf0f6e0e/grpcio-1.76.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:ff8a59ea85a1f2191a0ffcc61298c571bc566332f82e5f5be1b83c9d8e668a62", size = 5808417, upload-time = "2025-10-21T16:22:15.02Z" }, - { url = "https://files.pythonhosted.org/packages/9c/f5/a4531f7fb8b4e2a60b94e39d5d924469b7a6988176b3422487be61fe2998/grpcio-1.76.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:06c3d6b076e7b593905d04fdba6a0525711b3466f43b3400266f04ff735de0cd", size = 11828219, upload-time = "2025-10-21T16:22:17.954Z" }, - { url = "https://files.pythonhosted.org/packages/4b/1c/de55d868ed7a8bd6acc6b1d6ddc4aa36d07a9f31d33c912c804adb1b971b/grpcio-1.76.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd5ef5932f6475c436c4a55e4336ebbe47bd3272be04964a03d316bbf4afbcbc", size = 6367826, upload-time = "2025-10-21T16:22:20.721Z" }, - { url = "https://files.pythonhosted.org/packages/59/64/99e44c02b5adb0ad13ab3adc89cb33cb54bfa90c74770f2607eea629b86f/grpcio-1.76.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b331680e46239e090f5b3cead313cc772f6caa7d0fc8de349337563125361a4a", size = 7049550, upload-time = "2025-10-21T16:22:23.637Z" }, - { url = "https://files.pythonhosted.org/packages/43/28/40a5be3f9a86949b83e7d6a2ad6011d993cbe9b6bd27bea881f61c7788b6/grpcio-1.76.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2229ae655ec4e8999599469559e97630185fdd53ae1e8997d147b7c9b2b72cba", size = 6575564, upload-time = "2025-10-21T16:22:26.016Z" }, - { url = "https://files.pythonhosted.org/packages/4b/a9/1be18e6055b64467440208a8559afac243c66a8b904213af6f392dc2212f/grpcio-1.76.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:490fa6d203992c47c7b9e4a9d39003a0c2bcc1c9aa3c058730884bbbb0ee9f09", size = 7176236, upload-time = "2025-10-21T16:22:28.362Z" }, - { url = "https://files.pythonhosted.org/packages/0f/55/dba05d3fcc151ce6e81327541d2cc8394f442f6b350fead67401661bf041/grpcio-1.76.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:479496325ce554792dba6548fae3df31a72cef7bad71ca2e12b0e58f9b336bfc", size = 8125795, upload-time = "2025-10-21T16:22:31.075Z" }, - { url = "https://files.pythonhosted.org/packages/4a/45/122df922d05655f63930cf42c9e3f72ba20aadb26c100ee105cad4ce4257/grpcio-1.76.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c9b93f79f48b03ada57ea24725d83a30284a012ec27eab2cf7e50a550cbbbcc", size = 7592214, upload-time = "2025-10-21T16:22:33.831Z" }, - { url = "https://files.pythonhosted.org/packages/4a/6e/0b899b7f6b66e5af39e377055fb4a6675c9ee28431df5708139df2e93233/grpcio-1.76.0-cp314-cp314-win32.whl", hash = "sha256:747fa73efa9b8b1488a95d0ba1039c8e2dca0f741612d80415b1e1c560febf4e", size = 4062961, upload-time = "2025-10-21T16:22:36.468Z" }, - { url = "https://files.pythonhosted.org/packages/19/41/0b430b01a2eb38ee887f88c1f07644a1df8e289353b78e82b37ef988fb64/grpcio-1.76.0-cp314-cp314-win_amd64.whl", hash = "sha256:922fa70ba549fce362d2e2871ab542082d66e2aaf0c19480ea453905b01f384e", size = 4834462, upload-time = "2025-10-21T16:22:39.772Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/b6/e0/318c1ce3ae5a17894d5791e87aea147587c9e702f24122cc7a5c8bbaeeb1/grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73", size = 12785182 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/05/8e29121994b8d959ffa0afd28996d452f291b48cfc0875619de0bde2c50c/grpcio-1.76.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8", size = 5799718 }, + { url = "https://files.pythonhosted.org/packages/d9/75/11d0e66b3cdf998c996489581bdad8900db79ebd83513e45c19548f1cba4/grpcio-1.76.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:04bbe1bfe3a68bbfd4e52402ab7d4eb59d72d02647ae2042204326cf4bbad280", size = 11825627 }, + { url = "https://files.pythonhosted.org/packages/28/50/2f0aa0498bc188048f5d9504dcc5c2c24f2eb1a9337cd0fa09a61a2e75f0/grpcio-1.76.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d388087771c837cdb6515539f43b9d4bf0b0f23593a24054ac16f7a960be16f4", size = 6359167 }, + { url = "https://files.pythonhosted.org/packages/66/e5/bbf0bb97d29ede1d59d6588af40018cfc345b17ce979b7b45424628dc8bb/grpcio-1.76.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f8f757bebaaea112c00dba718fc0d3260052ce714e25804a03f93f5d1c6cc11", size = 7044267 }, + { url = "https://files.pythonhosted.org/packages/f5/86/f6ec2164f743d9609691115ae8ece098c76b894ebe4f7c94a655c6b03e98/grpcio-1.76.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:980a846182ce88c4f2f7e2c22c56aefd515daeb36149d1c897f83cf57999e0b6", size = 6573963 }, + { url = "https://files.pythonhosted.org/packages/60/bc/8d9d0d8505feccfdf38a766d262c71e73639c165b311c9457208b56d92ae/grpcio-1.76.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f92f88e6c033db65a5ae3d97905c8fea9c725b63e28d5a75cb73b49bda5024d8", size = 7164484 }, + { url = "https://files.pythonhosted.org/packages/67/e6/5d6c2fc10b95edf6df9b8f19cf10a34263b7fd48493936fffd5085521292/grpcio-1.76.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4baf3cbe2f0be3289eb68ac8ae771156971848bb8aaff60bad42005539431980", size = 8127777 }, + { url = "https://files.pythonhosted.org/packages/3f/c8/dce8ff21c86abe025efe304d9e31fdb0deaaa3b502b6a78141080f206da0/grpcio-1.76.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:615ba64c208aaceb5ec83bfdce7728b80bfeb8be97562944836a7a0a9647d882", size = 7594014 }, + { url = "https://files.pythonhosted.org/packages/e0/42/ad28191ebf983a5d0ecef90bab66baa5a6b18f2bfdef9d0a63b1973d9f75/grpcio-1.76.0-cp312-cp312-win32.whl", hash = "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958", size = 3984750 }, + { url = "https://files.pythonhosted.org/packages/9e/00/7bd478cbb851c04a48baccaa49b75abaa8e4122f7d86da797500cccdd771/grpcio-1.76.0-cp312-cp312-win_amd64.whl", hash = "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347", size = 4704003 }, + { url = "https://files.pythonhosted.org/packages/fc/ed/71467ab770effc9e8cef5f2e7388beb2be26ed642d567697bb103a790c72/grpcio-1.76.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:26ef06c73eb53267c2b319f43e6634c7556ea37672029241a056629af27c10e2", size = 5807716 }, + { url = "https://files.pythonhosted.org/packages/2c/85/c6ed56f9817fab03fa8a111ca91469941fb514e3e3ce6d793cb8f1e1347b/grpcio-1.76.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:45e0111e73f43f735d70786557dc38141185072d7ff8dc1829d6a77ac1471468", size = 11821522 }, + { url = "https://files.pythonhosted.org/packages/ac/31/2b8a235ab40c39cbc141ef647f8a6eb7b0028f023015a4842933bc0d6831/grpcio-1.76.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83d57312a58dcfe2a3a0f9d1389b299438909a02db60e2f2ea2ae2d8034909d3", size = 6362558 }, + { url = "https://files.pythonhosted.org/packages/bd/64/9784eab483358e08847498ee56faf8ff6ea8e0a4592568d9f68edc97e9e9/grpcio-1.76.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3e2a27c89eb9ac3d81ec8835e12414d73536c6e620355d65102503064a4ed6eb", size = 7049990 }, + { url = "https://files.pythonhosted.org/packages/2b/94/8c12319a6369434e7a184b987e8e9f3b49a114c489b8315f029e24de4837/grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61f69297cba3950a524f61c7c8ee12e55c486cb5f7db47ff9dcee33da6f0d3ae", size = 6575387 }, + { url = "https://files.pythonhosted.org/packages/15/0f/f12c32b03f731f4a6242f771f63039df182c8b8e2cf8075b245b409259d4/grpcio-1.76.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a15c17af8839b6801d554263c546c69c4d7718ad4321e3166175b37eaacca77", size = 7166668 }, + { url = "https://files.pythonhosted.org/packages/ff/2d/3ec9ce0c2b1d92dd59d1c3264aaec9f0f7c817d6e8ac683b97198a36ed5a/grpcio-1.76.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:25a18e9810fbc7e7f03ec2516addc116a957f8cbb8cbc95ccc80faa072743d03", size = 8124928 }, + { url = "https://files.pythonhosted.org/packages/1a/74/fd3317be5672f4856bcdd1a9e7b5e17554692d3db9a3b273879dc02d657d/grpcio-1.76.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:931091142fd8cc14edccc0845a79248bc155425eee9a98b2db2ea4f00a235a42", size = 7589983 }, + { url = "https://files.pythonhosted.org/packages/45/bb/ca038cf420f405971f19821c8c15bcbc875505f6ffadafe9ffd77871dc4c/grpcio-1.76.0-cp313-cp313-win32.whl", hash = "sha256:5e8571632780e08526f118f74170ad8d50fb0a48c23a746bef2a6ebade3abd6f", size = 3984727 }, + { url = "https://files.pythonhosted.org/packages/41/80/84087dc56437ced7cdd4b13d7875e7439a52a261e3ab4e06488ba6173b0a/grpcio-1.76.0-cp313-cp313-win_amd64.whl", hash = "sha256:f9f7bd5faab55f47231ad8dba7787866b69f5e93bc306e3915606779bbfb4ba8", size = 4702799 }, + { url = "https://files.pythonhosted.org/packages/b4/46/39adac80de49d678e6e073b70204091e76631e03e94928b9ea4ecf0f6e0e/grpcio-1.76.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:ff8a59ea85a1f2191a0ffcc61298c571bc566332f82e5f5be1b83c9d8e668a62", size = 5808417 }, + { url = "https://files.pythonhosted.org/packages/9c/f5/a4531f7fb8b4e2a60b94e39d5d924469b7a6988176b3422487be61fe2998/grpcio-1.76.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:06c3d6b076e7b593905d04fdba6a0525711b3466f43b3400266f04ff735de0cd", size = 11828219 }, + { url = "https://files.pythonhosted.org/packages/4b/1c/de55d868ed7a8bd6acc6b1d6ddc4aa36d07a9f31d33c912c804adb1b971b/grpcio-1.76.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd5ef5932f6475c436c4a55e4336ebbe47bd3272be04964a03d316bbf4afbcbc", size = 6367826 }, + { url = "https://files.pythonhosted.org/packages/59/64/99e44c02b5adb0ad13ab3adc89cb33cb54bfa90c74770f2607eea629b86f/grpcio-1.76.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b331680e46239e090f5b3cead313cc772f6caa7d0fc8de349337563125361a4a", size = 7049550 }, + { url = "https://files.pythonhosted.org/packages/43/28/40a5be3f9a86949b83e7d6a2ad6011d993cbe9b6bd27bea881f61c7788b6/grpcio-1.76.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2229ae655ec4e8999599469559e97630185fdd53ae1e8997d147b7c9b2b72cba", size = 6575564 }, + { url = "https://files.pythonhosted.org/packages/4b/a9/1be18e6055b64467440208a8559afac243c66a8b904213af6f392dc2212f/grpcio-1.76.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:490fa6d203992c47c7b9e4a9d39003a0c2bcc1c9aa3c058730884bbbb0ee9f09", size = 7176236 }, + { url = "https://files.pythonhosted.org/packages/0f/55/dba05d3fcc151ce6e81327541d2cc8394f442f6b350fead67401661bf041/grpcio-1.76.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:479496325ce554792dba6548fae3df31a72cef7bad71ca2e12b0e58f9b336bfc", size = 8125795 }, + { url = "https://files.pythonhosted.org/packages/4a/45/122df922d05655f63930cf42c9e3f72ba20aadb26c100ee105cad4ce4257/grpcio-1.76.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c9b93f79f48b03ada57ea24725d83a30284a012ec27eab2cf7e50a550cbbbcc", size = 7592214 }, + { url = "https://files.pythonhosted.org/packages/4a/6e/0b899b7f6b66e5af39e377055fb4a6675c9ee28431df5708139df2e93233/grpcio-1.76.0-cp314-cp314-win32.whl", hash = "sha256:747fa73efa9b8b1488a95d0ba1039c8e2dca0f741612d80415b1e1c560febf4e", size = 4062961 }, + { url = "https://files.pythonhosted.org/packages/19/41/0b430b01a2eb38ee887f88c1f07644a1df8e289353b78e82b37ef988fb64/grpcio-1.76.0-cp314-cp314-win_amd64.whl", hash = "sha256:922fa70ba549fce362d2e2871ab542082d66e2aaf0c19480ea453905b01f384e", size = 4834462 }, ] [[package]] @@ -415,79 +417,79 @@ dependencies = [ { name = "grpcio" }, { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3f/46/e9f19d5be65e8423f886813a2a9d0056ba94757b0c5007aa59aed1a961fa/grpcio_status-1.76.0.tar.gz", hash = "sha256:25fcbfec74c15d1a1cb5da3fab8ee9672852dc16a5a9eeb5baf7d7a9952943cd", size = 13679, upload-time = "2025-10-21T16:28:52.545Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/46/e9f19d5be65e8423f886813a2a9d0056ba94757b0c5007aa59aed1a961fa/grpcio_status-1.76.0.tar.gz", hash = "sha256:25fcbfec74c15d1a1cb5da3fab8ee9672852dc16a5a9eeb5baf7d7a9952943cd", size = 13679 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/cc/27ba60ad5a5f2067963e6a858743500df408eb5855e98be778eaef8c9b02/grpcio_status-1.76.0-py3-none-any.whl", hash = "sha256:380568794055a8efbbd8871162df92012e0228a5f6dffaf57f2a00c534103b18", size = 14425, upload-time = "2025-10-21T16:28:40.853Z" }, + { url = "https://files.pythonhosted.org/packages/8c/cc/27ba60ad5a5f2067963e6a858743500df408eb5855e98be778eaef8c9b02/grpcio_status-1.76.0-py3-none-any.whl", hash = "sha256:380568794055a8efbbd8871162df92012e0228a5f6dffaf57f2a00c534103b18", size = 14425 }, ] [[package]] name = "idna" version = "3.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008 }, ] [[package]] name = "iniconfig" version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484 }, ] [[package]] name = "librt" version = "0.7.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/93/e4/b59bdf1197fdf9888452ea4d2048cdad61aef85eb83e99dc52551d7fdc04/librt-0.7.4.tar.gz", hash = "sha256:3871af56c59864d5fd21d1ac001eb2fb3b140d52ba0454720f2e4a19812404ba", size = 145862, upload-time = "2025-12-15T16:52:43.862Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/e7/b805d868d21f425b7e76a0ea71a2700290f2266a4f3c8357fcf73efc36aa/librt-0.7.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7dd3b5c37e0fb6666c27cf4e2c88ae43da904f2155c4cfc1e5a2fdce3b9fcf92", size = 55688, upload-time = "2025-12-15T16:51:31.571Z" }, - { url = "https://files.pythonhosted.org/packages/59/5e/69a2b02e62a14cfd5bfd9f1e9adea294d5bcfeea219c7555730e5d068ee4/librt-0.7.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9c5de1928c486201b23ed0cc4ac92e6e07be5cd7f3abc57c88a9cf4f0f32108", size = 57141, upload-time = "2025-12-15T16:51:32.714Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6b/05dba608aae1272b8ea5ff8ef12c47a4a099a04d1e00e28a94687261d403/librt-0.7.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:078ae52ffb3f036396cc4aed558e5b61faedd504a3c1f62b8ae34bf95ae39d94", size = 165322, upload-time = "2025-12-15T16:51:33.986Z" }, - { url = "https://files.pythonhosted.org/packages/8f/bc/199533d3fc04a4cda8d7776ee0d79955ab0c64c79ca079366fbc2617e680/librt-0.7.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce58420e25097b2fc201aef9b9f6d65df1eb8438e51154e1a7feb8847e4a55ab", size = 174216, upload-time = "2025-12-15T16:51:35.384Z" }, - { url = "https://files.pythonhosted.org/packages/62/ec/09239b912a45a8ed117cb4a6616d9ff508f5d3131bd84329bf2f8d6564f1/librt-0.7.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b719c8730c02a606dc0e8413287e8e94ac2d32a51153b300baf1f62347858fba", size = 189005, upload-time = "2025-12-15T16:51:36.687Z" }, - { url = "https://files.pythonhosted.org/packages/46/2e/e188313d54c02f5b0580dd31476bb4b0177514ff8d2be9f58d4a6dc3a7ba/librt-0.7.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3749ef74c170809e6dee68addec9d2458700a8de703de081c888e92a8b015cf9", size = 183960, upload-time = "2025-12-15T16:51:37.977Z" }, - { url = "https://files.pythonhosted.org/packages/eb/84/f1d568d254518463d879161d3737b784137d236075215e56c7c9be191cee/librt-0.7.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b35c63f557653c05b5b1b6559a074dbabe0afee28ee2a05b6c9ba21ad0d16a74", size = 177609, upload-time = "2025-12-15T16:51:40.584Z" }, - { url = "https://files.pythonhosted.org/packages/5d/43/060bbc1c002f0d757c33a1afe6bf6a565f947a04841139508fc7cef6c08b/librt-0.7.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1ef704e01cb6ad39ad7af668d51677557ca7e5d377663286f0ee1b6b27c28e5f", size = 199269, upload-time = "2025-12-15T16:51:41.879Z" }, - { url = "https://files.pythonhosted.org/packages/ff/7f/708f8f02d8012ee9f366c07ea6a92882f48bd06cc1ff16a35e13d0fbfb08/librt-0.7.4-cp312-cp312-win32.whl", hash = "sha256:c66c2b245926ec15188aead25d395091cb5c9df008d3b3207268cd65557d6286", size = 43186, upload-time = "2025-12-15T16:51:43.149Z" }, - { url = "https://files.pythonhosted.org/packages/f1/a5/4e051b061c8b2509be31b2c7ad4682090502c0a8b6406edcf8c6b4fe1ef7/librt-0.7.4-cp312-cp312-win_amd64.whl", hash = "sha256:71a56f4671f7ff723451f26a6131754d7c1809e04e22ebfbac1db8c9e6767a20", size = 49455, upload-time = "2025-12-15T16:51:44.336Z" }, - { url = "https://files.pythonhosted.org/packages/d0/d2/90d84e9f919224a3c1f393af1636d8638f54925fdc6cd5ee47f1548461e5/librt-0.7.4-cp312-cp312-win_arm64.whl", hash = "sha256:419eea245e7ec0fe664eb7e85e7ff97dcdb2513ca4f6b45a8ec4a3346904f95a", size = 42828, upload-time = "2025-12-15T16:51:45.498Z" }, - { url = "https://files.pythonhosted.org/packages/fe/4d/46a53ccfbb39fd0b493fd4496eb76f3ebc15bb3e45d8c2e695a27587edf5/librt-0.7.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d44a1b1ba44cbd2fc3cb77992bef6d6fdb1028849824e1dd5e4d746e1f7f7f0b", size = 55745, upload-time = "2025-12-15T16:51:46.636Z" }, - { url = "https://files.pythonhosted.org/packages/7f/2b/3ac7f5212b1828bf4f979cf87f547db948d3e28421d7a430d4db23346ce4/librt-0.7.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c9cab4b3de1f55e6c30a84c8cee20e4d3b2476f4d547256694a1b0163da4fe32", size = 57166, upload-time = "2025-12-15T16:51:48.219Z" }, - { url = "https://files.pythonhosted.org/packages/e8/99/6523509097cbe25f363795f0c0d1c6a3746e30c2994e25b5aefdab119b21/librt-0.7.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2857c875f1edd1feef3c371fbf830a61b632fb4d1e57160bb1e6a3206e6abe67", size = 165833, upload-time = "2025-12-15T16:51:49.443Z" }, - { url = "https://files.pythonhosted.org/packages/fe/35/323611e59f8fe032649b4fb7e77f746f96eb7588fcbb31af26bae9630571/librt-0.7.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b370a77be0a16e1ad0270822c12c21462dc40496e891d3b0caf1617c8cc57e20", size = 174818, upload-time = "2025-12-15T16:51:51.015Z" }, - { url = "https://files.pythonhosted.org/packages/41/e6/40fb2bb21616c6e06b6a64022802228066e9a31618f493e03f6b9661548a/librt-0.7.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d05acd46b9a52087bfc50c59dfdf96a2c480a601e8898a44821c7fd676598f74", size = 189607, upload-time = "2025-12-15T16:51:52.671Z" }, - { url = "https://files.pythonhosted.org/packages/32/48/1b47c7d5d28b775941e739ed2bfe564b091c49201b9503514d69e4ed96d7/librt-0.7.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:70969229cb23d9c1a80e14225838d56e464dc71fa34c8342c954fc50e7516dee", size = 184585, upload-time = "2025-12-15T16:51:54.027Z" }, - { url = "https://files.pythonhosted.org/packages/75/a6/ee135dfb5d3b54d5d9001dbe483806229c6beac3ee2ba1092582b7efeb1b/librt-0.7.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4450c354b89dbb266730893862dbff06006c9ed5b06b6016d529b2bf644fc681", size = 178249, upload-time = "2025-12-15T16:51:55.248Z" }, - { url = "https://files.pythonhosted.org/packages/04/87/d5b84ec997338be26af982bcd6679be0c1db9a32faadab1cf4bb24f9e992/librt-0.7.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:adefe0d48ad35b90b6f361f6ff5a1bd95af80c17d18619c093c60a20e7a5b60c", size = 199851, upload-time = "2025-12-15T16:51:56.933Z" }, - { url = "https://files.pythonhosted.org/packages/86/63/ba1333bf48306fe398e3392a7427ce527f81b0b79d0d91618c4610ce9d15/librt-0.7.4-cp313-cp313-win32.whl", hash = "sha256:21ea710e96c1e050635700695095962a22ea420d4b3755a25e4909f2172b4ff2", size = 43249, upload-time = "2025-12-15T16:51:58.498Z" }, - { url = "https://files.pythonhosted.org/packages/f9/8a/de2c6df06cdfa9308c080e6b060fe192790b6a48a47320b215e860f0e98c/librt-0.7.4-cp313-cp313-win_amd64.whl", hash = "sha256:772e18696cf5a64afee908662fbcb1f907460ddc851336ee3a848ef7684c8e1e", size = 49417, upload-time = "2025-12-15T16:51:59.618Z" }, - { url = "https://files.pythonhosted.org/packages/31/66/8ee0949efc389691381ed686185e43536c20e7ad880c122dd1f31e65c658/librt-0.7.4-cp313-cp313-win_arm64.whl", hash = "sha256:52e34c6af84e12921748c8354aa6acf1912ca98ba60cdaa6920e34793f1a0788", size = 42824, upload-time = "2025-12-15T16:52:00.784Z" }, - { url = "https://files.pythonhosted.org/packages/74/81/6921e65c8708eb6636bbf383aa77e6c7dad33a598ed3b50c313306a2da9d/librt-0.7.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4f1ee004942eaaed6e06c087d93ebc1c67e9a293e5f6b9b5da558df6bf23dc5d", size = 55191, upload-time = "2025-12-15T16:52:01.97Z" }, - { url = "https://files.pythonhosted.org/packages/0d/d6/3eb864af8a8de8b39cc8dd2e9ded1823979a27795d72c4eea0afa8c26c9f/librt-0.7.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d854c6dc0f689bad7ed452d2a3ecff58029d80612d336a45b62c35e917f42d23", size = 56898, upload-time = "2025-12-15T16:52:03.356Z" }, - { url = "https://files.pythonhosted.org/packages/49/bc/b1d4c0711fdf79646225d576faee8747b8528a6ec1ceb6accfd89ade7102/librt-0.7.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a4f7339d9e445280f23d63dea842c0c77379c4a47471c538fc8feedab9d8d063", size = 163725, upload-time = "2025-12-15T16:52:04.572Z" }, - { url = "https://files.pythonhosted.org/packages/2c/08/61c41cd8f0a6a41fc99ea78a2205b88187e45ba9800792410ed62f033584/librt-0.7.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39003fc73f925e684f8521b2dbf34f61a5deb8a20a15dcf53e0d823190ce8848", size = 172469, upload-time = "2025-12-15T16:52:05.863Z" }, - { url = "https://files.pythonhosted.org/packages/8b/c7/4ee18b4d57f01444230bc18cf59103aeab8f8c0f45e84e0e540094df1df1/librt-0.7.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb15ee29d95875ad697d449fe6071b67f730f15a6961913a2b0205015ca0843", size = 186804, upload-time = "2025-12-15T16:52:07.192Z" }, - { url = "https://files.pythonhosted.org/packages/a1/af/009e8ba3fbf830c936842da048eda1b34b99329f402e49d88fafff6525d1/librt-0.7.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:02a69369862099e37d00765583052a99d6a68af7e19b887e1b78fee0146b755a", size = 181807, upload-time = "2025-12-15T16:52:08.554Z" }, - { url = "https://files.pythonhosted.org/packages/85/26/51ae25f813656a8b117c27a974f25e8c1e90abcd5a791ac685bf5b489a1b/librt-0.7.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ec72342cc4d62f38b25a94e28b9efefce41839aecdecf5e9627473ed04b7be16", size = 175595, upload-time = "2025-12-15T16:52:10.186Z" }, - { url = "https://files.pythonhosted.org/packages/48/93/36d6c71f830305f88996b15c8e017aa8d1e03e2e947b40b55bbf1a34cf24/librt-0.7.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:776dbb9bfa0fc5ce64234b446995d8d9f04badf64f544ca036bd6cff6f0732ce", size = 196504, upload-time = "2025-12-15T16:52:11.472Z" }, - { url = "https://files.pythonhosted.org/packages/08/11/8299e70862bb9d704735bf132c6be09c17b00fbc7cda0429a9df222fdc1b/librt-0.7.4-cp314-cp314-win32.whl", hash = "sha256:0f8cac84196d0ffcadf8469d9ded4d4e3a8b1c666095c2a291e22bf58e1e8a9f", size = 39738, upload-time = "2025-12-15T16:52:12.962Z" }, - { url = "https://files.pythonhosted.org/packages/54/d5/656b0126e4e0f8e2725cd2d2a1ec40f71f37f6f03f135a26b663c0e1a737/librt-0.7.4-cp314-cp314-win_amd64.whl", hash = "sha256:037f5cb6fe5abe23f1dc058054d50e9699fcc90d0677eee4e4f74a8677636a1a", size = 45976, upload-time = "2025-12-15T16:52:14.441Z" }, - { url = "https://files.pythonhosted.org/packages/60/86/465ff07b75c1067da8fa7f02913c4ead096ef106cfac97a977f763783bfb/librt-0.7.4-cp314-cp314-win_arm64.whl", hash = "sha256:a5deebb53d7a4d7e2e758a96befcd8edaaca0633ae71857995a0f16033289e44", size = 39073, upload-time = "2025-12-15T16:52:15.621Z" }, - { url = "https://files.pythonhosted.org/packages/b3/a0/24941f85960774a80d4b3c2aec651d7d980466da8101cae89e8b032a3e21/librt-0.7.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b4c25312c7f4e6ab35ab16211bdf819e6e4eddcba3b2ea632fb51c9a2a97e105", size = 57369, upload-time = "2025-12-15T16:52:16.782Z" }, - { url = "https://files.pythonhosted.org/packages/77/a0/ddb259cae86ab415786c1547d0fe1b40f04a7b089f564fd5c0242a3fafb2/librt-0.7.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:618b7459bb392bdf373f2327e477597fff8f9e6a1878fffc1b711c013d1b0da4", size = 59230, upload-time = "2025-12-15T16:52:18.259Z" }, - { url = "https://files.pythonhosted.org/packages/31/11/77823cb530ab8a0c6fac848ac65b745be446f6f301753b8990e8809080c9/librt-0.7.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1437c3f72a30c7047f16fd3e972ea58b90172c3c6ca309645c1c68984f05526a", size = 183869, upload-time = "2025-12-15T16:52:19.457Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ce/157db3614cf3034b3f702ae5ba4fefda4686f11eea4b7b96542324a7a0e7/librt-0.7.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c96cb76f055b33308f6858b9b594618f1b46e147a4d03a4d7f0c449e304b9b95", size = 194606, upload-time = "2025-12-15T16:52:20.795Z" }, - { url = "https://files.pythonhosted.org/packages/30/ef/6ec4c7e3d6490f69a4fd2803516fa5334a848a4173eac26d8ee6507bff6e/librt-0.7.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28f990e6821204f516d09dc39966ef8b84556ffd648d5926c9a3f681e8de8906", size = 206776, upload-time = "2025-12-15T16:52:22.229Z" }, - { url = "https://files.pythonhosted.org/packages/ad/22/750b37bf549f60a4782ab80e9d1e9c44981374ab79a7ea68670159905918/librt-0.7.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc4aebecc79781a1b77d7d4e7d9fe080385a439e198d993b557b60f9117addaf", size = 203205, upload-time = "2025-12-15T16:52:23.603Z" }, - { url = "https://files.pythonhosted.org/packages/7a/87/2e8a0f584412a93df5faad46c5fa0a6825fdb5eba2ce482074b114877f44/librt-0.7.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:022cc673e69283a42621dd453e2407cf1647e77f8bd857d7ad7499901e62376f", size = 196696, upload-time = "2025-12-15T16:52:24.951Z" }, - { url = "https://files.pythonhosted.org/packages/e5/ca/7bf78fa950e43b564b7de52ceeb477fb211a11f5733227efa1591d05a307/librt-0.7.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2b3ca211ae8ea540569e9c513da052699b7b06928dcda61247cb4f318122bdb5", size = 217191, upload-time = "2025-12-15T16:52:26.194Z" }, - { url = "https://files.pythonhosted.org/packages/d6/49/3732b0e8424ae35ad5c3166d9dd5bcdae43ce98775e0867a716ff5868064/librt-0.7.4-cp314-cp314t-win32.whl", hash = "sha256:8a461f6456981d8c8e971ff5a55f2e34f4e60871e665d2f5fde23ee74dea4eeb", size = 40276, upload-time = "2025-12-15T16:52:27.54Z" }, - { url = "https://files.pythonhosted.org/packages/35/d6/d8823e01bd069934525fddb343189c008b39828a429b473fb20d67d5cd36/librt-0.7.4-cp314-cp314t-win_amd64.whl", hash = "sha256:721a7b125a817d60bf4924e1eec2a7867bfcf64cfc333045de1df7a0629e4481", size = 46772, upload-time = "2025-12-15T16:52:28.653Z" }, - { url = "https://files.pythonhosted.org/packages/36/e9/a0aa60f5322814dd084a89614e9e31139702e342f8459ad8af1984a18168/librt-0.7.4-cp314-cp314t-win_arm64.whl", hash = "sha256:76b2ba71265c0102d11458879b4d53ccd0b32b0164d14deb8d2b598a018e502f", size = 39724, upload-time = "2025-12-15T16:52:29.836Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/93/e4/b59bdf1197fdf9888452ea4d2048cdad61aef85eb83e99dc52551d7fdc04/librt-0.7.4.tar.gz", hash = "sha256:3871af56c59864d5fd21d1ac001eb2fb3b140d52ba0454720f2e4a19812404ba", size = 145862 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/e7/b805d868d21f425b7e76a0ea71a2700290f2266a4f3c8357fcf73efc36aa/librt-0.7.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7dd3b5c37e0fb6666c27cf4e2c88ae43da904f2155c4cfc1e5a2fdce3b9fcf92", size = 55688 }, + { url = "https://files.pythonhosted.org/packages/59/5e/69a2b02e62a14cfd5bfd9f1e9adea294d5bcfeea219c7555730e5d068ee4/librt-0.7.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9c5de1928c486201b23ed0cc4ac92e6e07be5cd7f3abc57c88a9cf4f0f32108", size = 57141 }, + { url = "https://files.pythonhosted.org/packages/6e/6b/05dba608aae1272b8ea5ff8ef12c47a4a099a04d1e00e28a94687261d403/librt-0.7.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:078ae52ffb3f036396cc4aed558e5b61faedd504a3c1f62b8ae34bf95ae39d94", size = 165322 }, + { url = "https://files.pythonhosted.org/packages/8f/bc/199533d3fc04a4cda8d7776ee0d79955ab0c64c79ca079366fbc2617e680/librt-0.7.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce58420e25097b2fc201aef9b9f6d65df1eb8438e51154e1a7feb8847e4a55ab", size = 174216 }, + { url = "https://files.pythonhosted.org/packages/62/ec/09239b912a45a8ed117cb4a6616d9ff508f5d3131bd84329bf2f8d6564f1/librt-0.7.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b719c8730c02a606dc0e8413287e8e94ac2d32a51153b300baf1f62347858fba", size = 189005 }, + { url = "https://files.pythonhosted.org/packages/46/2e/e188313d54c02f5b0580dd31476bb4b0177514ff8d2be9f58d4a6dc3a7ba/librt-0.7.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3749ef74c170809e6dee68addec9d2458700a8de703de081c888e92a8b015cf9", size = 183960 }, + { url = "https://files.pythonhosted.org/packages/eb/84/f1d568d254518463d879161d3737b784137d236075215e56c7c9be191cee/librt-0.7.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b35c63f557653c05b5b1b6559a074dbabe0afee28ee2a05b6c9ba21ad0d16a74", size = 177609 }, + { url = "https://files.pythonhosted.org/packages/5d/43/060bbc1c002f0d757c33a1afe6bf6a565f947a04841139508fc7cef6c08b/librt-0.7.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1ef704e01cb6ad39ad7af668d51677557ca7e5d377663286f0ee1b6b27c28e5f", size = 199269 }, + { url = "https://files.pythonhosted.org/packages/ff/7f/708f8f02d8012ee9f366c07ea6a92882f48bd06cc1ff16a35e13d0fbfb08/librt-0.7.4-cp312-cp312-win32.whl", hash = "sha256:c66c2b245926ec15188aead25d395091cb5c9df008d3b3207268cd65557d6286", size = 43186 }, + { url = "https://files.pythonhosted.org/packages/f1/a5/4e051b061c8b2509be31b2c7ad4682090502c0a8b6406edcf8c6b4fe1ef7/librt-0.7.4-cp312-cp312-win_amd64.whl", hash = "sha256:71a56f4671f7ff723451f26a6131754d7c1809e04e22ebfbac1db8c9e6767a20", size = 49455 }, + { url = "https://files.pythonhosted.org/packages/d0/d2/90d84e9f919224a3c1f393af1636d8638f54925fdc6cd5ee47f1548461e5/librt-0.7.4-cp312-cp312-win_arm64.whl", hash = "sha256:419eea245e7ec0fe664eb7e85e7ff97dcdb2513ca4f6b45a8ec4a3346904f95a", size = 42828 }, + { url = "https://files.pythonhosted.org/packages/fe/4d/46a53ccfbb39fd0b493fd4496eb76f3ebc15bb3e45d8c2e695a27587edf5/librt-0.7.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d44a1b1ba44cbd2fc3cb77992bef6d6fdb1028849824e1dd5e4d746e1f7f7f0b", size = 55745 }, + { url = "https://files.pythonhosted.org/packages/7f/2b/3ac7f5212b1828bf4f979cf87f547db948d3e28421d7a430d4db23346ce4/librt-0.7.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c9cab4b3de1f55e6c30a84c8cee20e4d3b2476f4d547256694a1b0163da4fe32", size = 57166 }, + { url = "https://files.pythonhosted.org/packages/e8/99/6523509097cbe25f363795f0c0d1c6a3746e30c2994e25b5aefdab119b21/librt-0.7.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2857c875f1edd1feef3c371fbf830a61b632fb4d1e57160bb1e6a3206e6abe67", size = 165833 }, + { url = "https://files.pythonhosted.org/packages/fe/35/323611e59f8fe032649b4fb7e77f746f96eb7588fcbb31af26bae9630571/librt-0.7.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b370a77be0a16e1ad0270822c12c21462dc40496e891d3b0caf1617c8cc57e20", size = 174818 }, + { url = "https://files.pythonhosted.org/packages/41/e6/40fb2bb21616c6e06b6a64022802228066e9a31618f493e03f6b9661548a/librt-0.7.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d05acd46b9a52087bfc50c59dfdf96a2c480a601e8898a44821c7fd676598f74", size = 189607 }, + { url = "https://files.pythonhosted.org/packages/32/48/1b47c7d5d28b775941e739ed2bfe564b091c49201b9503514d69e4ed96d7/librt-0.7.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:70969229cb23d9c1a80e14225838d56e464dc71fa34c8342c954fc50e7516dee", size = 184585 }, + { url = "https://files.pythonhosted.org/packages/75/a6/ee135dfb5d3b54d5d9001dbe483806229c6beac3ee2ba1092582b7efeb1b/librt-0.7.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4450c354b89dbb266730893862dbff06006c9ed5b06b6016d529b2bf644fc681", size = 178249 }, + { url = "https://files.pythonhosted.org/packages/04/87/d5b84ec997338be26af982bcd6679be0c1db9a32faadab1cf4bb24f9e992/librt-0.7.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:adefe0d48ad35b90b6f361f6ff5a1bd95af80c17d18619c093c60a20e7a5b60c", size = 199851 }, + { url = "https://files.pythonhosted.org/packages/86/63/ba1333bf48306fe398e3392a7427ce527f81b0b79d0d91618c4610ce9d15/librt-0.7.4-cp313-cp313-win32.whl", hash = "sha256:21ea710e96c1e050635700695095962a22ea420d4b3755a25e4909f2172b4ff2", size = 43249 }, + { url = "https://files.pythonhosted.org/packages/f9/8a/de2c6df06cdfa9308c080e6b060fe192790b6a48a47320b215e860f0e98c/librt-0.7.4-cp313-cp313-win_amd64.whl", hash = "sha256:772e18696cf5a64afee908662fbcb1f907460ddc851336ee3a848ef7684c8e1e", size = 49417 }, + { url = "https://files.pythonhosted.org/packages/31/66/8ee0949efc389691381ed686185e43536c20e7ad880c122dd1f31e65c658/librt-0.7.4-cp313-cp313-win_arm64.whl", hash = "sha256:52e34c6af84e12921748c8354aa6acf1912ca98ba60cdaa6920e34793f1a0788", size = 42824 }, + { url = "https://files.pythonhosted.org/packages/74/81/6921e65c8708eb6636bbf383aa77e6c7dad33a598ed3b50c313306a2da9d/librt-0.7.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4f1ee004942eaaed6e06c087d93ebc1c67e9a293e5f6b9b5da558df6bf23dc5d", size = 55191 }, + { url = "https://files.pythonhosted.org/packages/0d/d6/3eb864af8a8de8b39cc8dd2e9ded1823979a27795d72c4eea0afa8c26c9f/librt-0.7.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d854c6dc0f689bad7ed452d2a3ecff58029d80612d336a45b62c35e917f42d23", size = 56898 }, + { url = "https://files.pythonhosted.org/packages/49/bc/b1d4c0711fdf79646225d576faee8747b8528a6ec1ceb6accfd89ade7102/librt-0.7.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a4f7339d9e445280f23d63dea842c0c77379c4a47471c538fc8feedab9d8d063", size = 163725 }, + { url = "https://files.pythonhosted.org/packages/2c/08/61c41cd8f0a6a41fc99ea78a2205b88187e45ba9800792410ed62f033584/librt-0.7.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39003fc73f925e684f8521b2dbf34f61a5deb8a20a15dcf53e0d823190ce8848", size = 172469 }, + { url = "https://files.pythonhosted.org/packages/8b/c7/4ee18b4d57f01444230bc18cf59103aeab8f8c0f45e84e0e540094df1df1/librt-0.7.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb15ee29d95875ad697d449fe6071b67f730f15a6961913a2b0205015ca0843", size = 186804 }, + { url = "https://files.pythonhosted.org/packages/a1/af/009e8ba3fbf830c936842da048eda1b34b99329f402e49d88fafff6525d1/librt-0.7.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:02a69369862099e37d00765583052a99d6a68af7e19b887e1b78fee0146b755a", size = 181807 }, + { url = "https://files.pythonhosted.org/packages/85/26/51ae25f813656a8b117c27a974f25e8c1e90abcd5a791ac685bf5b489a1b/librt-0.7.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ec72342cc4d62f38b25a94e28b9efefce41839aecdecf5e9627473ed04b7be16", size = 175595 }, + { url = "https://files.pythonhosted.org/packages/48/93/36d6c71f830305f88996b15c8e017aa8d1e03e2e947b40b55bbf1a34cf24/librt-0.7.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:776dbb9bfa0fc5ce64234b446995d8d9f04badf64f544ca036bd6cff6f0732ce", size = 196504 }, + { url = "https://files.pythonhosted.org/packages/08/11/8299e70862bb9d704735bf132c6be09c17b00fbc7cda0429a9df222fdc1b/librt-0.7.4-cp314-cp314-win32.whl", hash = "sha256:0f8cac84196d0ffcadf8469d9ded4d4e3a8b1c666095c2a291e22bf58e1e8a9f", size = 39738 }, + { url = "https://files.pythonhosted.org/packages/54/d5/656b0126e4e0f8e2725cd2d2a1ec40f71f37f6f03f135a26b663c0e1a737/librt-0.7.4-cp314-cp314-win_amd64.whl", hash = "sha256:037f5cb6fe5abe23f1dc058054d50e9699fcc90d0677eee4e4f74a8677636a1a", size = 45976 }, + { url = "https://files.pythonhosted.org/packages/60/86/465ff07b75c1067da8fa7f02913c4ead096ef106cfac97a977f763783bfb/librt-0.7.4-cp314-cp314-win_arm64.whl", hash = "sha256:a5deebb53d7a4d7e2e758a96befcd8edaaca0633ae71857995a0f16033289e44", size = 39073 }, + { url = "https://files.pythonhosted.org/packages/b3/a0/24941f85960774a80d4b3c2aec651d7d980466da8101cae89e8b032a3e21/librt-0.7.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b4c25312c7f4e6ab35ab16211bdf819e6e4eddcba3b2ea632fb51c9a2a97e105", size = 57369 }, + { url = "https://files.pythonhosted.org/packages/77/a0/ddb259cae86ab415786c1547d0fe1b40f04a7b089f564fd5c0242a3fafb2/librt-0.7.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:618b7459bb392bdf373f2327e477597fff8f9e6a1878fffc1b711c013d1b0da4", size = 59230 }, + { url = "https://files.pythonhosted.org/packages/31/11/77823cb530ab8a0c6fac848ac65b745be446f6f301753b8990e8809080c9/librt-0.7.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1437c3f72a30c7047f16fd3e972ea58b90172c3c6ca309645c1c68984f05526a", size = 183869 }, + { url = "https://files.pythonhosted.org/packages/a4/ce/157db3614cf3034b3f702ae5ba4fefda4686f11eea4b7b96542324a7a0e7/librt-0.7.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c96cb76f055b33308f6858b9b594618f1b46e147a4d03a4d7f0c449e304b9b95", size = 194606 }, + { url = "https://files.pythonhosted.org/packages/30/ef/6ec4c7e3d6490f69a4fd2803516fa5334a848a4173eac26d8ee6507bff6e/librt-0.7.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28f990e6821204f516d09dc39966ef8b84556ffd648d5926c9a3f681e8de8906", size = 206776 }, + { url = "https://files.pythonhosted.org/packages/ad/22/750b37bf549f60a4782ab80e9d1e9c44981374ab79a7ea68670159905918/librt-0.7.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc4aebecc79781a1b77d7d4e7d9fe080385a439e198d993b557b60f9117addaf", size = 203205 }, + { url = "https://files.pythonhosted.org/packages/7a/87/2e8a0f584412a93df5faad46c5fa0a6825fdb5eba2ce482074b114877f44/librt-0.7.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:022cc673e69283a42621dd453e2407cf1647e77f8bd857d7ad7499901e62376f", size = 196696 }, + { url = "https://files.pythonhosted.org/packages/e5/ca/7bf78fa950e43b564b7de52ceeb477fb211a11f5733227efa1591d05a307/librt-0.7.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2b3ca211ae8ea540569e9c513da052699b7b06928dcda61247cb4f318122bdb5", size = 217191 }, + { url = "https://files.pythonhosted.org/packages/d6/49/3732b0e8424ae35ad5c3166d9dd5bcdae43ce98775e0867a716ff5868064/librt-0.7.4-cp314-cp314t-win32.whl", hash = "sha256:8a461f6456981d8c8e971ff5a55f2e34f4e60871e665d2f5fde23ee74dea4eeb", size = 40276 }, + { url = "https://files.pythonhosted.org/packages/35/d6/d8823e01bd069934525fddb343189c008b39828a429b473fb20d67d5cd36/librt-0.7.4-cp314-cp314t-win_amd64.whl", hash = "sha256:721a7b125a817d60bf4924e1eec2a7867bfcf64cfc333045de1df7a0629e4481", size = 46772 }, + { url = "https://files.pythonhosted.org/packages/36/e9/a0aa60f5322814dd084a89614e9e31139702e342f8459ad8af1984a18168/librt-0.7.4-cp314-cp314t-win_arm64.whl", hash = "sha256:76b2ba71265c0102d11458879b4d53ccd0b32b0164d14deb8d2b598a018e502f", size = 39724 }, ] [[package]] @@ -497,18 +499,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070 } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321 }, ] [[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, upload-time = "2022-08-14T12:40:10.846Z" } +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, upload-time = "2022-08-14T12:40:09.779Z" }, + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, ] [[package]] @@ -521,63 +523,63 @@ dependencies = [ { name = "pathspec" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" }, - { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" }, - { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" }, - { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" }, - { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" }, - { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" }, - { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, - { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, - { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, - { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, - { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, - { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, - { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" }, - { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" }, - { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, - { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, - { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, - { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053 }, + { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134 }, + { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616 }, + { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847 }, + { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976 }, + { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104 }, + { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927 }, + { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730 }, + { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581 }, + { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252 }, + { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848 }, + { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510 }, + { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744 }, + { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815 }, + { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047 }, + { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998 }, + { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476 }, + { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872 }, + { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239 }, ] [[package]] name = "mypy-extensions" version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 }, ] [[package]] name = "packaging" version = "25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, ] [[package]] name = "pathspec" version = "0.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, ] [[package]] name = "pluggy" version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, ] [[package]] @@ -587,33 +589,33 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/01/89/9cbe2f4bba860e149108b683bc2efec21f14d5f7ed6e25562ad86acbc373/proto_plus-1.27.0.tar.gz", hash = "sha256:873af56dd0d7e91836aee871e5799e1c6f1bda86ac9a983e0bb9f0c266a568c4", size = 56158, upload-time = "2025-12-16T13:46:25.729Z" } +sdist = { url = "https://files.pythonhosted.org/packages/01/89/9cbe2f4bba860e149108b683bc2efec21f14d5f7ed6e25562ad86acbc373/proto_plus-1.27.0.tar.gz", hash = "sha256:873af56dd0d7e91836aee871e5799e1c6f1bda86ac9a983e0bb9f0c266a568c4", size = 56158 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/24/3b7a0818484df9c28172857af32c2397b6d8fcd99d9468bd4684f98ebf0a/proto_plus-1.27.0-py3-none-any.whl", hash = "sha256:1baa7f81cf0f8acb8bc1f6d085008ba4171eaf669629d1b6d1673b21ed1c0a82", size = 50205, upload-time = "2025-12-16T13:46:24.76Z" }, + { url = "https://files.pythonhosted.org/packages/cd/24/3b7a0818484df9c28172857af32c2397b6d8fcd99d9468bd4684f98ebf0a/proto_plus-1.27.0-py3-none-any.whl", hash = "sha256:1baa7f81cf0f8acb8bc1f6d085008ba4171eaf669629d1b6d1673b21ed1c0a82", size = 50205 }, ] [[package]] name = "protobuf" version = "6.33.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, - { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, - { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, - { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, - { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, + { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769 }, + { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118 }, + { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766 }, + { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638 }, + { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411 }, + { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465 }, + { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687 }, ] [[package]] name = "pyasn1" version = "0.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997 }, ] [[package]] @@ -623,18 +625,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyasn1" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892 } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259 }, ] [[package]] name = "pygments" version = "2.19.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, ] [[package]] @@ -648,9 +650,9 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801 }, ] [[package]] @@ -662,55 +664,55 @@ dependencies = [ { name = "pluggy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424 }, ] [[package]] name = "pyyaml" version = "6.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, - { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, - { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, - { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, - { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, - { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, - { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, - { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, - { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, - { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, - { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, - { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, - { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, - { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, - { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, - { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, - { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, - { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, - { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, - { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, - { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, - { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, - { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, - { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, - { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, - { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063 }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973 }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116 }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011 }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870 }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089 }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181 }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658 }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003 }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344 }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669 }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252 }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081 }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159 }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626 }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613 }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115 }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427 }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090 }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246 }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814 }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809 }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454 }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355 }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175 }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228 }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194 }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429 }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912 }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108 }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641 }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901 }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132 }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261 }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272 }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923 }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062 }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341 }, ] [[package]] @@ -723,9 +725,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232, upload-time = "2026-03-25T15:10:41.586Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232 } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" }, + { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017 }, ] [[package]] @@ -736,9 +738,9 @@ dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990 } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, + { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393 }, ] [[package]] @@ -748,46 +750,51 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyasn1" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034 } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696 }, ] [[package]] name = "ruff" version = "0.14.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/08/52232a877978dd8f9cf2aeddce3e611b40a63287dfca29b6b8da791f5e8d/ruff-0.14.10.tar.gz", hash = "sha256:9a2e830f075d1a42cd28420d7809ace390832a490ed0966fe373ba288e77aaf4", size = 5859763, upload-time = "2025-12-18T19:28:57.98Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/60/01/933704d69f3f05ee16ef11406b78881733c186fe14b6a46b05cfcaf6d3b2/ruff-0.14.10-py3-none-linux_armv6l.whl", hash = "sha256:7a3ce585f2ade3e1f29ec1b92df13e3da262178df8c8bdf876f48fa0e8316c49", size = 13527080, upload-time = "2025-12-18T19:29:25.642Z" }, - { url = "https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f", size = 13797320, upload-time = "2025-12-18T19:29:02.571Z" }, - { url = "https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d", size = 12918434, upload-time = "2025-12-18T19:28:51.202Z" }, - { url = "https://files.pythonhosted.org/packages/a6/00/45c62a7f7e34da92a25804f813ebe05c88aa9e0c25e5cb5a7d23dd7450e3/ruff-0.14.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6987ebe0501ae4f4308d7d24e2d0fe3d7a98430f5adfd0f1fead050a740a3a77", size = 13371961, upload-time = "2025-12-18T19:29:04.991Z" }, - { url = "https://files.pythonhosted.org/packages/40/31/a5906d60f0405f7e57045a70f2d57084a93ca7425f22e1d66904769d1628/ruff-0.14.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16a01dfb7b9e4eee556fbfd5392806b1b8550c9b4a9f6acd3dbe6812b193c70a", size = 13275629, upload-time = "2025-12-18T19:29:21.381Z" }, - { url = "https://files.pythonhosted.org/packages/3e/60/61c0087df21894cf9d928dc04bcd4fb10e8b2e8dca7b1a276ba2155b2002/ruff-0.14.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7165d31a925b7a294465fa81be8c12a0e9b60fb02bf177e79067c867e71f8b1f", size = 14029234, upload-time = "2025-12-18T19:29:00.132Z" }, - { url = "https://files.pythonhosted.org/packages/44/84/77d911bee3b92348b6e5dab5a0c898d87084ea03ac5dc708f46d88407def/ruff-0.14.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c561695675b972effb0c0a45db233f2c816ff3da8dcfbe7dfc7eed625f218935", size = 15449890, upload-time = "2025-12-18T19:28:53.573Z" }, - { url = "https://files.pythonhosted.org/packages/e9/36/480206eaefa24a7ec321582dda580443a8f0671fdbf6b1c80e9c3e93a16a/ruff-0.14.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb98fcbbc61725968893682fd4df8966a34611239c9fd07a1f6a07e7103d08e", size = 15123172, upload-time = "2025-12-18T19:29:23.453Z" }, - { url = "https://files.pythonhosted.org/packages/5c/38/68e414156015ba80cef5473d57919d27dfb62ec804b96180bafdeaf0e090/ruff-0.14.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f24b47993a9d8cb858429e97bdf8544c78029f09b520af615c1d261bf827001d", size = 14460260, upload-time = "2025-12-18T19:29:27.808Z" }, - { url = "https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f", size = 14229978, upload-time = "2025-12-18T19:29:11.32Z" }, - { url = "https://files.pythonhosted.org/packages/51/eb/e8dd1dd6e05b9e695aa9dd420f4577debdd0f87a5ff2fedda33c09e9be8c/ruff-0.14.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:213db2b2e44be8625002dbea33bb9c60c66ea2c07c084a00d55732689d697a7f", size = 14338036, upload-time = "2025-12-18T19:29:09.184Z" }, - { url = "https://files.pythonhosted.org/packages/6a/12/f3e3a505db7c19303b70af370d137795fcfec136d670d5de5391e295c134/ruff-0.14.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b914c40ab64865a17a9a5b67911d14df72346a634527240039eb3bd650e5979d", size = 13264051, upload-time = "2025-12-18T19:29:13.431Z" }, - { url = "https://files.pythonhosted.org/packages/08/64/8c3a47eaccfef8ac20e0484e68e0772013eb85802f8a9f7603ca751eb166/ruff-0.14.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1484983559f026788e3a5c07c81ef7d1e97c1c78ed03041a18f75df104c45405", size = 13283998, upload-time = "2025-12-18T19:29:06.994Z" }, - { url = "https://files.pythonhosted.org/packages/12/84/534a5506f4074e5cc0529e5cd96cfc01bb480e460c7edf5af70d2bcae55e/ruff-0.14.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c70427132db492d25f982fffc8d6c7535cc2fd2c83fc8888f05caaa248521e60", size = 13601891, upload-time = "2025-12-18T19:28:55.811Z" }, - { url = "https://files.pythonhosted.org/packages/0d/1e/14c916087d8598917dbad9b2921d340f7884824ad6e9c55de948a93b106d/ruff-0.14.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5bcf45b681e9f1ee6445d317ce1fa9d6cba9a6049542d1c3d5b5958986be8830", size = 14336660, upload-time = "2025-12-18T19:29:16.531Z" }, - { url = "https://files.pythonhosted.org/packages/f2/1c/d7b67ab43f30013b47c12b42d1acd354c195351a3f7a1d67f59e54227ede/ruff-0.14.10-py3-none-win32.whl", hash = "sha256:104c49fc7ab73f3f3a758039adea978869a918f31b73280db175b43a2d9b51d6", size = 13196187, upload-time = "2025-12-18T19:29:19.006Z" }, - { url = "https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl", hash = "sha256:466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154", size = 14661283, upload-time = "2025-12-18T19:29:30.16Z" }, - { url = "https://files.pythonhosted.org/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839, upload-time = "2025-12-18T19:28:48.636Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/57/08/52232a877978dd8f9cf2aeddce3e611b40a63287dfca29b6b8da791f5e8d/ruff-0.14.10.tar.gz", hash = "sha256:9a2e830f075d1a42cd28420d7809ace390832a490ed0966fe373ba288e77aaf4", size = 5859763 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/01/933704d69f3f05ee16ef11406b78881733c186fe14b6a46b05cfcaf6d3b2/ruff-0.14.10-py3-none-linux_armv6l.whl", hash = "sha256:7a3ce585f2ade3e1f29ec1b92df13e3da262178df8c8bdf876f48fa0e8316c49", size = 13527080 }, + { url = "https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f", size = 13797320 }, + { url = "https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d", size = 12918434 }, + { url = "https://files.pythonhosted.org/packages/a6/00/45c62a7f7e34da92a25804f813ebe05c88aa9e0c25e5cb5a7d23dd7450e3/ruff-0.14.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6987ebe0501ae4f4308d7d24e2d0fe3d7a98430f5adfd0f1fead050a740a3a77", size = 13371961 }, + { url = "https://files.pythonhosted.org/packages/40/31/a5906d60f0405f7e57045a70f2d57084a93ca7425f22e1d66904769d1628/ruff-0.14.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16a01dfb7b9e4eee556fbfd5392806b1b8550c9b4a9f6acd3dbe6812b193c70a", size = 13275629 }, + { url = "https://files.pythonhosted.org/packages/3e/60/61c0087df21894cf9d928dc04bcd4fb10e8b2e8dca7b1a276ba2155b2002/ruff-0.14.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7165d31a925b7a294465fa81be8c12a0e9b60fb02bf177e79067c867e71f8b1f", size = 14029234 }, + { url = "https://files.pythonhosted.org/packages/44/84/77d911bee3b92348b6e5dab5a0c898d87084ea03ac5dc708f46d88407def/ruff-0.14.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c561695675b972effb0c0a45db233f2c816ff3da8dcfbe7dfc7eed625f218935", size = 15449890 }, + { url = "https://files.pythonhosted.org/packages/e9/36/480206eaefa24a7ec321582dda580443a8f0671fdbf6b1c80e9c3e93a16a/ruff-0.14.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb98fcbbc61725968893682fd4df8966a34611239c9fd07a1f6a07e7103d08e", size = 15123172 }, + { url = "https://files.pythonhosted.org/packages/5c/38/68e414156015ba80cef5473d57919d27dfb62ec804b96180bafdeaf0e090/ruff-0.14.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f24b47993a9d8cb858429e97bdf8544c78029f09b520af615c1d261bf827001d", size = 14460260 }, + { url = "https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f", size = 14229978 }, + { url = "https://files.pythonhosted.org/packages/51/eb/e8dd1dd6e05b9e695aa9dd420f4577debdd0f87a5ff2fedda33c09e9be8c/ruff-0.14.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:213db2b2e44be8625002dbea33bb9c60c66ea2c07c084a00d55732689d697a7f", size = 14338036 }, + { url = "https://files.pythonhosted.org/packages/6a/12/f3e3a505db7c19303b70af370d137795fcfec136d670d5de5391e295c134/ruff-0.14.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b914c40ab64865a17a9a5b67911d14df72346a634527240039eb3bd650e5979d", size = 13264051 }, + { url = "https://files.pythonhosted.org/packages/08/64/8c3a47eaccfef8ac20e0484e68e0772013eb85802f8a9f7603ca751eb166/ruff-0.14.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1484983559f026788e3a5c07c81ef7d1e97c1c78ed03041a18f75df104c45405", size = 13283998 }, + { url = "https://files.pythonhosted.org/packages/12/84/534a5506f4074e5cc0529e5cd96cfc01bb480e460c7edf5af70d2bcae55e/ruff-0.14.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c70427132db492d25f982fffc8d6c7535cc2fd2c83fc8888f05caaa248521e60", size = 13601891 }, + { url = "https://files.pythonhosted.org/packages/0d/1e/14c916087d8598917dbad9b2921d340f7884824ad6e9c55de948a93b106d/ruff-0.14.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5bcf45b681e9f1ee6445d317ce1fa9d6cba9a6049542d1c3d5b5958986be8830", size = 14336660 }, + { url = "https://files.pythonhosted.org/packages/f2/1c/d7b67ab43f30013b47c12b42d1acd354c195351a3f7a1d67f59e54227ede/ruff-0.14.10-py3-none-win32.whl", hash = "sha256:104c49fc7ab73f3f3a758039adea978869a918f31b73280db175b43a2d9b51d6", size = 13196187 }, + { url = "https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl", hash = "sha256:466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154", size = 14661283 }, + { url = "https://files.pythonhosted.org/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839 }, ] [[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, upload-time = "2023-10-24T04:13:40.426Z" } +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, upload-time = "2023-10-24T04:13:38.866Z" }, + { 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 = "toon-format" +version = "0.9.0b1" +source = { git = "https://github.com/toon-format/toon-python.git?rev=90861444e5bf7d6408e91bd95e58dba41dd99be8#90861444e5bf7d6408e91bd95e58dba41dd99be8" } + [[package]] name = "typer" version = "0.20.1" @@ -798,34 +805,34 @@ dependencies = [ { name = "shellingham" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c1/933d30fd7a123ed981e2a1eedafceab63cb379db0402e438a13bc51bbb15/typer-0.20.1.tar.gz", hash = "sha256:68585eb1b01203689c4199bc440d6be616f0851e9f0eb41e4a778845c5a0fd5b", size = 105968, upload-time = "2025-12-19T16:48:56.302Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/c1/933d30fd7a123ed981e2a1eedafceab63cb379db0402e438a13bc51bbb15/typer-0.20.1.tar.gz", hash = "sha256:68585eb1b01203689c4199bc440d6be616f0851e9f0eb41e4a778845c5a0fd5b", size = 105968 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/52/1f2df7e7d1be3d65ddc2936d820d4a3d9777a54f4204f5ca46b8513eff77/typer-0.20.1-py3-none-any.whl", hash = "sha256:4b3bde918a67c8e03d861aa02deca90a95bbac572e71b1b9be56ff49affdb5a8", size = 47381, upload-time = "2025-12-19T16:48:53.679Z" }, + { url = "https://files.pythonhosted.org/packages/c8/52/1f2df7e7d1be3d65ddc2936d820d4a3d9777a54f4204f5ca46b8513eff77/typer-0.20.1-py3-none-any.whl", hash = "sha256:4b3bde918a67c8e03d861aa02deca90a95bbac572e71b1b9be56ff49affdb5a8", size = 47381 }, ] [[package]] name = "types-pyyaml" version = "6.0.12.20250915" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", size = 17522, upload-time = "2025-09-15T03:01:00.728Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", size = 17522 } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z" }, + { url = "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338 }, ] [[package]] name = "typing-extensions" version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391 } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614 }, ] [[package]] name = "urllib3" version = "2.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556 } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584 }, ]