diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a822f5bd..3ae602c8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -55,5 +55,8 @@ jobs: - name: Check formatting run: uv run ruff format --check . + - name: Check types + run: uv run mypy + - name: Check docstring coverage run: uv run interrogate diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 96e5b004..270c5bea 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,6 +19,14 @@ repos: language: system types: [python] require_serial: true + # No filenames: mypy reads its own scope (core/ and nb-dt-import.py) from pyproject.toml. + - id: mypy + name: mypy + entry: uv run --native-tls mypy + language: system + types: [python] + pass_filenames: false + require_serial: true - id: interrogate name: interrogate entry: uv run --native-tls interrogate diff --git a/core/change_detector.py b/core/change_detector.py index aee6014e..389927d2 100644 --- a/core/change_detector.py +++ b/core/change_detector.py @@ -376,7 +376,7 @@ def _compare_component_properties( # so that any change to rear port name, front_port_position, or # rear_port_position is detected. yaml_mappings = yaml_comp.get("_mappings") or [] - yaml_set = frozenset( + yaml_set: frozenset = frozenset( ( m.get("rear_port", ""), m.get("front_port_position", 1), @@ -392,7 +392,7 @@ def _compare_component_properties( has_names = any(m.get("rear_port_name") is not None for m in canonical) if has_names: # NetBox >= 4.5: compare with rear port names - netbox_set = frozenset( + netbox_set: frozenset = frozenset( ( m.get("rear_port_name", ""), m.get("front_port_position", 1), diff --git a/core/component_cache.py b/core/component_cache.py index 32318c8f..d8dc4a22 100644 --- a/core/component_cache.py +++ b/core/component_cache.py @@ -12,6 +12,7 @@ import concurrent.futures import queue import threading +from typing import Any from core.compat import ( device_type_filter_key, @@ -136,9 +137,9 @@ def __init__(self, netbox, graphql, handle, new_filters, max_threads, wrap_recor self.max_threads = max_threads self._wrap_record = wrap_record or (lambda record: record) - self._entries = {} + self._entries: dict = {} self._ready = False - self._job = None + self._job: Any = None # ── State ──────────────────────────────────────────────────────────────── @@ -169,7 +170,7 @@ def begin_prefetch(self, manufacturer_slug=None, display=None): for component in COMPONENT_TYPES: display.add(component.endpoint, component.plural_label) - updates = queue.Queue() + updates: queue.Queue = queue.Queue() max_workers = max(1, min(len(COMPONENT_TYPES), self.max_threads)) executor = concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) worker_state = threading.local() @@ -369,7 +370,7 @@ def _pending(self): def _drain_updates(self): """Apply every queued page count to the display, coalescing per endpoint.""" - totals = {} + totals: dict = {} while True: try: endpoint_name, advance = self._job["updates"].get_nowait() diff --git a/core/component_registry.py b/core/component_registry.py index 21bac067..ed991052 100644 --- a/core/component_registry.py +++ b/core/component_registry.py @@ -25,10 +25,10 @@ class ComponentType: yaml_key: str endpoint: str label: str - fields: tuple + fields: tuple[str, ...] module_types: bool = True - graphql_extra: tuple = field(default_factory=tuple) - compare_extra: tuple = field(default_factory=tuple) + graphql_extra: tuple[str, ...] = field(default_factory=tuple) + compare_extra: tuple[str, ...] = field(default_factory=tuple) link: Optional[str] = None @property diff --git a/core/config.py b/core/config.py index 7a67f694..d37778e6 100644 --- a/core/config.py +++ b/core/config.py @@ -56,8 +56,8 @@ class RunConfig: repo_branch: str repo_path: str - vendors: tuple = () - slugs: tuple = () + vendors: tuple[str, ...] = () + slugs: tuple[str, ...] = () export_diff: bool = False export_diff_dir: str = "extra/" @@ -74,7 +74,7 @@ class RunConfig: show_remaining_time: bool = False # Resolution decisions worth telling the user about, logged once the handler exists. - notices: tuple = field(default_factory=tuple) + notices: tuple[str, ...] = field(default_factory=tuple) def _text(env, name, default=None): @@ -246,7 +246,7 @@ def _split_slugs(values): return tuple(s.strip() for slug in values for s in slug.split(",") if s.strip()) -def resolve_run_config(argv=None, env=None): +def resolve_run_config(argv=None, env=None) -> RunConfig: """Resolve *argv* and *env* into one RunConfig, or raise ConfigError. Reads argv before the environment, so ``--help`` answers without an environment diff --git a/core/export.py b/core/export.py index c3e43aa1..c0dd57ac 100644 --- a/core/export.py +++ b/core/export.py @@ -9,7 +9,7 @@ import threading from dataclasses import dataclass from pathlib import Path -from typing import Any, List, Optional +from typing import Any, List, Optional, Sequence import requests import yaml @@ -30,7 +30,12 @@ from core.netbox_api import IMAGE_EXTENSIONS, _build_auth_header from core.repo import LIBRARY_TYPE_DIRS, library_dirs_present -_SKIP = object() # sentinel: image already exists, no download needed + +class _SkipSentinel: + """Type of the ``_SKIP`` sentinel: the image already exists, so no download is needed.""" + + +_SKIP = _SkipSentinel() # Maps Content-Type to a canonical extension for extension-less attachments. _CONTENT_TYPE_EXT = { @@ -191,13 +196,17 @@ def _is_subset(sub: Any, sup: Any) -> bool: class Exporter: """Exports NetBox device/module/rack types to a local directory in DTL format.""" - def __init__(self, config, handle, export_dir: str, force_overwrite: bool, vendor_slugs: Optional[List[str]]): + def __init__(self, config, handle, export_dir: str, force_overwrite: bool, vendor_slugs: Optional[Sequence[str]]): """Initialize the Exporter from the resolved run configuration.""" self.config = config self.handle = handle self.export_dir = Path(export_dir) self.force_overwrite = force_overwrite - self.vendor_slugs = vendor_slugs # None means all vendors + # tuple("cisco") is five single-character slugs, each one valid to the GraphQL layer. + if isinstance(vendor_slugs, (str, bytes)): + raise ValueError("vendor_slugs must be None or a sequence of vendor slugs, not a bare string") + # None means all vendors. The GraphQL layer rejects an empty sequence, so normalize one here. + self.vendor_slugs = tuple(vendor_slugs) if vendor_slugs else None self.repo_path = Path(config.repo_path) self.base_url = config.netbox_url.rstrip("/") self.token = config.netbox_token @@ -233,11 +242,9 @@ def run(self, progress=None) -> None: self.handle.log(f"Export-diff: fetching NetBox device/module/rack types{scope}") # ── Fetch all types from NetBox ────────────────────────────────────── - by_model, by_slug = self.graphql.get_device_types( - manufacturer_slugs=self.vendor_slugs if self.vendor_slugs else None - ) - all_mt = self.graphql.get_module_types(manufacturer_slugs=self.vendor_slugs if self.vendor_slugs else None) - all_rt = self.graphql.get_rack_types(manufacturer_slugs=self.vendor_slugs if self.vendor_slugs else None) + by_model, by_slug = self.graphql.get_device_types(manufacturer_slugs=self.vendor_slugs) + all_mt = self.graphql.get_module_types(manufacturer_slugs=self.vendor_slugs) + all_rt = self.graphql.get_rack_types(manufacturer_slugs=self.vendor_slugs) total_dt = len(by_model) total_mt = sum(len(v) for v in all_mt.values()) @@ -671,6 +678,7 @@ def _determine_export_set_for_device_types( manifest_key = f"{mfr_name}/{rec.slug}" repo_yaml = repo_dt_by_slug.get((mfr_slug, rec.slug)) + reason: Optional[str] if repo_yaml is None: reason = "absent" elif _repo_supersedes(repo_yaml, serialized): @@ -911,7 +919,9 @@ def _download_module_type_images(self, item: ExportItem) -> bool: ok = False return ok - def _download_image(self, url_path: str, dest: Path, content_type_out: "Optional[list]" = None) -> "Optional[str]": + def _download_image( + self, url_path: str, dest: Path, content_type_out: "Optional[list]" = None + ) -> "str | _SkipSentinel | None": """Download an image from NetBox and write to *dest*. Returns SHA-256 hex digest on success, None on failure, or the module-level diff --git a/core/export_manifest.py b/core/export_manifest.py index 9d632420..b959ddeb 100644 --- a/core/export_manifest.py +++ b/core/export_manifest.py @@ -9,7 +9,7 @@ import os from pathlib import Path -_EMPTY = {"device-types": {}, "module-types": {}, "rack-types": {}} +_EMPTY: dict = {"device-types": {}, "module-types": {}, "rack-types": {}} def load_manifest(path: Path) -> dict: diff --git a/core/graphql_client.py b/core/graphql_client.py index 953d4d59..70baabfd 100644 --- a/core/graphql_client.py +++ b/core/graphql_client.py @@ -7,6 +7,7 @@ import threading import time +from collections.abc import Sequence import requests @@ -78,7 +79,7 @@ def _to_dotdict(obj): IDs as strings but the rest of the codebase expects integer IDs. """ if isinstance(obj, dict): - converted = {} + converted: dict = {} for k, v in obj.items(): if k == "id" and isinstance(v, str): try: @@ -362,16 +363,29 @@ def _build_manufacturer_filter(self, slugs): ``extra_variables`` is the dict to pass as ``variables`` to :meth:`query_all`. + This is the one place manufacturer_slugs is validated, for every caller. + Args: - slugs: ``None`` or a non-empty list of manufacturer slug strings. + slugs: ``None`` or a non-empty sequence (list or tuple) of manufacturer + slug strings. Returns: tuple[str, str, dict] + + Raises: + ValueError: If *slugs* is anything but None or a non-empty sequence of + non-blank strings. """ - if not slugs: + if slugs is None: return "", "", {} - if not isinstance(slugs, list) or any(not isinstance(s, str) or not s.strip() for s in slugs): - raise ValueError("manufacturer_slugs must be None or a non-empty list of non-empty strings") + # A str is itself a Sequence of characters, so reject it before the item check. + if ( + isinstance(slugs, (str, bytes)) + or not isinstance(slugs, Sequence) + or not slugs + or any(not isinstance(s, str) or not s.strip() for s in slugs) + ): + raise ValueError("manufacturer_slugs must be None or a non-empty sequence of non-empty strings") slugs = [s.strip() for s in slugs] if len(slugs) == 1: return ( @@ -389,8 +403,9 @@ def get_device_types(self, manufacturer_slugs=None): """Fetch all device types and return two lookup indexes. Args: - manufacturer_slugs: Optional list of manufacturer slugs to filter by. - When provided, only device types from the specified manufacturers are returned. + manufacturer_slugs: Optional sequence (list or tuple) of manufacturer slugs + to filter by. When provided, only device types from the specified + manufacturers are returned. Returns: tuple[dict, dict]: @@ -398,11 +413,9 @@ def get_device_types(self, manufacturer_slugs=None): - ``by_slug``: ``{(manufacturer_slug, slug): record}`` Raises: - ValueError: If *manufacturer_slugs* is an empty list. + ValueError: If *manufacturer_slugs* is anything but None or a non-empty + sequence of non-blank strings. """ - if manufacturer_slugs is not None and len(manufacturer_slugs) == 0: - raise ValueError("manufacturer_slugs must be None or a non-empty list") - var_decl, filter_fragment, extra_vars = self._build_manufacturer_filter(manufacturer_slugs) query = f""" @@ -452,18 +465,17 @@ def get_module_types(self, manufacturer_slugs=None): """Fetch all module types and return them indexed by manufacturer slug and model. Args: - manufacturer_slugs: Optional list of manufacturer slugs to filter by. - When provided, only module types from the specified manufacturers are returned. + manufacturer_slugs: Optional sequence (list or tuple) of manufacturer slugs + to filter by. When provided, only module types from the specified + manufacturers are returned. Returns: dict: ``{manufacturer_slug: {model: record}}`` Raises: - ValueError: If *manufacturer_slugs* is an empty list. + ValueError: If *manufacturer_slugs* is anything but None or a non-empty + sequence of non-blank strings. """ - if manufacturer_slugs is not None and len(manufacturer_slugs) == 0: - raise ValueError("manufacturer_slugs must be None or a non-empty list") - var_decl, filter_fragment, extra_vars = self._build_manufacturer_filter(manufacturer_slugs) query = f""" @@ -488,7 +500,7 @@ def get_module_types(self, manufacturer_slugs=None): """ items = self.query_all(query, list_key="module_type_list", variables=extra_vars or None) - result = {} + result: dict = {} for item in items: record = _to_dotdict(item) mfr_slug = record.manufacturer.slug @@ -500,18 +512,17 @@ def get_rack_types(self, manufacturer_slugs=None): """Fetch all rack types and return them indexed by manufacturer slug and model. Args: - manufacturer_slugs: Optional list of manufacturer slugs to filter by. - When provided, only rack types from the specified manufacturers are returned. + manufacturer_slugs: Optional sequence (list or tuple) of manufacturer slugs + to filter by. When provided, only rack types from the specified + manufacturers are returned. Returns: dict: ``{manufacturer_slug: {model: record}}`` Raises: - ValueError: If *manufacturer_slugs* is an empty list. + ValueError: If *manufacturer_slugs* is anything but None or a non-empty + sequence of non-blank strings. """ - if manufacturer_slugs is not None and len(manufacturer_slugs) == 0: - raise ValueError("manufacturer_slugs must be None or a non-empty list") - var_decl, filter_fragment, extra_vars = self._build_manufacturer_filter(manufacturer_slugs) query = f""" @@ -545,7 +556,7 @@ def get_rack_types(self, manufacturer_slugs=None): }} """ items = self.query_all(query, list_key="rack_type_list", variables=extra_vars or None) - result = {} + result: dict = {} for item in items: record = _to_dotdict(item) mfr_slug = record.manufacturer.slug @@ -600,7 +611,7 @@ def get_module_type_images(self): and (i.get("object_type") or {}).get("model") == "moduletype" ] - result = {} + result: dict = {} for item in items: name = item.get("name") if not name: @@ -660,7 +671,7 @@ def get_module_type_image_details(self): and (i.get("object_type") or {}).get("model") == "moduletype" ] - result = {} + result: dict = {} for item in items: name = item.get("name") if not name: @@ -743,7 +754,8 @@ def _build_query(field_list): # Only a schema rejection means the tier is unsupported. Transport failures # propagate: falling back on those queries an older tier against a server that # supports the newer one, silently dropping the mappings data. - original_exc = last_exc = None + original_exc: GraphQLSchemaError | None = None + last_exc: GraphQLSchemaError | None = None for variant in field_variants: try: return self.query_all( @@ -754,6 +766,8 @@ def _build_query(field_list): if original_exc is None: original_exc = exc + if last_exc is None: # pragma: no cover - every variant list carries at least one entry + raise GraphQLError(f"No field variant was attempted for {list_key}") if original_exc is last_exc: raise last_exc raise last_exc from original_exc @@ -776,13 +790,14 @@ def get_component_templates(self, endpoint_name, manufacturer_slug=None, on_page Raises: ValueError: If *endpoint_name* is not a recognized component template endpoint. - ValueError: If *manufacturer_slug* is an empty string. + ValueError: If *manufacturer_slug* is anything but None or a non-blank string. """ component = BY_ENDPOINT.get(endpoint_name) if component is None: raise ValueError(f"Unknown component endpoint: {endpoint_name}") - if manufacturer_slug is not None and len(manufacturer_slug) == 0: + # A sequence would pass a bare length check and build a filter that matches nothing. + if manufacturer_slug is not None and (not isinstance(manufacturer_slug, str) or not manufacturer_slug.strip()): raise ValueError("manufacturer_slug must be None or a non-empty string") fields = component.graphql_fields diff --git a/core/import_run.py b/core/import_run.py index 00da6870..92adbafc 100644 --- a/core/import_run.py +++ b/core/import_run.py @@ -5,6 +5,7 @@ from dataclasses import dataclass from datetime import datetime, timedelta import os +from typing import Any from core.change_detector import ChangeDetector, ChangeType, IMAGE_PROPERTIES from core.component_cache import NullTaskDisplay, RichTaskDisplay @@ -575,7 +576,7 @@ def __init__(self, config, repo, netbox, reporter, progress_factory, *, started_ self.reporter = reporter self.progress_factory = progress_factory self.started_at = started_at or datetime.now() - self.progress = None + self.progress: Any = None self.task_registry = None self.vendor_task_id = None diff --git a/core/log_handler.py b/core/log_handler.py index 972fa7d3..2a53eab9 100644 --- a/core/log_handler.py +++ b/core/log_handler.py @@ -15,7 +15,7 @@ def __init__(self, verbose: bool): self.verbose = verbose self.console = None self._defer_depth = 0 - self._deferred_messages = [] + self._deferred_messages: list = [] def _timestamp(self): """Return the current time formatted as HH:MM:SS.""" diff --git a/core/nb_serializer.py b/core/nb_serializer.py index 9d382d10..c6535506 100644 --- a/core/nb_serializer.py +++ b/core/nb_serializer.py @@ -5,7 +5,7 @@ """ import warnings -from typing import Any +from typing import Any, Sequence from core.component_registry import BY_ENDPOINT, COMPONENT_TYPES @@ -114,7 +114,7 @@ def _should_include(field: str, val: Any) -> bool: return True -def _serialize_component(record: Any, fields: list) -> dict: +def _serialize_component(record: Any, fields: Sequence[str]) -> dict: """Serialize a single component template record to a YAML-ready dict.""" result = {} for field in fields: diff --git a/core/netbox_api.py b/core/netbox_api.py index 7425479f..6338f07b 100644 --- a/core/netbox_api.py +++ b/core/netbox_api.py @@ -12,6 +12,7 @@ import os import glob from pathlib import Path +from typing import Any, Optional from core.change_detector import ChangeDetector, ChangeType from core.component_cache import ComponentCache @@ -213,7 +214,7 @@ def _is_image_hash_changed(local_path: str, hash_cache: dict, log_fn=None) -> bo return current != cached -def _load_image_hash_cache(path: str, log_fn=None) -> dict: +def _load_image_hash_cache(path: Optional[str], log_fn=None) -> dict: """Load the image-hash cache from *path* (JSON), returning an empty dict when it cannot be read. An absent file is the normal first run and stays quiet. Anything else means the @@ -448,7 +449,7 @@ def __init__(self, config, handle): self.repo_path = config.repo_path self.verbose = config.verbose self.handle = handle - self.netbox = None + self.netbox: Any = None self.ignore_ssl = config.ignore_ssl_errors self.modules = False self.new_filters = False @@ -465,7 +466,7 @@ def __init__(self, config, handle): _cache_dir = Path(os.environ.get("XDG_CACHE_HOME", Path.home() / ".cache")) / "nb-dt-import" try: _cache_dir.mkdir(parents=True, exist_ok=True) - self._image_hash_cache_path = str(_cache_dir / "image-hashes.json") + self._image_hash_cache_path: Optional[str] = str(_cache_dir / "image-hashes.json") except OSError as exc: self.handle.log( "[yellow]Warning: could not create image hash cache directory " @@ -2019,6 +2020,7 @@ def __init__(self, record): """ object.__setattr__(self, "_record", record) mappings_raw = getattr(record, "mappings", None) + canonical: Optional[list] if mappings_raw is not None: # NetBox >= 4.5: mappings is a list of PortTemplateMapping objects canonical = [] @@ -2407,8 +2409,8 @@ def update_components(self, yaml_data, device_type_id, component_changes, parent parent_type: "device" or "module" """ # Group changes by component type and change type - changes_to_update = {} - changes_to_add = {} + changes_to_update: dict = {} + changes_to_add: dict = {} for change in component_changes: if change.change_type == ChangeType.COMPONENT_CHANGED: if change.component_type not in changes_to_update: @@ -2437,7 +2439,7 @@ def remove_components(self, device_type_id, component_changes, parent_type="devi removals = [c for c in component_changes if c.change_type == ChangeType.COMPONENT_REMOVED] # Group removals by component type - removals_by_type = {} + removals_by_type: dict = {} for removal in removals: if removal.component_type not in removals_by_type: removals_by_type[removal.component_type] = [] diff --git a/core/repo.py b/core/repo.py index 57656278..2022cb57 100644 --- a/core/repo.py +++ b/core/repo.py @@ -6,7 +6,7 @@ import pickle from glob import glob from re import sub as re_sub -from typing import Optional +from typing import Optional, Sequence from urllib.parse import urlparse from git import Repo, exc import yaml @@ -320,7 +320,7 @@ def normalize_port_mappings(data): # --- Old inline format --- # Collect rear_port references declared directly on front-port entries. - inline_mappings = {} # {front_port_name: [mapping_dict, ...]} + inline_mappings: dict = {} # {front_port_name: [mapping_dict, ...]} for fp in front_ports: rp_name = fp.get("rear_port") if rp_name is None: @@ -339,7 +339,7 @@ def normalize_port_mappings(data): ) # --- New port-mappings stanza --- - stanza_mappings = {} # {front_port_name: [mapping_dict, ...]} + stanza_mappings: dict = {} # {front_port_name: [mapping_dict, ...]} if "port-mappings" in data: for entry in port_mappings_stanza or []: fp_name = entry.get("front_port") @@ -589,7 +589,7 @@ def clone_repo(self): except Exception as git_error: raise UnknownError("Git Repository Error", cause=git_error) from git_error - def get_devices(self, base_path, vendors: list = None): + def get_devices(self, base_path, vendors: Optional[Sequence[str]] = None): """Discover device YAML files and vendor directories under a base path. Args: @@ -663,7 +663,7 @@ def resolve_slug_files(self, slugs): slugs_lower = [s.casefold() for s in slugs] # --- device types -------------------------------------------------- - device_files = {} # vendor_slug -> [abs_path] + device_files: dict = {} # vendor_slug -> [abs_path] try: known_slugs = _safe_index_load(device_index) except Exception: @@ -733,7 +733,7 @@ def discover_vendors(self, devices_path, modules_path, racks_path): # Return sorted list by slug return sorted(vendors_dict.values(), key=lambda v: v["slug"]) - def parse_files(self, files: list, slugs: list = None, progress=None): + def parse_files(self, files: list, slugs: Optional[Sequence[str]] = None, progress=None): """Parse YAML device files into device type dicts, optionally filtering and tracking progress. Args: @@ -787,7 +787,7 @@ def parse_files(self, files: list, slugs: list = None, progress=None): # for the user to fix upstream. deduped = [] seen = {} # key -> kept item - groups = {} # key -> list of all srcs in sorted order + groups: dict = {} # key -> list of all srcs in sorted order for item in sorted(deviceTypes, key=lambda d: d.get("src", "")): try: key = (item["manufacturer"]["slug"], item.get("model")) diff --git a/nb-dt-import.py b/nb-dt-import.py index acb339ab..6d53f2da 100644 --- a/nb-dt-import.py +++ b/nb-dt-import.py @@ -22,7 +22,7 @@ ) from rich.text import Text -from core.config import ConfigError, resolve_run_config +from core.config import ConfigError, RunConfig, resolve_run_config from core.errors import FatalError from core.graphql_client import GraphQLError from core.import_run import ImportRun @@ -112,7 +112,7 @@ def get_progress_panel(show_remaining_time=False): yield progress -def _run_export_diff(config, handle): +def _run_export_diff(config: RunConfig, handle): """Run the export-diff pipeline.""" from core.export import Exporter @@ -121,7 +121,7 @@ def _run_export_diff(config, handle): handle=handle, export_dir=config.export_diff_dir, force_overwrite=config.force_export_overwrite, - vendor_slugs=config.vendors if config.vendors else None, + vendor_slugs=config.vendors, ) with get_progress_panel(config.show_remaining_time) as progress: if progress is not None: @@ -133,7 +133,7 @@ def _run_export_diff(config, handle): handle.set_console(None) -def _run(config): +def _run(config: RunConfig): """Build and execute the selected run pipeline.""" started_at = datetime.now() handle = LogHandler(config.verbose) diff --git a/pyproject.toml b/pyproject.toml index 6fd4477f..0101090d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,12 +16,14 @@ dependencies = [ [dependency-groups] dev = [ "interrogate>=1.7.0", + "mypy>=2.3.1", "pre-commit>=4.6.2", "pytest>=9.1.1", "pytest-cov>=6.0", "pytest-mock>=3.15.1", "pytest-timeout>=2.4.0", "ruff>=0.16.2", + "types-pyyaml>=6.0.12.20260815", "zizmor==1.29.0", ] @@ -52,6 +54,17 @@ max-complexity = 15 [tool.ruff.lint.per-file-ignores] "tests/**" = ["D102", "D103"] # test functions don't need docstrings +[tool.mypy] +python_version = "3.12" +files = ["core", "nb-dt-import.py"] +# Most of core/ carries no annotations, and mypy skips unannotated bodies by default. +# Those bodies are where this bug class hides: a tuple reaching a list-typed parameter. +check_untyped_defs = true + +[[tool.mypy.overrides]] +module = ["pynetbox.*"] +ignore_missing_imports = true + [tool.interrogate] # Measure docstring coverage on first-party source only. The vendored # Device-Type-Library lives under repo/, and test functions are exempt from diff --git a/tests/test_exporter.py b/tests/test_exporter.py index a356e528..18c06bf2 100644 --- a/tests/test_exporter.py +++ b/tests/test_exporter.py @@ -715,6 +715,30 @@ def test_absent_module_type_triggers_export(self, tmp_path): assert items[0].kind == "module-type" +class TestVendorSlugNormalization: + """vendor_slugs reaches every downstream call, so the Exporter normalizes it once.""" + + def _make_exporter(self, tmp_path, vendor_slugs): + settings = _make_settings(tmp_path) + return Exporter(settings, _make_handle(), str(tmp_path / "extra"), False, vendor_slugs) + + @pytest.mark.parametrize("empty", [(), [], None], ids=["tuple", "list", "none"]) + def test_empty_selection_becomes_all_vendors(self, tmp_path, empty): + """The GraphQL layer rejects an empty sequence, so an empty selection must not reach it.""" + assert self._make_exporter(tmp_path, empty).vendor_slugs is None + + def test_selection_is_kept_as_a_tuple(self, tmp_path): + """--vendors arrives from the CLI as a tuple and stays one.""" + assert self._make_exporter(tmp_path, ("nokia",)).vendor_slugs == ("nokia",) + assert self._make_exporter(tmp_path, ["nokia"]).vendor_slugs == ("nokia",) + + @pytest.mark.parametrize("bare", ["cisco", b"cisco"], ids=["str", "bytes"]) + def test_bare_string_raises_value_error(self, tmp_path, bare): + """tuple("cisco") would filter on five single-character slugs, and every one of them is valid.""" + with pytest.raises(ValueError, match="vendor_slugs must be None or a sequence"): + self._make_exporter(tmp_path, bare) + + class TestVendorDirSlugNormalization: """Tests for Exporter._vendor_dirs slug-based directory matching.""" @@ -725,7 +749,7 @@ def _make_exporter(self, tmp_path, vendor_slugs): def test_single_word_dir_matches_slug(self, tmp_path): root = tmp_path / "device-types" (root / "Nokia").mkdir(parents=True) - exporter = self._make_exporter(tmp_path, ["nokia"]) + exporter = self._make_exporter(tmp_path, ("nokia",)) dirs = list(exporter._vendor_dirs(root)) assert len(dirs) == 1 assert dirs[0].name == "Nokia" @@ -734,7 +758,7 @@ def test_multi_word_dir_matches_hyphenated_slug(self, tmp_path): """'Extreme Networks' dir must match slug 'extreme-networks'.""" root = tmp_path / "device-types" (root / "Extreme Networks").mkdir(parents=True) - exporter = self._make_exporter(tmp_path, ["extreme-networks"]) + exporter = self._make_exporter(tmp_path, ("extreme-networks",)) dirs = list(exporter._vendor_dirs(root)) assert len(dirs) == 1 assert dirs[0].name == "Extreme Networks" @@ -743,7 +767,7 @@ def test_non_matching_vendor_excluded(self, tmp_path): root = tmp_path / "device-types" (root / "Nokia").mkdir(parents=True) (root / "Juniper").mkdir(parents=True) - exporter = self._make_exporter(tmp_path, ["nokia"]) + exporter = self._make_exporter(tmp_path, ("nokia",)) dirs = list(exporter._vendor_dirs(root)) assert len(dirs) == 1 assert dirs[0].name == "Nokia" diff --git a/tests/test_graphql_client.py b/tests/test_graphql_client.py index 39d9376c..6c5b22b5 100644 --- a/tests/test_graphql_client.py +++ b/tests/test_graphql_client.py @@ -1682,6 +1682,72 @@ def test_request_exception_retries_before_exhausting(self, mock_post): # ── Vendor-scoped filtering tests ───────────────────────────────────────── +class TestBuildManufacturerFilter: + """Tests for _build_manufacturer_filter() input handling.""" + + def _make_client(self): + from core.graphql_client import NetBoxGraphQLClient + + return NetBoxGraphQLClient("http://netbox.local", "tok") + + def test_single_slug_tuple_matches_list(self): + """config.vendors is a tuple, so a tuple must build the same filter as a list.""" + client = self._make_client() + assert client._build_manufacturer_filter(("cisco",)) == client._build_manufacturer_filter(["cisco"]) + + def test_multiple_slug_tuple_matches_list(self): + """A multi-slug tuple must build the same filter as the equivalent list.""" + client = self._make_client() + assert client._build_manufacturer_filter(("cisco", "juniper")) == client._build_manufacturer_filter( + ["cisco", "juniper"] + ) + + def test_tuple_slugs_are_stripped_and_sent_as_list(self): + """The GraphQL variable must be a JSON-serializable list, not the incoming tuple.""" + client = self._make_client() + var_decl, filter_fragment, variables = client._build_manufacturer_filter((" cisco ", "juniper ")) + assert var_decl == ", $manufacturer_slugs: [String!]!" + assert filter_fragment == "filters: {manufacturer: {slug: {in_list: $manufacturer_slugs}}}, " + assert variables == {"manufacturer_slugs": ["cisco", "juniper"]} + + def test_bare_string_raises_value_error(self): + """A bare string is a sequence of characters, not a sequence of slugs.""" + client = self._make_client() + with pytest.raises(ValueError, match="manufacturer_slugs must be None or a non-empty"): + client._build_manufacturer_filter("cisco") + + def test_non_string_item_raises_value_error(self): + + client = self._make_client() + with pytest.raises(ValueError, match="manufacturer_slugs must be None or a non-empty"): + client._build_manufacturer_filter(("cisco", 5)) + + def test_blank_item_raises_value_error(self): + + client = self._make_client() + with pytest.raises(ValueError, match="manufacturer_slugs must be None or a non-empty"): + client._build_manufacturer_filter(("cisco", " ")) + + @pytest.mark.parametrize("slugs", [5, 5.0, object()], ids=["int", "float", "object"]) + def test_non_sequence_raises_value_error(self, slugs): + """Garbage input is a caller error, not a TypeError from a length check.""" + client = self._make_client() + with pytest.raises(ValueError, match="manufacturer_slugs must be None or a non-empty"): + client._build_manufacturer_filter(slugs) + + @pytest.mark.parametrize("slugs", [(), []], ids=["tuple", "list"]) + def test_empty_sequence_raises_value_error(self, slugs): + """An empty selection would silently widen the query to every manufacturer.""" + client = self._make_client() + with pytest.raises(ValueError, match="manufacturer_slugs must be None or a non-empty"): + client._build_manufacturer_filter(slugs) + + def test_none_builds_no_filter(self): + """Only None means "every manufacturer".""" + client = self._make_client() + assert client._build_manufacturer_filter(None) == ("", "", {}) + + class TestVendorScopedDeviceTypes: """Tests for vendor-scoped filtering in get_device_types().""" @@ -1690,7 +1756,8 @@ def _make_client(self): return NetBoxGraphQLClient("http://netbox.local", "tok") - def test_single_vendor_filter(self, mock_post): + @pytest.mark.parametrize("slugs", [["cisco"], ("cisco",)], ids=["list", "tuple"]) + def test_single_vendor_filter(self, mock_post, slugs): """Test filtering by a single manufacturer slug.""" data = { "device_type_list": [ @@ -1716,7 +1783,7 @@ def test_single_vendor_filter(self, mock_post): mock_post.side_effect = _make_paged_responses(data, "device_type_list") client = self._make_client() - by_model, by_slug = client.get_device_types(manufacturer_slugs=["cisco"]) + by_model, by_slug = client.get_device_types(manufacturer_slugs=slugs) assert ("cisco", "Catalyst 3850") in by_model assert by_model[("cisco", "Catalyst 3850")].model == "Catalyst 3850" @@ -1728,7 +1795,8 @@ def test_single_vendor_filter(self, mock_post): assert "$manufacturer_slug: String!" in query assert variables["manufacturer_slug"] == "cisco" - def test_multiple_vendor_filter(self, mock_post): + @pytest.mark.parametrize("slugs", [["cisco", "juniper"], ("cisco", "juniper")], ids=["list", "tuple"]) + def test_multiple_vendor_filter(self, mock_post, slugs): """Test filtering by multiple manufacturer slugs.""" data = { "device_type_list": [ @@ -1771,7 +1839,7 @@ def test_multiple_vendor_filter(self, mock_post): mock_post.side_effect = _make_paged_responses(data, "device_type_list") client = self._make_client() - by_model, by_slug = client.get_device_types(manufacturer_slugs=["cisco", "juniper"]) + by_model, by_slug = client.get_device_types(manufacturer_slugs=slugs) assert ("cisco", "Catalyst 3850") in by_model assert ("juniper", "EX4300") in by_model @@ -1818,11 +1886,18 @@ def test_none_manufacturer_slugs_unfiltered(self, mock_post): assert "filters:" not in query assert "manufacturer_slug" not in variables - def test_empty_list_raises_value_error(self): - """Passing [] for manufacturer_slugs should raise ValueError immediately.""" + @pytest.mark.parametrize("slugs", [[], ()], ids=["list", "tuple"]) + def test_empty_sequence_raises_value_error(self, slugs): + """An empty manufacturer_slugs sequence should raise ValueError immediately.""" client = self._make_client() - with pytest.raises(ValueError, match="manufacturer_slugs must be None or a non-empty list"): - client.get_device_types(manufacturer_slugs=[]) + with pytest.raises(ValueError, match="manufacturer_slugs must be None or a non-empty"): + client.get_device_types(manufacturer_slugs=slugs) + + def test_non_sequence_raises_value_error(self): + """Garbage input must fail as a caller error, not as a TypeError from a length check.""" + client = self._make_client() + with pytest.raises(ValueError, match="manufacturer_slugs must be None or a non-empty"): + client.get_device_types(manufacturer_slugs=5) class TestVendorScopedModuleTypes: @@ -1833,7 +1908,8 @@ def _make_client(self): return NetBoxGraphQLClient("http://netbox.local", "tok") - def test_single_vendor_filter(self, mock_post): + @pytest.mark.parametrize("slugs", [["cisco"], ("cisco",)], ids=["list", "tuple"]) + def test_single_vendor_filter(self, mock_post, slugs): """Test filtering by a single manufacturer slug.""" data = { "module_type_list": [ @@ -1853,7 +1929,7 @@ def test_single_vendor_filter(self, mock_post): mock_post.side_effect = _make_paged_responses(data, "module_type_list") client = self._make_client() - result = client.get_module_types(manufacturer_slugs=["cisco"]) + result = client.get_module_types(manufacturer_slugs=slugs) assert "cisco" in result assert "C9300-NM-8X" in result["cisco"] @@ -1865,7 +1941,8 @@ def test_single_vendor_filter(self, mock_post): assert "$manufacturer_slug: String!" in query assert variables["manufacturer_slug"] == "cisco" - def test_multiple_vendor_filter(self, mock_post): + @pytest.mark.parametrize("slugs", [["cisco", "juniper"], ("cisco", "juniper")], ids=["list", "tuple"]) + def test_multiple_vendor_filter(self, mock_post, slugs): """Test filtering by multiple manufacturer slugs.""" data = { "module_type_list": [ @@ -1896,7 +1973,7 @@ def test_multiple_vendor_filter(self, mock_post): mock_post.side_effect = _make_paged_responses(data, "module_type_list") client = self._make_client() - result = client.get_module_types(manufacturer_slugs=["cisco", "juniper"]) + result = client.get_module_types(manufacturer_slugs=slugs) assert "cisco" in result assert "juniper" in result @@ -1908,11 +1985,18 @@ def test_multiple_vendor_filter(self, mock_post): assert "$manufacturer_slugs: [String!]!" in query assert variables["manufacturer_slugs"] == ["cisco", "juniper"] - def test_empty_list_raises_value_error(self): - """Passing [] for manufacturer_slugs should raise ValueError immediately.""" + @pytest.mark.parametrize("slugs", [[], ()], ids=["list", "tuple"]) + def test_empty_sequence_raises_value_error(self, slugs): + """An empty manufacturer_slugs sequence should raise ValueError immediately.""" client = self._make_client() - with pytest.raises(ValueError, match="manufacturer_slugs must be None or a non-empty list"): - client.get_module_types(manufacturer_slugs=[]) + with pytest.raises(ValueError, match="manufacturer_slugs must be None or a non-empty"): + client.get_module_types(manufacturer_slugs=slugs) + + def test_non_sequence_raises_value_error(self): + """Garbage input must fail as a caller error, not as a TypeError from a length check.""" + client = self._make_client() + with pytest.raises(ValueError, match="manufacturer_slugs must be None or a non-empty"): + client.get_module_types(manufacturer_slugs=5) class TestVendorScopedRackTypes: @@ -1923,7 +2007,8 @@ def _make_client(self): return NetBoxGraphQLClient("http://netbox.local", "tok") - def test_single_vendor_filter(self, mock_post): + @pytest.mark.parametrize("slugs", [["apc"], ("apc",)], ids=["list", "tuple"]) + def test_single_vendor_filter(self, mock_post, slugs): """Test filtering by a single manufacturer slug.""" data = { "rack_type_list": [ @@ -1953,7 +2038,7 @@ def test_single_vendor_filter(self, mock_post): mock_post.side_effect = _make_paged_responses(data, "rack_type_list") client = self._make_client() - result = client.get_rack_types(manufacturer_slugs=["apc"]) + result = client.get_rack_types(manufacturer_slugs=slugs) assert "apc" in result assert "AR1300" in result["apc"] @@ -1965,11 +2050,18 @@ def test_single_vendor_filter(self, mock_post): assert "$manufacturer_slug: String!" in query assert variables["manufacturer_slug"] == "apc" - def test_empty_list_raises_value_error(self): - """Passing [] for manufacturer_slugs should raise ValueError immediately.""" + @pytest.mark.parametrize("slugs", [[], ()], ids=["list", "tuple"]) + def test_empty_sequence_raises_value_error(self, slugs): + """An empty manufacturer_slugs sequence should raise ValueError immediately.""" client = self._make_client() - with pytest.raises(ValueError, match="manufacturer_slugs must be None or a non-empty list"): - client.get_rack_types(manufacturer_slugs=[]) + with pytest.raises(ValueError, match="manufacturer_slugs must be None or a non-empty"): + client.get_rack_types(manufacturer_slugs=slugs) + + def test_non_sequence_raises_value_error(self): + """Garbage input must fail as a caller error, not as a TypeError from a length check.""" + client = self._make_client() + with pytest.raises(ValueError, match="manufacturer_slugs must be None or a non-empty"): + client.get_rack_types(manufacturer_slugs=5) class TestVendorScopedComponentTemplates: @@ -2107,6 +2199,17 @@ def test_unfiltered_query(self, mock_post): query = call_args[1]["json"]["query"] assert "filters:" not in query + @pytest.mark.parametrize( + "slug", + ["", " ", ("cisco",), ["cisco"], 5], + ids=["empty", "blank", "tuple", "list", "int"], + ) + def test_non_string_or_blank_slug_raises_value_error(self, slug): + """The endpoint takes one slug: a sequence would build a filter that matches nothing.""" + client = self._make_client() + with pytest.raises(ValueError, match="manufacturer_slug must be None or a non-empty string"): + client.get_component_templates("interface_templates", manufacturer_slug=slug) + def test_vendor_scoped_two_queries(self, mock_post): """Test that vendor-scoped query makes two separate queries (device + module).""" device_data = { diff --git a/tests/test_nb_dt_import.py b/tests/test_nb_dt_import.py index 929231ca..31245d0b 100644 --- a/tests/test_nb_dt_import.py +++ b/tests/test_nb_dt_import.py @@ -1747,7 +1747,7 @@ def test_run_export_diff_wires_up_exporter(self, nb_dt_import): args = SimpleNamespace( export_diff_dir="extra", force_export_overwrite=True, - vendors=["nokia"], + vendors=("nokia",), show_remaining_time=True, ) @@ -1769,7 +1769,7 @@ def __exit__(self, exc_type, exc, tb): "handle": handle, "export_dir": "extra", "force_overwrite": True, - "vendor_slugs": ["nokia"], + "vendor_slugs": ("nokia",), } assert handle.set_console.call_args_list == [ ((progress.console,),), @@ -1777,12 +1777,12 @@ def __exit__(self, exc_type, exc, tb): ] MockExporter.return_value.run.assert_called_once_with(progress=progress) - def test_run_export_diff_sends_no_vendor_filter_when_vendors_is_empty(self, nb_dt_import): - """An empty --vendors must reach the exporter as None, not as an empty list.""" + def test_run_export_diff_passes_an_empty_vendor_selection_straight_through(self, nb_dt_import): + """The Exporter normalizes an empty selection to "all vendors"; the CLI does not pre-filter it.""" args = SimpleNamespace( export_diff_dir="extra", force_export_overwrite=False, - vendors=[], + vendors=(), show_remaining_time=False, ) @@ -1792,7 +1792,7 @@ def test_run_export_diff_sends_no_vendor_filter_when_vendors_is_empty(self, nb_d ): nb_dt_import._run_export_diff(args, MagicMock()) - assert MockExporter.call_args.kwargs["vendor_slugs"] is None + assert MockExporter.call_args.kwargs["vendor_slugs"] == () class TestMainAdditionalCoverage: @@ -2106,3 +2106,126 @@ def test_every_parser_flag_is_documented(self, nb_dt_import): def test_table_documents_no_removed_flags(self, nb_dt_import): stale = self._documented_flags() - self._parser_flags(nb_dt_import) assert not stale, f"README arguments table documents flags the parser no longer defines: {sorted(stale)}" + + +# --------------------------------------------------------------------------- +# Export-diff end-to-end: argv → resolve_run_config → main() → Exporter → GraphQL +# --------------------------------------------------------------------------- + +_LIST_FIELDS = ("device_type_list", "module_type_list", "rack_type_list") + + +def _run_export_diff_cli(nb_dt_import, monkeypatch, tmp_path, library_root, vendors=None): + """Run main() for one --export-diff invocation, stubbing nothing below the CLI.""" + monkeypatch.setenv("REPO_PATH", str(library_root)) + monkeypatch.delenv("VENDORS", raising=False) + argv = ["nb-dt-import.py", "--export-diff", "--export-diff-dir", str(tmp_path / "export")] + if vendors is not None: + argv += ["--vendors", vendors] + with patch.object(sys, "argv", argv): + nb_dt_import.main() + + +def _filters_by_list_field(payloads): + """Map each queried type-list field to the variables of its first GraphQL request.""" + seen = {} + for payload in payloads: + for field in _LIST_FIELDS: + if f"{field}(" in payload["query"]: + seen.setdefault(field, payload.get("variables", {})) + return seen + + +class TestExportDiffVendorFilterEndToEnd: + """The vendor filter must survive argv → RunConfig → Exporter → GraphQL untouched. + + Nothing below the CLI is stubbed: the real Exporter drives the real client, so a + config value the GraphQL layer rejects (config.vendors is a tuple) fails right here. + """ + + def test_single_vendor_reaches_the_graphql_request( + self, nb_dt_import, monkeypatch, tmp_path, capsys, mock_post, _real_library_root + ): + _run_export_diff_cli(nb_dt_import, monkeypatch, tmp_path, _real_library_root, "Cisco") + + payloads = [call.kwargs["json"] for call in mock_post.call_args_list] + filters = _filters_by_list_field(payloads) + assert set(filters) == set(_LIST_FIELDS) + for field, variables in filters.items(): + assert variables["manufacturer_slug"] == "cisco", field + assert isinstance(variables["manufacturer_slug"], str), field + assert "Nothing to export" in capsys.readouterr().out + + def test_multiple_vendors_are_sent_as_a_json_list( + self, nb_dt_import, monkeypatch, tmp_path, capsys, mock_post, _real_library_root + ): + """A tuple would not compare equal here, and NetBox would not accept it as a list variable.""" + _run_export_diff_cli(nb_dt_import, monkeypatch, tmp_path, _real_library_root, "Cisco,Juniper") + + payloads = [call.kwargs["json"] for call in mock_post.call_args_list] + filters = _filters_by_list_field(payloads) + assert set(filters) == set(_LIST_FIELDS) + for field, variables in filters.items(): + assert variables["manufacturer_slugs"] == ["cisco", "juniper"], field + assert isinstance(variables["manufacturer_slugs"], list), field + assert "Nothing to export" in capsys.readouterr().out + + def test_no_vendors_queries_every_manufacturer( + self, nb_dt_import, monkeypatch, tmp_path, capsys, mock_post, _real_library_root + ): + """Without --vendors, config.vendors is (), which the GraphQL layer rejects if it reaches it.""" + _run_export_diff_cli(nb_dt_import, monkeypatch, tmp_path, _real_library_root) + + payloads = [call.kwargs["json"] for call in mock_post.call_args_list] + filters = _filters_by_list_field(payloads) + assert set(filters) == set(_LIST_FIELDS) + for field, variables in filters.items(): + assert set(variables) == {"pagination"}, field + assert not any("filters:" in payload["query"] for payload in payloads) + assert "Nothing to export" in capsys.readouterr().out + + +@pytest.mark.real_http +class TestExportDiffVendorFilterOverRealHTTP: + """Same run against a local HTTP server, so the filter is asserted as it is serialized on the wire.""" + + @staticmethod + def _serve(): + """Serve empty GraphQL pages and record every decoded request body.""" + import json + import threading + from http.server import BaseHTTPRequestHandler, HTTPServer + + bodies = [] + + class Handler(BaseHTTPRequestHandler): + def do_POST(self): + length = int(self.headers["Content-Length"]) + bodies.append(json.loads(self.rfile.read(length))) + payload = b'{"data": {}}' + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(payload))) + self.end_headers() + self.wfile.write(payload) + + def log_message(self, *args): + """Silence the default stderr access log.""" + + server = HTTPServer(("127.0.0.1", 0), Handler) + threading.Thread(target=server.serve_forever, daemon=True).start() + return f"http://127.0.0.1:{server.server_port}", server, bodies + + def test_vendor_filter_is_serialized_as_a_json_list(self, nb_dt_import, monkeypatch, tmp_path, _real_library_root): + url, server, bodies = self._serve() + monkeypatch.setenv("NETBOX_URL", url) + try: + _run_export_diff_cli(nb_dt_import, monkeypatch, tmp_path, _real_library_root, "Cisco,Juniper") + finally: + server.shutdown() + server.server_close() + + filters = _filters_by_list_field(bodies) + assert set(filters) == set(_LIST_FIELDS) + for field, variables in filters.items(): + assert variables["manufacturer_slugs"] == ["cisco", "juniper"], field diff --git a/uv.lock b/uv.lock index de81e5dd..9c05b0f9 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,74 @@ version = 1 revision = 3 requires-python = ">=3.12" +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version < '3.15'", +] + +[[package]] +name = "ast-serialize" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/a9/11851c3e02a3fea2ddc9932d1fdc7d2edaeecc0d2e11bc5f2a7fde2b0934/ast_serialize-0.8.0.tar.gz", hash = "sha256:6c37c43e4004dfb42d321ddedc569dc17ff4259296f3af577c9ea46a809bc010", size = 845638, upload-time = "2026-08-07T11:29:02.152Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/16/6e520b57cd8c75914b38c670ad4593d13c22911e4306cc7165dab8b0789b/ast_serialize-0.8.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:3d822605fa7bb326ef868d25fafced7fc660fa46d9b90c02ea86d5e2f5d325f7", size = 863924, upload-time = "2026-08-07T11:27:34.579Z" }, + { url = "https://files.pythonhosted.org/packages/03/e1/48802de9b22a2bcad42ec80601a17e3f69172fe4f590e6311bcc2b323aeb/ast_serialize-0.8.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:2efa40b068197d5efb62655b43baadb842ed71c4958cccd3e8b86a35726f0119", size = 1177662, upload-time = "2026-08-07T11:27:36.196Z" }, + { url = "https://files.pythonhosted.org/packages/38/d4/323438db76bded3a1f3523a3167b8325916b2ddceb2107a330c6ec9fcf4d/ast_serialize-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:db1b957291bca08c7e72f43a12357b2948e20775d970e3fc3dac0aa3160ab725", size = 1167072, upload-time = "2026-08-07T11:27:37.646Z" }, + { url = "https://files.pythonhosted.org/packages/77/82/53c5400b54144b56de8ed7f957fd1ccd97e42482009292ab46121d15f8dd/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdc0d5b18ff8fb364e87923e47c0a91d0d69dbcaeaa274591f7fd26892cc3a3a", size = 1225497, upload-time = "2026-08-07T11:27:39.225Z" }, + { url = "https://files.pythonhosted.org/packages/44/5f/36c07327a8b91303fbf1382c7c3e8a2902072dbe1b9546138a5288e75ff0/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9da7330f3e235bf7da89b8d39205c6350fc0c08a85379743f2df9fff87d6d980", size = 1227101, upload-time = "2026-08-07T11:27:40.799Z" }, + { url = "https://files.pythonhosted.org/packages/9d/48/5adf5c67addc7ddb328122208c6d375a84cf154984f412b4087330a157bd/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3186969ee66a9863b00acc6523ace44c56974eecb348a7ea4b228d9f0b80e19", size = 1424001, upload-time = "2026-08-07T11:27:42.708Z" }, + { url = "https://files.pythonhosted.org/packages/38/a1/70074dd3869d2b0e934f91891d8d6b734361cd3b80f85ca7ece2e668ecdd/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40a57b73731be45da4fa41430c4d5dc94a24b3a4faba7b9e069978c0402064ea", size = 1245545, upload-time = "2026-08-07T11:27:44.4Z" }, + { url = "https://files.pythonhosted.org/packages/e3/be/53b9c0a8a6399950c2e3546bdfab96d2b299d5b114b47eb94fd3c49c4054/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5075b9da3ef807eda752502446dfecea3b381c4900b7e27a5d5f4f899eb39951", size = 1248961, upload-time = "2026-08-07T11:27:45.781Z" }, + { url = "https://files.pythonhosted.org/packages/eb/13/3651d3812548a2bda15e26e5dd51aadb48cf682d0865370255fcf0e367dd/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:293cc1c5bfa741f8e3fbe8175b9c07beee487c9a6fdbb25a5acad9f1df2d30a9", size = 1243877, upload-time = "2026-08-07T11:27:47.325Z" }, + { url = "https://files.pythonhosted.org/packages/21/a0/521f0bf000f675e9312a4aae2c8ba7a992405d072a85c485e08fd59433b9/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0910c3442a75216dde0f102d854ba2aaa71d2482e0ee213630b9bf29584fba3", size = 1293903, upload-time = "2026-08-07T11:27:49.264Z" }, + { url = "https://files.pythonhosted.org/packages/b1/7e/402fc902568aa2ee65865a3e151f000db0153da8ce6b1be4c9c349025f8d/ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:43dd6d596879bb1cb8a12cc9dae7bb10090a39a35883026c24f82488a195619a", size = 1401070, upload-time = "2026-08-07T11:27:50.947Z" }, + { url = "https://files.pythonhosted.org/packages/ff/7c/97d4b66c057f1706fc8be6dd532cc77c988794357c8f4ffdb6adabb39562/ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8c9d537f59e936392cfd3597789d1390304dd659efc3c486ce7f40fb6b8a9f53", size = 1502602, upload-time = "2026-08-07T11:27:52.364Z" }, + { url = "https://files.pythonhosted.org/packages/89/6f/72cc3b71562001bba46e898ccfbf1844f7939b3e28912736206102f2e5a8/ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f0190a33d7f97c65e9069f7a7f40499eea6b5cbe260c558378109caf20ce934b", size = 1495848, upload-time = "2026-08-07T11:27:53.803Z" }, + { url = "https://files.pythonhosted.org/packages/a0/53/d6f629d1e49308b2f363dae028baa213ec222c9106fa1f7f0d1f7b41499a/ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:77308ae6c5cf5264cc0f01a7c556ec77a9e68eb1f61b093534d698139fdc3b14", size = 1556556, upload-time = "2026-08-07T11:27:55.342Z" }, + { url = "https://files.pythonhosted.org/packages/ee/22/340f35dd8dfc6d412d53dc20699ca014b8d228db923e8ed4759c512b162c/ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8d53a23f27e1ed3a36b2d26fd2a1a6228c8e85a1ed62ff7cdb44bd610769f20a", size = 1417822, upload-time = "2026-08-07T11:27:56.712Z" }, + { url = "https://files.pythonhosted.org/packages/11/29/6dde5c13fbebc051d3a6df4ec0a6fd1d5359333cc1193f7f609f3410b4d8/ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ffa5e7cb08f96fed9121f77b224151e41caf88feab9d652bb46c78202b6fbeda", size = 1445153, upload-time = "2026-08-07T11:27:58.275Z" }, + { url = "https://files.pythonhosted.org/packages/62/c5/f473a8ed030f7a0ca24b9849cca184677a50c053867a7b808c2e1289bbd3/ast_serialize-0.8.0-cp314-cp314t-win32.whl", hash = "sha256:fa70ed4dea0bb18b30a1789c77baa701d0ef30c474f2ccabdea61e25623a8827", size = 1063711, upload-time = "2026-08-07T11:27:59.793Z" }, + { url = "https://files.pythonhosted.org/packages/23/63/39e171fcd38ca057c2e1979d5ee81ac7a3502784abe3d83df7454f7a0978/ast_serialize-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d8b3c8eee4c1baef9d4e84d2a59a805501617127be42615cb48970b15b0892b6", size = 1103740, upload-time = "2026-08-07T11:28:01.405Z" }, + { url = "https://files.pythonhosted.org/packages/21/1c/d00762b399e7726d68d0a088cc946e3a4c60f1c6176f557608f672f627f3/ast_serialize-0.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:ac4f0a83c55a9b782f79ad55a5247b7db123c1db405959791c2ef886e9710c9f", size = 1076021, upload-time = "2026-08-07T11:28:02.947Z" }, + { url = "https://files.pythonhosted.org/packages/4c/11/911210c3c78923273a9211a2b6cfc4c8aa723b30dab3e1c8d19afb983b40/ast_serialize-0.8.0-cp315-abi3.abi3t-macosx_10_12_x86_64.whl", hash = "sha256:86b8a1e6d90467345356098b040150e82fbc26d24a7a202224b13dc1f6264ca0", size = 1177715, upload-time = "2026-08-07T11:28:04.654Z" }, + { url = "https://files.pythonhosted.org/packages/77/89/6282881c8587606638db153cbe21e1e0c4d1f3970dee1aa0610a1c62a026/ast_serialize-0.8.0-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:39e92ff8e8cb45947fe9007174b2950e1fb098e6abd00266a13cd3bcf6675068", size = 1169347, upload-time = "2026-08-07T11:28:06.1Z" }, + { url = "https://files.pythonhosted.org/packages/97/78/a9f846a03a340ff3728c915f23338ca742742f3292700559cdb3ad999b1e/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c85d8d18db5b2dfcb3b7e38a4d600ca35504c0ed8a6f75cd1c811e4ffe248a15", size = 1225916, upload-time = "2026-08-07T11:28:07.654Z" }, + { url = "https://files.pythonhosted.org/packages/c0/15/aba6ef8a988a6eceb6f0359589aac509e29ae2dba67fd9bfd5af0c3f13e7/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9830ff7e764f74d9eefb01170c61a9f0fd2c027dac5fcb72e064decd57d56371", size = 1227135, upload-time = "2026-08-07T11:28:09.504Z" }, + { url = "https://files.pythonhosted.org/packages/94/29/3f63d696ea7c5b8abadcecc3505be51bd900daaccc522ed8322fa5b05a93/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6479d9722a4cd21b578f5478074c41e6169f04811996ec881655560f703a5bba", size = 1425040, upload-time = "2026-08-07T11:28:11.044Z" }, + { url = "https://files.pythonhosted.org/packages/e2/5d/0aac338604ff59df5774d4304307898982252f325ff7cafe31d52fedcb65/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a63bed264e818cd83eec11feed0f50aa162542b91132ef58afebc857182763a5", size = 1246278, upload-time = "2026-08-07T11:28:12.519Z" }, + { url = "https://files.pythonhosted.org/packages/23/ca/9f1ef795bb724719532bd86dbec11e5b66857d3fbe9b6772baec0191a6ed/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d187197d234aa45d6cfa2b096be5f666e8cc2e7eb3722d0ab8926293cf5720c", size = 1250029, upload-time = "2026-08-07T11:28:13.896Z" }, + { url = "https://files.pythonhosted.org/packages/dc/25/5e061372d2ed953b9ba3b9c4f73de3b8e9234cda3f6c088db4686801d0e1/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_31_riscv64.whl", hash = "sha256:2d39a56282cfcc0d8eeea37267c754be59c98d48505c23b1dae5c6011f3813dd", size = 1243575, upload-time = "2026-08-07T11:28:15.37Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c1/ae7da218053120635a4ca802366c69f707203641af95372eeb83f70dfd52/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f7cc5f10386994c0f4844f1e6d6a97127e9b478660eb6dec2b257644f0acab64", size = 1294396, upload-time = "2026-08-07T11:28:16.813Z" }, + { url = "https://files.pythonhosted.org/packages/2e/89/271d1f49c5269fcddcc789ea3f25be401f6723fc1138aeda539f4d05516d/ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:6102f2f985c2e542be85cd857678ec9356fefa792b93cadfadd31139f5696f27", size = 1401987, upload-time = "2026-08-07T11:28:18.333Z" }, + { url = "https://files.pythonhosted.org/packages/55/be/4e7d77fcf571ac7cb5cf7115a20c36642bd7d29473b45dfaaefeb9618f90/ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_armv7l.whl", hash = "sha256:3a8660fe66667b76a6e9dccd1d33e66b229fde3b308db991c041609226c005b6", size = 1502904, upload-time = "2026-08-07T11:28:20.039Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ae/ed1de2db7e019d4236fbc164ffa5ef9a6022a300a342bbf142d21b7c141e/ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_i686.whl", hash = "sha256:e7266307e5fba39836edb79def8608887af48820508bff3c5f2941e1e04d1534", size = 1496967, upload-time = "2026-08-07T11:28:21.734Z" }, + { url = "https://files.pythonhosted.org/packages/92/89/5fea507fae5c5f18b7dc7f95e5c00956574b8c717b8fd2049c504fab0b18/ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca7e6fd1ad845d1cc649dc2ecd499db2f8f46af5bf8da7b70dd858774cc038b", size = 1559041, upload-time = "2026-08-07T11:28:23.194Z" }, + { url = "https://files.pythonhosted.org/packages/42/71/478d69df21b64e064554a68134c94be304270316ca676a94e63c389a636a/ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_riscv64.whl", hash = "sha256:2880350b13d3eae69a0d70bc1fb6c9bfaca4dbd0e20ba8cd1aa483080b56ff06", size = 1417367, upload-time = "2026-08-07T11:28:24.601Z" }, + { url = "https://files.pythonhosted.org/packages/5e/2d/8962dc8d5b3a9dc27b36f9db199afa25264c741505469d9ec10ffbfd2ba7/ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:ab0f9a59f7d63d0d441b56b9a818b273705264352d5115cfee12e940e816d958", size = 1446178, upload-time = "2026-08-07T11:28:26.152Z" }, + { url = "https://files.pythonhosted.org/packages/4f/22/14d2ad4fd1d1bcd0dc687ca268e0630069f45162496260c0efb70ee0ea72/ast_serialize-0.8.0-cp315-abi3.abi3t-win32.whl", hash = "sha256:0485a25ef519c62e749ee3c1ad8070e591b380d67226349eb5a70b228dc1ac4a", size = 1063811, upload-time = "2026-08-07T11:28:27.864Z" }, + { url = "https://files.pythonhosted.org/packages/18/1d/84a327c0202a41aa5fdba3ade33904d6d8f3b9e6806fa83568d835395850/ast_serialize-0.8.0-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:bd84d60bca7079e741be4ac5dbe237751a59d7f6f9f0126b11880d63822cbe16", size = 1105518, upload-time = "2026-08-07T11:28:29.691Z" }, + { url = "https://files.pythonhosted.org/packages/8c/92/74556dec52fde85a2ad84ed159991b916241043788609c15d8b77e14570b/ast_serialize-0.8.0-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:057769b5921336eb2d9124f2a731b42ed05ffdac559b840dbdf6f3937cf153dc", size = 1076319, upload-time = "2026-08-07T11:28:31.282Z" }, + { url = "https://files.pythonhosted.org/packages/d1/5d/c650b1f2cc1e75193358da95a080261422e8cd10b66d7370b1688c9915c5/ast_serialize-0.8.0-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:a02cbed7d8bfdcdee88edaac12bd50d53d9953aaa2e1852ef078625be5f1c0b5", size = 852914, upload-time = "2026-08-07T11:28:32.929Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e3/6142e920fec6ef7bccabd8c24ed8ed99f8bdc6cb8b065e1df7c6a3b2d667/ast_serialize-0.8.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e1bd223df0f6c96b396975fa604cb33bce53d9b4a0185490be4c4a289f7c9c87", size = 1184007, upload-time = "2026-08-07T11:28:34.654Z" }, + { url = "https://files.pythonhosted.org/packages/a6/e9/6e8be8df02b35d85e2b8809f7f1cfa290bdf5882b55127a539d049482db0/ast_serialize-0.8.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ddd3b61f45c132da66c5476b281891e08c1fd87fbdabe8a6973e1622efc85f06", size = 1177588, upload-time = "2026-08-07T11:28:36.318Z" }, + { url = "https://files.pythonhosted.org/packages/8c/80/7e0fd2e2e2aba257820db4a8657c4c356844d36b914b20a4af294bcfb902/ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f9caa63fad8241257ae401b5ff0a64026c6adb36b8e86cbe8782d9ea505daf6", size = 1234575, upload-time = "2026-08-07T11:28:37.772Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/3bae0af06f9b1bae3001c44d64215f5b567877e7aae9ffd45db11c3a7647/ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3926fa117b5e65019853a2969966d11c7175af377a3425991f3fe73784412405", size = 1236015, upload-time = "2026-08-07T11:28:39.14Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c4/ce2d41a1bc22508e82618901f7e10f2a5e2f9556553fea90624daf9875e2/ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:485f1113af805e9e170b95ef993ca3fbd4f89c04bab25c58b4fc632d854801ab", size = 1432808, upload-time = "2026-08-07T11:28:40.664Z" }, + { url = "https://files.pythonhosted.org/packages/1a/90/f5058f209756dd70e958b7538aaa82d25d24944baf9ec8ae6f27b06fcacc/ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccebbed24f1281062d5852353c72c47502955926cfcb8345ffb3a44d87ff3d3", size = 1256251, upload-time = "2026-08-07T11:28:42.223Z" }, + { url = "https://files.pythonhosted.org/packages/bf/32/7f77ea87fa0836daab706ed5cb7f903bb25fa26a77439011aee626af11d8/ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:252f883290d1cdb728eb7fe1d9a7221b88af5a329aae0bc91ddee4dafb820331", size = 1258574, upload-time = "2026-08-07T11:28:43.751Z" }, + { url = "https://files.pythonhosted.org/packages/eb/5a/75b82ad2725b5e8e8c742732f9e76c6738a292d0709e1f60d10a973730b4/ast_serialize-0.8.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:96abc072ad29db8d02194afd47d68987322622787daceae82398d7b69f3ba2e6", size = 1254075, upload-time = "2026-08-07T11:28:45.28Z" }, + { url = "https://files.pythonhosted.org/packages/4e/54/8c20ed4eea805516a3fd23dd4a721ce28c64f50f0e4b359969f60a8c97a6/ast_serialize-0.8.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9118ad3e369727060b2696fc4078f250ecffca4248ba87f537f55cea9f9dce06", size = 1301018, upload-time = "2026-08-07T11:28:46.851Z" }, + { url = "https://files.pythonhosted.org/packages/cb/5b/9f14430f12fe830b656fb38f8e2e05ee13b02a88967660bef46af0ab22a8/ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f359df4bd921918af8bebd142a376c77511d7151cc8ba852760b587b5a4a54f3", size = 1409951, upload-time = "2026-08-07T11:28:48.312Z" }, + { url = "https://files.pythonhosted.org/packages/2d/3d/084882eca93c842bd4262591a071ec7f825340644035e51501208cc5a8d4/ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:e94f9121d13fa36cbf21314783c77d05ae3a0868decd18cf5233fdcc6de49ac8", size = 1509544, upload-time = "2026-08-07T11:28:49.847Z" }, + { url = "https://files.pythonhosted.org/packages/ce/73/ea84852096c2036c61cc0b2f97b90242207419f534dc671060ee1c8e05cb/ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:54f95b486018d262bcb387a9afd96f0da74508b442762b80c769454a6fbb3ee3", size = 1505671, upload-time = "2026-08-07T11:28:51.239Z" }, + { url = "https://files.pythonhosted.org/packages/cb/88/287b9a5300c1f2f651d259f670931b63110adc265b7613c885b44c5bc53d/ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c38b915511e32bc718c49dbce98ff9af36bac0ad6a604f58000cd5e3aecdba7", size = 1563685, upload-time = "2026-08-07T11:28:53.112Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f3/1bc3a79afcf0c2a8d2c37182d0d659d1545a9d7f7f6dc9cf3e63d6c17135/ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:9a2ef9cf12f2de4f1028c42c1dd7d775255e0fb3e5bb48896c97e35ef52366fe", size = 1427977, upload-time = "2026-08-07T11:28:54.418Z" }, + { url = "https://files.pythonhosted.org/packages/5c/cd/440c798957e14e31776bfeb024d8fafe0bb1d5b89c51c2f067e69938f7b0/ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6f18048fe9f6dd266bd577cdec48bdcecb74faaa01fe941324435483b013ed2a", size = 1454335, upload-time = "2026-08-07T11:28:55.968Z" }, + { url = "https://files.pythonhosted.org/packages/4f/4a/587eb36dcc240a54c8660f599464516b469ecad96f0dbdb6bccbedb50745/ast_serialize-0.8.0-cp39-abi3-win32.whl", hash = "sha256:31883542dd6c94d178f5db3d32fbd69c5eb88b3a7c018e7ac8cc0c45195ddbed", size = 1068858, upload-time = "2026-08-07T11:28:57.541Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a4/3e887bbd92164e183cb6e412c6a3e9198ddd446d7fe405958293ef5ef49c/ast_serialize-0.8.0-cp39-abi3-win_amd64.whl", hash = "sha256:861794565b06337005c1447ef23103a3d5a627d08bdc827870d00d0b28ef5f51", size = 1111839, upload-time = "2026-08-07T11:28:59Z" }, + { url = "https://files.pythonhosted.org/packages/25/6c/b400476d3ceba681ab929787edc9554f6d88fcc69435eb681b00fc0457a5/ast_serialize-0.8.0-cp39-abi3-win_arm64.whl", hash = "sha256:b2a5978662fd4db463dfb4b974d2b10ac6430b98f5333aabc7051909df3561d0", size = 1083655, upload-time = "2026-08-07T11:29:00.349Z" }, +] [[package]] name = "attrs" @@ -207,12 +275,14 @@ dependencies = [ [package.dev-dependencies] dev = [ { name = "interrogate" }, + { name = "mypy" }, { name = "pre-commit" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-mock" }, { name = "pytest-timeout" }, { name = "ruff" }, + { name = "types-pyyaml" }, { name = "zizmor" }, ] @@ -229,12 +299,14 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "interrogate", specifier = ">=1.7.0" }, + { name = "mypy", specifier = ">=2.3.1" }, { name = "pre-commit", specifier = ">=4.6.2" }, { name = "pytest", specifier = ">=9.1.1" }, { name = "pytest-cov", specifier = ">=6.0" }, { name = "pytest-mock", specifier = ">=3.15.1" }, { name = "pytest-timeout", specifier = ">=2.4.0" }, { name = "ruff", specifier = ">=0.16.2" }, + { name = "types-pyyaml", specifier = ">=6.0.12.20260815" }, { name = "zizmor", specifier = "==1.29.0" }, ] @@ -323,6 +395,107 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl", hash = "sha256:b13ff4dd8403369670e2efe684066de9fcb868ad9d7f2b4095d8112142dc9d12", size = 46982, upload-time = "2024-04-07T22:30:44.277Z" }, ] +[[package]] +name = "librt" +version = "0.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/36/9b/356320fbae2ac8467e21c5e73e1389c80468e4998c62cc7d3536cc51b614/librt-0.15.0.tar.gz", hash = "sha256:4e66cbe84437497d951b799d3e1551291b6fb3d643820a7014b3655d57a59162", size = 214338, upload-time = "2026-08-07T10:49:42.663Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/39/99c25030e782bdfb7a21be8c05254806a2e4bbb05c8d50c2a2130acbfa05/librt-0.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e87bc679f86a99aa3b26e3c78eeb821a247c9a28eae48eaafcc32c3bf4c3bb9e", size = 151021, upload-time = "2026-08-07T10:47:00.057Z" }, + { url = "https://files.pythonhosted.org/packages/14/43/f4b1bd1b2888798a1409808889a25ea1ba49eaabce7d681ed27734c2df9d/librt-0.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71599e011ac880e8e45d46047d714871894c7d4ab6f25626f8d4f89da21f368d", size = 155267, upload-time = "2026-08-07T10:47:01.311Z" }, + { url = "https://files.pythonhosted.org/packages/0c/db/3ad9c965c72f1e1d6beeec44ec10a54e17be8ae042fbb4baade16cbadced/librt-0.15.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c802434092b769b1d613ed2e13fac15fbfce1934a74bd10283b03c0fae231cd1", size = 503136, upload-time = "2026-08-07T10:47:02.45Z" }, + { url = "https://files.pythonhosted.org/packages/4b/07/5888a6d76acd62ebce66c61b74d94e9370b9c32929f111e487bb6546f8ed/librt-0.15.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5500eeae393a184d14e1f35645962c27129d20c81afa4069e6ef826ebc2b3aaa", size = 496670, upload-time = "2026-08-07T10:47:03.675Z" }, + { url = "https://files.pythonhosted.org/packages/29/39/ab57cc2f5b276156da02bb7f5a8921bada1cb1993ffec99acf811c602c23/librt-0.15.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6ecfc32dfb46fb7b565bcd6abf9412acf978775a998273d22888a6d7953730dd", size = 513688, upload-time = "2026-08-07T10:47:04.981Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b9/bdbb0b648b5c2befb031f4c6f3b1dd857415e8fb492a25a3c764a6681e6c/librt-0.15.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cc46cfd15022e35084355478c9ac809d90b1152222706ac9a7655ec21df6fa", size = 531904, upload-time = "2026-08-07T10:47:06.211Z" }, + { url = "https://files.pythonhosted.org/packages/93/26/473c2e4b6c104e9e58e27ce95fc8005c8bd4fc36cae4f254371125a92db8/librt-0.15.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d5f51401d102c885b9ca509e62c79b1dbff286e1b9b047fde6f763780789356d", size = 524427, upload-time = "2026-08-07T10:47:07.592Z" }, + { url = "https://files.pythonhosted.org/packages/26/60/03b3abb82b41714671b907bf6989b228e31e6a8af52dec82b5b0728dc250/librt-0.15.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cc30523e3f1a23fb7511cc659834a0d01a1042bb9de359bc1c131cc4ec6c9656", size = 543155, upload-time = "2026-08-07T10:47:08.866Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0e/9bb1f0a4affbd0a1888f4f79dc03ed2a299d9a2c26c59ab2a97dcbf11903/librt-0.15.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:59fe030d8ae4a57e3fb7756bf35a858de74e04066fc8555c53d0af979132af81", size = 546890, upload-time = "2026-08-07T10:47:10.327Z" }, + { url = "https://files.pythonhosted.org/packages/dc/84/6937a280d461f7de6e031ffb02edc2b7c3c90d49d630565ce8ff27cbc5f2/librt-0.15.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5a6526a2a956bbb1e4ae3568c82e650fc99119c66bb011ea60715744955a2b4d", size = 555163, upload-time = "2026-08-07T10:47:11.798Z" }, + { url = "https://files.pythonhosted.org/packages/bc/95/2a2853c1ee014bf102116e7f897a04beeaeb2461b45b79af98bdfb95f1ef/librt-0.15.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:85ea21ec6730194d67156b0e0b5430ccb1d61f8b8b907e39b37f9812b74a13f0", size = 535812, upload-time = "2026-08-07T10:47:13.279Z" }, + { url = "https://files.pythonhosted.org/packages/c9/4c/cf9601c1b4c5f09280acd5d83abdb2e68527a2be8257136eb42304218622/librt-0.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1e47b8ba865d7ede071a91a7163073bbaeb72541f1ef8a07d512c45c7b5007f2", size = 573688, upload-time = "2026-08-07T10:47:14.727Z" }, + { url = "https://files.pythonhosted.org/packages/47/6d/9ac7cbec46189a7625af4b5acbd25f10d827f4141b2002181848c8418923/librt-0.15.0-cp312-cp312-win32.whl", hash = "sha256:a5207ec414d1c4a2a7231b2086970dc036f94293cdf338190984958a013a42f1", size = 106138, upload-time = "2026-08-07T10:47:15.973Z" }, + { url = "https://files.pythonhosted.org/packages/38/d0/2ae99c83be86ce23f925ac1aeeedc777e97f427c4a8d190c70d0a16e9a87/librt-0.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:73b30cfa976659b3917c8f6153bdb0591c6a9ec6583599fd24a689b690622022", size = 126974, upload-time = "2026-08-07T10:47:17.049Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ef/dd24f9635c730b86b87587967dda7516b1845e8b17684603d31607fed598/librt-0.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:a54cf9e0ef47b96af580849db5471142200568ce1e02cbf416addab551369570", size = 112292, upload-time = "2026-08-07T10:47:18.222Z" }, + { url = "https://files.pythonhosted.org/packages/e7/42/467b53a601b406ccd7b97c1fd54b59cb34f9185ad5ce7e9d5c3c4e8961c8/librt-0.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:db13ca398005abcbe538deda87b686d9bd08b7001cf40c4c06b444960ae10a26", size = 151029, upload-time = "2026-08-07T10:47:19.312Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e6/36c2299b7a94b84fdd01220d8a777a71be5be0925bb0dbdf71c0a06a34d9/librt-0.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa1f1995789dca3698bc550aaceb09a51bd5df0a057ff84ff15296cd1975b801", size = 155194, upload-time = "2026-08-07T10:47:20.398Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b6/ed5071f9325845e670bd36012757419767fbf56af77ed483077b9e4db541/librt-0.15.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55456ea87d8df21808446d03817be2f65e20391c1c615d9187440dff28cd08dc", size = 502568, upload-time = "2026-08-07T10:47:21.652Z" }, + { url = "https://files.pythonhosted.org/packages/7f/81/6450c67c3615d87704bcbc21323fafc69c799b06a044c447529f725d4b01/librt-0.15.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5a86a5a08c2235316bdb359d5dbb6ce0abfca7fac06363103e2c5af571d92f95", size = 496153, upload-time = "2026-08-07T10:47:22.925Z" }, + { url = "https://files.pythonhosted.org/packages/e1/d6/5f52b722bc75076954b3bfd49be15ea362df4d580c6fb315d0f617100d30/librt-0.15.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e56b6a368529bed262da40ce13f8fef590db0479819cca84f16a1f01ac356d0b", size = 513336, upload-time = "2026-08-07T10:47:24.213Z" }, + { url = "https://files.pythonhosted.org/packages/8d/e2/c08fd1d36ce63ea5a12b85c5d37f4550b5f86a692167e41e5a74222607ae/librt-0.15.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:234d8d394721fa0d786af15ebf1f3fb7f3ed82fd1cd0cde45c2f247b5d4281d2", size = 531661, upload-time = "2026-08-07T10:47:25.507Z" }, + { url = "https://files.pythonhosted.org/packages/3f/d8/d9482fcbeb177b9eb87bb3899eeb3b42be690313c652f9e146b1d0681fb2/librt-0.15.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d8363d7accb0286ac3a0e633f396e93800dafb8150494505daf9515bbda591f3", size = 524487, upload-time = "2026-08-07T10:47:26.79Z" }, + { url = "https://files.pythonhosted.org/packages/10/cc/075171517b41f861753034fbb151b42cfc83bcc853849f24f5e66fd60ccf/librt-0.15.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0f0ee3644d951f31055ad07d77d92520e84505dd7a432cc4cd501dd70ee06785", size = 543201, upload-time = "2026-08-07T10:47:27.999Z" }, + { url = "https://files.pythonhosted.org/packages/b0/03/42c2330f37eeb475b6affeedd06518f60035f323af3a839335e3fc9fef2d/librt-0.15.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2cfd1a81a648806e6a7717be4cc4d1bb392fa229752bf8444ba365e381e984d6", size = 546467, upload-time = "2026-08-07T10:47:29.396Z" }, + { url = "https://files.pythonhosted.org/packages/57/1e/1ad4c5638f7e64d8560328bd25c54b409a661bdb6ff254b38ff90744288d/librt-0.15.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a6cd22c9da0d866558e46a041f1cc0c2bbb26b61b137b2347fa834c332e1d101", size = 555139, upload-time = "2026-08-07T10:47:30.815Z" }, + { url = "https://files.pythonhosted.org/packages/49/41/39fa7d15db1204cd1cbe6514680fbdc243adf754a0885061308f43afc013/librt-0.15.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6d5225ef8801e4ea5e482fa9b5dfb891dd9ef6f6d870f1f25d449ca2c70ac218", size = 536050, upload-time = "2026-08-07T10:47:32.222Z" }, + { url = "https://files.pythonhosted.org/packages/1e/88/c6dcf0dd8e26dc0c9a499a2abab8646c86dcaf9ecea9524cb46d3686331a/librt-0.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d28a05796b99f749bf8794f17ba9ba1612d0076b802e9cfc62c554634e9ce3b", size = 573700, upload-time = "2026-08-07T10:47:33.527Z" }, + { url = "https://files.pythonhosted.org/packages/1b/9b/ab54c71a7918a7c34fa5327fb61390a77446a07a146fbfb1165250a61035/librt-0.15.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:2067ff438048cead9d223ca5675bae2a25e520a7c3e6c1498bf9c6892d22caab", size = 82194, upload-time = "2026-08-07T10:47:34.835Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b2/4f9a243bb892395f3becb80789ade13771701091f9f07ab8230247953ba8/librt-0.15.0-cp313-cp313-win32.whl", hash = "sha256:1cd3b721f24c206398b9e26da3c3a9c011e6e89d06f318ba8ebefc30f1003890", size = 106231, upload-time = "2026-08-07T10:47:36.251Z" }, + { url = "https://files.pythonhosted.org/packages/bf/af/64aff4885a40b93132382f2c314647d722574605416504379184ef3045ea/librt-0.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:f395a4a9a03ac062dbe9a9f82e0c720502e590a38feee6a757bc82e9c63afbd8", size = 126996, upload-time = "2026-08-07T10:47:37.453Z" }, + { url = "https://files.pythonhosted.org/packages/27/83/335bccf6c7cb9028cb0b54aead27d9ece3f01f83bc6baa2abace5da655c1/librt-0.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:0a15cb554761247d84a3ec0cbdf4078d70725384f0e4662c0fa3b26266eb60ad", size = 112188, upload-time = "2026-08-07T10:47:38.729Z" }, + { url = "https://files.pythonhosted.org/packages/a8/93/949053fb462eecc4a9a5ee770a81f4b40be7b79538b245545d4aebc6b58b/librt-0.15.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f5de7feedc56337a088eb15cd9fafa9938367362221d8cc62c642b7f94821993", size = 149833, upload-time = "2026-08-07T10:47:39.86Z" }, + { url = "https://files.pythonhosted.org/packages/61/ca/8281aa6cd560a3420e4497729f6b704b53be3eeaaef82d5aeadddaf7441f/librt-0.15.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6c0eb900c0e91f4aebe680845242e614f1864edfd44106380d0752ac29522bf8", size = 154088, upload-time = "2026-08-07T10:47:41.065Z" }, + { url = "https://files.pythonhosted.org/packages/dd/02/1a1662dceaba6a086360891448d5ce9a7d3555976cae59a31a39d744b9c7/librt-0.15.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e8c9a650a188e38bac005048cbe6342e81407782944d01934540ab75e417df21", size = 494215, upload-time = "2026-08-07T10:47:42.388Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/99211619dc656370a3740c33d2b0b6d5a3fb1e73689314f6ed477a397dc4/librt-0.15.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:92bfed8deec93df30286b9fe9e3b1dd17329cc076a192b4ee5ec223841d54953", size = 491173, upload-time = "2026-08-07T10:47:43.683Z" }, + { url = "https://files.pythonhosted.org/packages/d4/aa/5448d0b05f4579b635d3899176817ebf561af0e57bacd425b5b1887264c1/librt-0.15.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ec4b19788f835711a2072f9dbe6b03b3bf32ed1f0fb30cf399bdd59d9f0c33fa", size = 505512, upload-time = "2026-08-07T10:47:45.314Z" }, + { url = "https://files.pythonhosted.org/packages/95/82/01940e40b83c43a546c4a3c896cf34ca272a9690899d55914e4827b3dcce/librt-0.15.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4c7bacb70930f3d0a56f4ecf1be474a1f0d941b01dd73b756f3c256d42cb879", size = 523073, upload-time = "2026-08-07T10:47:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/88/fa/759c0030f3ee371439eb26de34fc745807caf0abb878af7af4b8b7c3dd3d/librt-0.15.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3e79f05e4a08b4d880342673312bbc895b56df7765605796f15902eb5367d3ae", size = 515080, upload-time = "2026-08-07T10:47:48.319Z" }, + { url = "https://files.pythonhosted.org/packages/0b/27/894e072228fcb159703c655da69f8cd10dbed489c36e3df7dd032a2483be/librt-0.15.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a417149c0cba4d50b61e992e5a15e69eaf96746609b461cc4ed168aeef6b79dd", size = 534164, upload-time = "2026-08-07T10:47:49.875Z" }, + { url = "https://files.pythonhosted.org/packages/98/a3/0078e91c1f36f8815db17827de15650b9a3fe56c55fbf998c854b34e40d3/librt-0.15.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:da7a94d6a3411f579d72aa3e3bc5fbca7ed4549f3dbd7e5de3aa567333374285", size = 540616, upload-time = "2026-08-07T10:47:51.408Z" }, + { url = "https://files.pythonhosted.org/packages/86/33/81a29b796dd52a45e9ef7974c7732926e8f10f15b8d2be505665979f896d/librt-0.15.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:856f743ae607f2c1380eccb566c0038a9fb3eabf0fc2be2704d76d9f73557239", size = 545890, upload-time = "2026-08-07T10:47:52.818Z" }, + { url = "https://files.pythonhosted.org/packages/05/82/8be1baa1350e5d30cfd70ae79d0a6f4dc5862ef47f7bb2808aabc9bb86e5/librt-0.15.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:779a6e7c894737e5983e7790a9c78c4000c30e23c9aada08081bdbea53b0fa60", size = 523287, upload-time = "2026-08-07T10:47:54.165Z" }, + { url = "https://files.pythonhosted.org/packages/c6/4f/d1be6a01a35c20ef734e0e44113f87d4af756a9354a89dcfbe3b4f8af5e1/librt-0.15.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:96bb17dbe8bab3c0954fbebfc69ed395599de75b6bbc35e3270a878e15d4dd65", size = 565868, upload-time = "2026-08-07T10:47:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/67/88/649cfa33f5825927b160610f670bdab012a64d627eddb94fa795ea4292fd/librt-0.15.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:7220697efaa6e5348fc3d18ee7f8563d4bfecd9872b37ffb915bfc1d08840622", size = 81619, upload-time = "2026-08-07T10:47:56.886Z" }, + { url = "https://files.pythonhosted.org/packages/22/31/8e88a8d5e48fc8d1a817787fb6811dfff6499acd6c8683dd83934aa6ede0/librt-0.15.0-cp314-cp314-win32.whl", hash = "sha256:f54598964d357b1c5ab77cf5d92f21e598fe0e23cdbe9618480807f81b4eba15", size = 100138, upload-time = "2026-08-07T10:47:58.093Z" }, + { url = "https://files.pythonhosted.org/packages/80/92/20fd6c4b6a1b1a564b076d55cd3d427d8428217d7638dc25a654cc4791d4/librt-0.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:3ff5893a2c23d886aa9ce786de5ac6ddc74aeeaf90743682b74d920e117d2e28", size = 121258, upload-time = "2026-08-07T10:47:59.564Z" }, + { url = "https://files.pythonhosted.org/packages/fc/28/6af430b44d9ebb897b865a3c363b6dcace51357be2347cc0f8f869656a86/librt-0.15.0-cp314-cp314-win_arm64.whl", hash = "sha256:3722a099730704c9a3d70c879fc0f51daec25fe5f1555672d97bc595abeafb95", size = 106467, upload-time = "2026-08-07T10:48:01.097Z" }, + { url = "https://files.pythonhosted.org/packages/7e/aa/b42bb798942ced219f6d63b27e07f91237887a8d0bd0921666db79a13790/librt-0.15.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:38c0c7d4b6fc06c3324b3f9162c8391bfc4fd9dde53afe1033ce7edb48d5a714", size = 159523, upload-time = "2026-08-07T10:48:02.442Z" }, + { url = "https://files.pythonhosted.org/packages/75/03/1b53cd4ef904e73b1d828a5f90143bf94a2967d7cfff0b9ccf93e12aa9b4/librt-0.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8b2fdd7ead3c995c37940a790690660d0ca006c302db26cc51933f6766866fc3", size = 161638, upload-time = "2026-08-07T10:48:03.725Z" }, + { url = "https://files.pythonhosted.org/packages/ac/c4/9f9c9fba097d49e9e694c2b4dc331df31884645ecbc58a93b4b5fc69d2c5/librt-0.15.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2fde98cf1fc4bac144ce23c2c4c017b924ba714509ea9334977b0b27050c837d", size = 701795, upload-time = "2026-08-07T10:48:05.135Z" }, + { url = "https://files.pythonhosted.org/packages/4c/05/0966840bda0380c8ae167b9043c6230202941cc90ea29c48e096964c765e/librt-0.15.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:e3b461183c5fa7681b48560f91515f53a953122fb30c71e07abc67d7ddf58c38", size = 682147, upload-time = "2026-08-07T10:48:06.555Z" }, + { url = "https://files.pythonhosted.org/packages/18/af/1c47ca573c30ea47d195aec26133af522fea1104afaace028d7b32247ea8/librt-0.15.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4bbcc257e3babea20a91715c361b24554ec4e8f51aa578568afc230799fe1a19", size = 696397, upload-time = "2026-08-07T10:48:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/2e/0f/1aed6223d4f9f9d1171a8596ff100ea4c3f7699fea7a4ba657c3e60daa6c/librt-0.15.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b845b8d48088fad0cadc84be4b8fda63203be7e9237b71015b3925443c1f35ab", size = 722542, upload-time = "2026-08-07T10:48:09.569Z" }, + { url = "https://files.pythonhosted.org/packages/c6/22/9e3a929aea456c97d69e6ef3884efea56d4807f97399471cc946baebd8af/librt-0.15.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b30e600e8f337b9bd7f39b86d9fdfedc73cc46e3d0f745931a23a234220bb7e2", size = 729709, upload-time = "2026-08-07T10:48:11.129Z" }, + { url = "https://files.pythonhosted.org/packages/e9/1b/c327ef6018e3a9ca0b8e7c5eddeeb331ba8f9b76c24e126d37d0f6d62faf/librt-0.15.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:64b0c8c35aa4c4ed79896359f3e0b285cbe4e610042106500da4811c322cc108", size = 752891, upload-time = "2026-08-07T10:48:12.558Z" }, + { url = "https://files.pythonhosted.org/packages/d7/d1/d5f1ea02c56930087009e39db9b70660a663e76c730b27b925d786718457/librt-0.15.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0da0d94cb802f32a0524653e7201f2cef72d5f700a5407678f5290483d4fcd08", size = 745301, upload-time = "2026-08-07T10:48:14.55Z" }, + { url = "https://files.pythonhosted.org/packages/d9/3c/5f7c585d15ebb2250c73e7c0ee4e9e47be72c65d520c07ddbcdc62037674/librt-0.15.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4a6369168d371207339b1e50d4532b06a7121586141f82599505a3f315751d47", size = 747921, upload-time = "2026-08-07T10:48:16.453Z" }, + { url = "https://files.pythonhosted.org/packages/7f/52/1443a446486eba966bcbca1696b472e4f210320ec42f490a47f48fbf0fdc/librt-0.15.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c434e072557ade9cbc642d052c89d031efe47d5c9614523619d0d74a02378e81", size = 727561, upload-time = "2026-08-07T10:48:18.089Z" }, + { url = "https://files.pythonhosted.org/packages/79/91/2270a9380f11725cf83ce1925a5e32dd1dde2be9bba597f25c10a38644e7/librt-0.15.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7eec6a42018bc1d45763b1c162d3d2bf7c3b9a1b0ed30d3e91dcba390efefcc", size = 774417, upload-time = "2026-08-07T10:48:19.611Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/f4b1548d4f5b99186737fe27aec238e9823e8d5d23bf4df007c030689dc5/librt-0.15.0-cp314-cp314t-win32.whl", hash = "sha256:6912fa5e635d74529ac7cdb1bdf6ca3af4453da8d1edbe0110ee1cb4ad407ebf", size = 104381, upload-time = "2026-08-07T10:48:21.048Z" }, + { url = "https://files.pythonhosted.org/packages/80/b6/134afad262def1de04c0843c376d02135f1168af43f22e09a52bd8394727/librt-0.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8e11699ed745931c395acd3621b07062e0f840efa6935aad87a64ed0995f0915", size = 127034, upload-time = "2026-08-07T10:48:22.561Z" }, + { url = "https://files.pythonhosted.org/packages/99/5f/1b6846b20572bd699c9e9ec321a5f781845bee477df2aa2a43b28bc40119/librt-0.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:5d2a91724463bfed4f573cd7a9fdc856d2e230d0c0e5a61416a93481dccd8605", size = 110827, upload-time = "2026-08-07T10:48:23.804Z" }, + { url = "https://files.pythonhosted.org/packages/c6/44/4de9f4ddadb009a55c7758eb5736d62534a7daaf27bd71bc50e64b606b06/librt-0.15.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:8443e38dcfcfdbcf5add5118c623efd788d65ac2e25756d6251a54a06a4d0aca", size = 149843, upload-time = "2026-08-07T10:48:25.148Z" }, + { url = "https://files.pythonhosted.org/packages/1f/eb/5d9ab71e30119c44094e0275f38b47dd327aea0f843a080396677029d508/librt-0.15.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:6d15a29033c57490cfe2069097c6fc4049e4e65ffbb749be7dc453b7c4c68965", size = 154510, upload-time = "2026-08-07T10:48:26.485Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9c/8505d1b8f5e8c19587bd03f7429993b3e9ce5c06819d856bfb11d919374c/librt-0.15.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d2c05c729b589e734c09578bf5964be48a911765484840d017bbc84f49d4c4ad", size = 497543, upload-time = "2026-08-07T10:48:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/1d/9a/3a8390775cb095765aded027ac9c63e7c8ea74e731498607544c6505de0e/librt-0.15.0-cp315-cp315-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:fa60887537e1d0cd2d9982269d33a709bf54b195cd2b9364fc0a758022af5bd9", size = 480452, upload-time = "2026-08-07T10:48:29.531Z" }, + { url = "https://files.pythonhosted.org/packages/e7/40/258a4a7117ee915d66de5cd9b8ade65a440993161107ce3a686f1859955c/librt-0.15.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d8bc24219b24c0af375718942ab75e3544b2763085f40f965be4326734ae8328", size = 507768, upload-time = "2026-08-07T10:48:31.007Z" }, + { url = "https://files.pythonhosted.org/packages/6b/c6/2f4dd296c97a0b85b98894519b279408ec9dd602d4f692b1ea0e25dee670/librt-0.15.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86a21a7bd3fe3a419512ef424cc1c020f6771d0b29cfddff36d1635a855e63f0", size = 525122, upload-time = "2026-08-07T10:48:32.7Z" }, + { url = "https://files.pythonhosted.org/packages/49/dd/29eab42be13b2bf0ea8cb227135a45d44693e30a7e8b92871981ff56b82b/librt-0.15.0-cp315-cp315-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dbab647e88d90b3167b91efe7091e248653688ed4337e4f90907a722c7361bb9", size = 520371, upload-time = "2026-08-07T10:48:34.294Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/4bad71adeca8fe208b775c2a35417fa5a2584c8f4791daaf89a89450fea1/librt-0.15.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:d8edcf6f550e918dca779c069b9e156385c60b406f99fc7641f32c52f7193659", size = 537258, upload-time = "2026-08-07T10:48:35.88Z" }, + { url = "https://files.pythonhosted.org/packages/4c/63/59dba6143fdcc7240c54458b629f3250000a61b8945890fc9efd451b19c5/librt-0.15.0-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:8b62076030baa2d8b1501a46bf0e19c27a489aa90671c55665bff7887f7660b0", size = 527432, upload-time = "2026-08-07T10:48:37.466Z" }, + { url = "https://files.pythonhosted.org/packages/ec/21/21a24c6a2327d8362580efebe77286bf47b0f4062ec5ea41766e609d3c7d/librt-0.15.0-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:d00d20d1818e82a07a0ee0aa89a98b17ed7916b92441090b683719cb20a59b6d", size = 548108, upload-time = "2026-08-07T10:48:39.384Z" }, + { url = "https://files.pythonhosted.org/packages/5a/6d/fc68c89a7971418b41f9a873623ff935cb864097544c6a2f8ce491c8ef5d/librt-0.15.0-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:4e6ee93fc3cf848dcbf0cce2eca73d8e7dcd0cc2b6df3a529d57750b30a4c55c", size = 529681, upload-time = "2026-08-07T10:48:41.392Z" }, + { url = "https://files.pythonhosted.org/packages/65/7e/c2d98766124400d722063a630b0fde38a9fc768705d37eecca15c47dc192/librt-0.15.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:32896a0af72508ea979e0acb4e4c04cbeeae04938167950d535c83c45597167d", size = 567736, upload-time = "2026-08-07T10:48:43.124Z" }, + { url = "https://files.pythonhosted.org/packages/55/6c/f8c34a95e3a515c6e1c192b89511e7253c89a7760c6b500d57ffdb8d2dc8/librt-0.15.0-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:ec3ba415afaf951f6951b1dd16d3c8e4f540065fc382d7e70b823a79567ca374", size = 81673, upload-time = "2026-08-07T10:48:44.645Z" }, + { url = "https://files.pythonhosted.org/packages/c9/9e/e23fa8e78679ec45728188650b39e8ff476c83b691c96f749217df3b1b7c/librt-0.15.0-cp315-cp315-win32.whl", hash = "sha256:d2813ba2503764f0450680c533d13df7cff9b49df1411062eded5f67db4195b9", size = 100081, upload-time = "2026-08-07T10:48:46.171Z" }, + { url = "https://files.pythonhosted.org/packages/e1/dc/3eb4c5e297343f0620a55532cd7c8d764d3001fa2159212dadf480464827/librt-0.15.0-cp315-cp315-win_amd64.whl", hash = "sha256:b87d67e33afaf265262f2a66db578284b88ee2e6fcd224579cb5c15518677ad8", size = 121228, upload-time = "2026-08-07T10:48:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/97/70/43abce19f04e49762f8ec834c8fafee13cc40fd6b94a72a24e534febfcd0/librt-0.15.0-cp315-cp315-win_arm64.whl", hash = "sha256:713bd7df21170b982e729e46870f31d6b437bd1a9b4648cffb529bd3c2ec5c4b", size = 106487, upload-time = "2026-08-07T10:48:49.095Z" }, + { url = "https://files.pythonhosted.org/packages/de/15/83f2deddb9368b8951ec8c9477269b5b9b8bd9bbf15e57402d0f38817dca/librt-0.15.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:3de789c82752730f94782a5ee518baf9c05edf85733aeaf73bb6e518755cdf54", size = 159448, upload-time = "2026-08-07T10:48:50.649Z" }, + { url = "https://files.pythonhosted.org/packages/06/bf/043097353f9b3c73b583d07f6b8e552795463f4bfc8caf85e42eee50c26a/librt-0.15.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:e0b5deec9a8664eb722c797241970fd4aa1894d25fda36a1ddac0f7407606bd6", size = 161686, upload-time = "2026-08-07T10:48:52.174Z" }, + { url = "https://files.pythonhosted.org/packages/f4/2a/8ae77f9719d42ce71cd708560a3557b38ac3c17a0383e57f87084de45bbe/librt-0.15.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5563302a8359bc2295bb7084d1a8ed1519df96afb30eb2aa4e0bff7b54228988", size = 710668, upload-time = "2026-08-07T10:48:53.782Z" }, + { url = "https://files.pythonhosted.org/packages/61/34/c0436ea134deb9a0d6da80a396a2739a81cb31e0418f7227239e23140898/librt-0.15.0-cp315-cp315t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:22d6263b9d39d7bbb286fa791945646e3218f1be2d693e36fb630f1d0e59cd13", size = 679396, upload-time = "2026-08-07T10:48:55.645Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/001e0d99aa9250d5cd5715a9081291a20656083459f9019cda15255329e1/librt-0.15.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:39ffd14646190c454f0d86e0d256b33f00a87a26ab410e619773b841d0e41416", size = 704313, upload-time = "2026-08-07T10:48:57.46Z" }, + { url = "https://files.pythonhosted.org/packages/2d/53/b34fa9d0ff00f136f4d58ebb4c411ff634baed1eb412bb602a2bc8dcafcb/librt-0.15.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c47318cd3a61401452de11282242937e3e057c4fd3dbaf601e269d0928a06c0a", size = 729847, upload-time = "2026-08-07T10:48:59.231Z" }, + { url = "https://files.pythonhosted.org/packages/86/ac/fa4d7a424665040e95baf480a6d523446057684b6758624c85338e8a23b2/librt-0.15.0-cp315-cp315t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a56a1d4f859a82ca5b99fc4b82c9b027b15e3c455c5cd99e7d0719f27bb20b6c", size = 742736, upload-time = "2026-08-07T10:49:01.151Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f1/e17a9bb5de6fb8c3186ed1a7d68d21618b027ac2d3633e03d3b6109c67ae/librt-0.15.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:077471b3182db4e17c36ae91555f36a4d2c00080b267f749bcad34a478a9a302", size = 763454, upload-time = "2026-08-07T10:49:03.039Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ec/ecd02cd30935b931b9cdbfed6ab5a099c51b280b4e7baa274da80978ed27/librt-0.15.0-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:411ca4d1b905b860ceba7570dd6717a71dedaddcc4b0f77ece710aa41ee11f8d", size = 743296, upload-time = "2026-08-07T10:49:04.941Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b5/b3c2b8353ce820a4854f78d19321344242f89fa71c975b71132ba9bf242a/librt-0.15.0-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:1256589e0b0adb31751d685a68bce29d73407ddf4ef05d4188f49d5dcf9566d9", size = 756217, upload-time = "2026-08-07T10:49:06.825Z" }, + { url = "https://files.pythonhosted.org/packages/3c/52/6cc22542ba59146b05cca2a656f9ff8bb67e38e63d12c3b0cc183d837bf1/librt-0.15.0-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:f42b74a53e5f26a0ba0007411a7455b66c67ce4022a39cc1f56fc4efd65bcbab", size = 741934, upload-time = "2026-08-07T10:49:08.839Z" }, + { url = "https://files.pythonhosted.org/packages/40/32/a04b72b1aa86e3be23b2ecff8c1aad2dcc955bd3956d6d26e7e34267e57a/librt-0.15.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:291bf73caf78b9e88d6fae9bfd693207ff7d832e2fdbe2cf8e746bc13f5f892b", size = 783763, upload-time = "2026-08-07T10:49:10.661Z" }, + { url = "https://files.pythonhosted.org/packages/6c/f0/89eb11dffbe9279ff37144dec786927314502ae0b114f1449dc78c458aab/librt-0.15.0-cp315-cp315t-win32.whl", hash = "sha256:c16d15ee371643ab48dc8248a3e680ebbeca573a13af2c3dd0c985b142d77162", size = 104313, upload-time = "2026-08-07T10:49:12.305Z" }, + { url = "https://files.pythonhosted.org/packages/6d/4a/1f1978c200f563beda63c36adff2d65bbecb81e365e8e69e572f5f70fbc6/librt-0.15.0-cp315-cp315t-win_amd64.whl", hash = "sha256:dbd605739f228912dc49027cb764456b9757750bdc2b6b7773164db7096c6fd1", size = 126889, upload-time = "2026-08-07T10:49:13.881Z" }, + { url = "https://files.pythonhosted.org/packages/38/a6/800800bfed7b1fb10fc3f3d557785c3854e80d3f7a9800d784b176a1fc2d/librt-0.15.0-cp315-cp315t-win_arm64.whl", hash = "sha256:84d244b00604d17df3fc7736c327892d6bba66181254aa4087be807b6c342bdc", size = 110700, upload-time = "2026-08-07T10:49:15.499Z" }, +] + [[package]] name = "markdown-it-py" version = "4.0.0" @@ -344,6 +517,69 @@ 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" }, ] +[[package]] +name = "mypy" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ast-serialize" }, + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/6a/878cc1097d4035f82bd516658d0c528d2a9955bc7b363afcbd0b07fea11b/mypy-2.3.1.tar.gz", hash = "sha256:47c1b1207258513a9d93495f69c8be9de73916186f0e52703e8c461b7a623419", size = 3992554, upload-time = "2026-08-15T03:03:38.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/da/d6effc4f808a842d91edc22535dc9e799d2ff6e91449168b7f47a0771f54/mypy-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a32bbbb940af990d3be0b8af321c7b6815bb1b3b48142fe7459b9cc5f58959ff", size = 14047547, upload-time = "2026-08-15T03:02:57.707Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e6/478229701dab76f26485fc8ff5d6f241f393da22447400bbc56f6946aebe/mypy-2.3.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff715e45b2231a8e85de1d163d1b42791e4d7aab8f5145f85fee1b710b735aff", size = 14216515, upload-time = "2026-08-15T03:01:26.496Z" }, + { url = "https://files.pythonhosted.org/packages/8d/fe/7c42327a3b21e84681f691982cbfe43f334a3685f3b683b72c376476c4fa/mypy-2.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:858fc57d3d91fa728e33e7ad71def60fc6272694607b306cd3292db53ae39080", size = 15307789, upload-time = "2026-08-15T03:03:31.62Z" }, + { url = "https://files.pythonhosted.org/packages/59/f4/7e597edbe01b5a56fa958ce541302dcaabfed979966f1dffedbea0ea0fc2/mypy-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:851833db876e7b650f93719c74b7879a08e338979c96054fdfc3bfd90a486355", size = 15548831, upload-time = "2026-08-15T03:03:15.55Z" }, + { url = "https://files.pythonhosted.org/packages/a3/52/cb31e084bc0314a1e384bdd677a4b80e55af04ccac077545e2238b9d320a/mypy-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:4c5095a327483591c94e0c8d3ef9e50d4ab1369b541eae007c1f23bc2a41f6bb", size = 11226359, upload-time = "2026-08-15T03:03:29.002Z" }, + { url = "https://files.pythonhosted.org/packages/7a/47/88fcf6217b43fa2da81a8c2611370af18141536a4f0294bbf98b457d456d/mypy-2.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:bbfe022634a2a195406bd469e888d2eaf193b02ba7e607391cd7640374aaae3b", size = 10214707, upload-time = "2026-08-15T03:02:48.807Z" }, + { url = "https://files.pythonhosted.org/packages/de/cf/862010ee800ca9c2bd0c4c0dacf0f092e5411824a09b8f97ad4be8fe250e/mypy-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:114dff494000f18bd10d5d95d84b8567b26da60279ecbe838131841df20e635d", size = 13964542, upload-time = "2026-08-15T03:02:21.43Z" }, + { url = "https://files.pythonhosted.org/packages/75/5a/3f3a2107b41e3e92e617e25daaee121413b91e9784bea733131ed4fecc5d/mypy-2.3.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8637731bb5eee3671eb2c3200827aa3564ed8a9309ecee4d1afe77e6d031bdb", size = 14168922, upload-time = "2026-08-15T03:03:00.351Z" }, + { url = "https://files.pythonhosted.org/packages/8b/41/04dc4fe7e63d7820fa4eff272e95157d30cbea921388f3ab3fe77794cd0b/mypy-2.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c80fbc405ed8020f5ff3802dc18cf060197bcdd3fbdd6a26ef2fd34dfdd5226", size = 15244791, upload-time = "2026-08-15T03:02:31.089Z" }, + { url = "https://files.pythonhosted.org/packages/96/fc/c3053b26b9054949285aa868cb6af8c10e7591541cacd79c5dcc06a1fcf9/mypy-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:84081f538ce27375045c02e3d7f81bd11d853400621ae245d87ce7b6c420ec74", size = 15501627, upload-time = "2026-08-15T03:03:34.128Z" }, + { url = "https://files.pythonhosted.org/packages/70/4e/d77daab008bbc4e5001374d7928f4a260d28f0e6747af444fc4763f7a310/mypy-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:e9144ac16fde007096f9563eb2041b4433c2d705c4218edeb79e7e9d01035ee6", size = 11243961, upload-time = "2026-08-15T03:02:11.952Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f8/7eb68c136e4abd30569fe31ef2bfcb7eceae9952cab80017c04cd09f5d0c/mypy-2.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:77ad9529e67dca28e511f5cd5671436584ce91f6d3bac159a353158187b986ac", size = 10213219, upload-time = "2026-08-15T03:02:26.361Z" }, + { url = "https://files.pythonhosted.org/packages/be/c4/42a49d44aeff804edf1b19acce0b49e8bd1a9c57dee9605dd8d980aa43d7/mypy-2.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:192abaedf75da1bc0b1cef104927e70ec49c1ef0031cc4825c7ee10a438ed24d", size = 13986778, upload-time = "2026-08-15T03:01:33.69Z" }, + { url = "https://files.pythonhosted.org/packages/45/13/9331fd2dfed7194d66c5304072894a8be3e51e9deda6863c1eceaa35a43d/mypy-2.3.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf678dffd16efcda2c15cbd30e9ecc0081388e29ea23687a88e686ed92638dc3", size = 14188467, upload-time = "2026-08-15T03:02:40.554Z" }, + { url = "https://files.pythonhosted.org/packages/78/f7/f4a34edab45667c5465855dc585a20e87978ffa8aee711445b7239d120c6/mypy-2.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e036f06b41630f4c8a1d48f9ac6aa26acc65f8be089973f5519da643318f03f", size = 15225538, upload-time = "2026-08-15T03:03:09.761Z" }, + { url = "https://files.pythonhosted.org/packages/40/05/534b3590757bd05794f73e07f6666c2a77b8597ffed795c94ce570096aa0/mypy-2.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71af9c8a894e862b58e92abb08e53b05a384a1e5e5d6dc7cda59126211a53d82", size = 15480805, upload-time = "2026-08-15T03:01:41.134Z" }, + { url = "https://files.pythonhosted.org/packages/55/da/bdfba852e2562f599624af5bb7d29e36b0b4f526f2b8bac85efe0dd1803d/mypy-2.3.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:3c80cd23d85368bdd9f37d5231dfd97d35bcbf5bf41af96ef3a9b078ad1957f9", size = 7761712, upload-time = "2026-08-15T03:02:36.008Z" }, + { url = "https://files.pythonhosted.org/packages/98/31/60fc64a74cdba4f2a5d642d32317993e479163e1ac7d91b695e5d15e2264/mypy-2.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:4956f34d145e145562a0a0bf367f642bbc85c04ec2baf47ae015947c3169a85d", size = 11423968, upload-time = "2026-08-15T03:02:06.931Z" }, + { url = "https://files.pythonhosted.org/packages/a9/23/eb5950b24cd26ba3b78f87707a275568d633c77dae8e61c9661be6055ca6/mypy-2.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:cfb12e360242d23d91f5e978d94f58ea66acf5804c4fb6f2f794a20d4cb1b595", size = 10399323, upload-time = "2026-08-15T03:02:33.671Z" }, + { url = "https://files.pythonhosted.org/packages/82/c7/f80f4e46c0b9a00eb5f78a79d49dda8bdf56a5230f7257fb33e76be04da7/mypy-2.3.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5f1c50bb05b64e2026b52867e8d21106f01313c744a2c4ecc34c90d12e8d6e2", size = 15121308, upload-time = "2026-08-15T03:01:46.053Z" }, + { url = "https://files.pythonhosted.org/packages/5d/74/9b04f17c7074cc5188f02fb63a2ca1d43fedf479e84fe3091c39061a1d7f/mypy-2.3.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:667196b352f4cf304ded4c10f90cfc179263a1acfb3cdcfa984bdfd340d498bc", size = 15536590, upload-time = "2026-08-15T03:01:35.941Z" }, + { url = "https://files.pythonhosted.org/packages/26/04/c837ef6208e567774e2ed1f863f8ba6ec4817b1b6dd426315e5d559b6ec9/mypy-2.3.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9c53e395c12cad2c6d4b67d5da7c6057638a132d85c08b73646b18f802a0045", size = 16791074, upload-time = "2026-08-15T03:01:31.073Z" }, + { url = "https://files.pythonhosted.org/packages/37/68/48730230afa45192d5bd429a6a2ff24a6f8dedda90fdf2b221792b54518f/mypy-2.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:18162b128c3f9c703cd35f5537446900b0d21a2549aa7a95d21380d2ef643fb0", size = 17069183, upload-time = "2026-08-15T03:02:28.566Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ea/ca23fc9c20eeda09a15c9cbcf50015d0e73f409f6ead059e42aa69a608ff/mypy-2.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:30c0477d4aab7b7f39c8397dc877f2c96b9fe5588ec379f372c56eb63d599f63", size = 12154679, upload-time = "2026-08-15T03:02:04.809Z" }, + { url = "https://files.pythonhosted.org/packages/3b/67/8d982126034990869466f73b8db80dcb2234a7ac39b4dad093e047a79835/mypy-2.3.1-cp314-cp314t-win_arm64.whl", hash = "sha256:6941ab3619377bc3f32ca02876b07d27f216f5201604b664d3937ea0fdd23bb4", size = 10969159, upload-time = "2026-08-15T03:02:38.152Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f7/41e7f2d8117fbc7a7587286162ffe2f688984b69c46ed63cf5f2e4fc3bae/mypy-2.3.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:6f041a6de52c9217ca125e78ba0a335cb7fd98a1c0580978e49ab2b126f70b57", size = 13990694, upload-time = "2026-08-15T03:03:21.919Z" }, + { url = "https://files.pythonhosted.org/packages/06/85/8f665811a0c8f3bf6fa1d9acd665ec2d97a2bcc453ae68dcd92340941cd6/mypy-2.3.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5159ae60f5dbc3a498af5ba8365505808ac8031bc63f9e00304ad545d40bdd9b", size = 14203518, upload-time = "2026-08-15T03:01:48.455Z" }, + { url = "https://files.pythonhosted.org/packages/2d/82/91b866c8546b120bff83b73a439d90d2d63ef3aff113599e6b8e4d566848/mypy-2.3.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47a8a7a0a7f6f6e63995c0ac36fa0c07b127413fdc81f0439b7f3dccafd33561", size = 15220224, upload-time = "2026-08-15T03:01:23.577Z" }, + { url = "https://files.pythonhosted.org/packages/c8/78/c226c99208ee40de7c768369fa533f933afa003dfdc606ff021450724e91/mypy-2.3.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2329c0501293d4e1f33bc15d04d6304d65a1cdda967ee93a05c1e681a3923133", size = 15501512, upload-time = "2026-08-15T03:02:09.453Z" }, + { url = "https://files.pythonhosted.org/packages/a9/e7/7cfb3f106c393979f4cc37ad6c0586044d50401e3c35b0c003e4f3ba6bc9/mypy-2.3.1-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:bb26deed807bdb0457cf3e3f1cd7c4a1cf9d66864eaf1b4a61e06805d4c6b1f9", size = 7761913, upload-time = "2026-08-15T03:01:55.65Z" }, + { url = "https://files.pythonhosted.org/packages/99/3c/52affefa273b97939a1f474ae4a349c8718635c15b941112dfab4291b0c1/mypy-2.3.1-cp315-cp315-win_amd64.whl", hash = "sha256:375d7013876a8233b2d05be185bfa09f689696cd999ce8b1cfe6acac5c80e8a3", size = 11422533, upload-time = "2026-08-15T03:03:24.101Z" }, + { url = "https://files.pythonhosted.org/packages/2a/b7/75643e70c72a5b346d8a9b1543c967ea8824df2ee3fb7ccba652c272b7bb/mypy-2.3.1-cp315-cp315-win_arm64.whl", hash = "sha256:586b3612214cceabb3c0f588c97e7d1e535393f06a60e912e994f6b3ace97523", size = 10397931, upload-time = "2026-08-15T03:02:55.265Z" }, + { url = "https://files.pythonhosted.org/packages/10/ce/53be21f2d4adfcd26f63f1184a13ed797015ab463853f117e2e11e4d726f/mypy-2.3.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:ef0c6335cda9d807f8193d8ff6204a72bc909fa9882aacbca14f43cdb7188306", size = 15118669, upload-time = "2026-08-15T03:02:51.479Z" }, + { url = "https://files.pythonhosted.org/packages/62/43/20de757cd42989d291a17fad607742c4c74e875ce5cea00e5a5225020ac1/mypy-2.3.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e598c8c66401d26b150872154a286e6d484cf2789c3bb28a7556806298423021", size = 15545627, upload-time = "2026-08-15T03:03:05.132Z" }, + { url = "https://files.pythonhosted.org/packages/7e/fc/092bdf77ad280eaf501422f0f3b966012b528076cc13e41a774861c907d1/mypy-2.3.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eda22fd4efa9dcd39331d1dede9b5b8b8a7fd69af07592e778433da98610d29e", size = 16764157, upload-time = "2026-08-15T03:02:23.958Z" }, + { url = "https://files.pythonhosted.org/packages/94/5c/c94c4d62d909b07f552d0d9356d7acc943825558e602a64822ffa2231536/mypy-2.3.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:2a0ba2e57847849fb0d1fcdabb32786d223095ed8bc121dfe322bcdb3d9c46bc", size = 17073258, upload-time = "2026-08-15T03:02:14.573Z" }, + { url = "https://files.pythonhosted.org/packages/c0/f7/511a88b89e478053c02d22039bb8f3ce4183efe8fd7a4f0a5910a8bb0a32/mypy-2.3.1-cp315-cp315t-win_amd64.whl", hash = "sha256:3f7e865dd51f235f60a2dbcd8728a1c095f5ca28f095d48a725b84cd935735c4", size = 12135505, upload-time = "2026-08-15T03:02:16.714Z" }, + { url = "https://files.pythonhosted.org/packages/71/bf/02573b56964ecb0f7c644f915f53c325ae15c3faec521c5adf11599a32df/mypy-2.3.1-cp315-cp315t-win_arm64.whl", hash = "sha256:8ad80807dc3ab8ea978b1b2b6e4a657194ace1d4ef03e0e731aff1abd517da29", size = 10962647, upload-time = "2026-08-15T03:01:43.712Z" }, + { url = "https://files.pythonhosted.org/packages/8e/41/9675c7a1e78edecfba0b79e587a52594c56e189368261dc7b3a7fffb9527/mypy-2.3.1-py3-none-any.whl", hash = "sha256:6ed5c7e3419083268e5c9258bd1c1ef91af44a9e89374dbcaf37b775716e72eb", size = 2754338, upload-time = "2026-08-15T03:02:53.4Z" }, +] + +[[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" } +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" }, +] + [[package]] name = "nodeenv" version = "1.9.1" @@ -362,6 +598,15 @@ 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" }, ] +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + [[package]] name = "platformdirs" version = "4.5.1" @@ -607,6 +852,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl", hash = "sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3", size = 39814, upload-time = "2026-03-04T18:55:31.284Z" }, ] +[[package]] +name = "types-pyyaml" +version = "6.0.12.20260815" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/72/b56089aeee6c496d969bac42376bedb6e3eeab4682e1018fa3137122f94b/types_pyyaml-6.0.12.20260815.tar.gz", hash = "sha256:28764110c9cf35846e733da32d8d734df7473c5dde9ef67c3b7332ec0e819858", size = 18545, upload-time = "2026-08-15T02:41:51.532Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/52/eefeba09be4ef2a1eb989eb92934561e8e502a6ee3c32654996e4be7e399/types_pyyaml-6.0.12.20260815-py3-none-any.whl", hash = "sha256:6f332212b7e191f3afd5016a713c510b6340593b7ebec573c7d5d20aa5386d3b", size = 21148, upload-time = "2026-08-15T02:41:50.555Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + [[package]] name = "urllib3" version = "2.7.0"