From f22fee83a36d89007496c7a05b462a6fec41854b Mon Sep 17 00:00:00 2001 From: TKaltofen Date: Fri, 3 Jul 2026 14:36:35 +0200 Subject: [PATCH 1/3] Merge pull request #1 from TomKaltofen/feat/govdata-population-connector feat: GovData connector for M1 (population, elections, environment) --- README.md | 54 + .../compute_frameworks/my_plugin/__init__.py | 5 - .../my_plugin/my_compute_framework.py | 22 - .../my_plugin/tests/__init__.py | 1 - .../tests/test_my_compute_framework.py | 15 - .../extenders/my_plugin/__init__.py | 5 - .../extenders/my_plugin/my_extender.py | 17 - .../extenders/my_plugin/tests/__init__.py | 1 - .../my_plugin/tests/test_my_extender.py | 15 - .../feature_groups/govdata/__init__.py | 28 + .../feature_groups/govdata/cache.py | 112 ++ .../feature_groups/govdata/client.py | 72 ++ .../feature_groups/govdata/discovery.py | 127 +++ .../feature_groups/govdata/locator.py | 42 + .../feature_groups/govdata/parse.py | 273 +++++ .../feature_groups/govdata/reader.py | 117 ++ .../feature_groups/govdata/tests/__init__.py | 0 .../feature_groups/govdata/tests/conftest.py | 8 + .../govdata/tests/fixtures/kerg_sample.csv | 25 + .../govdata/tests/fixtures/package_show.json | 1 + .../tests/fixtures/population_sample.csv | 1001 +++++++++++++++++ .../govdata/tests/fixtures/uba_measures.json | 1 + .../govdata/tests/fixtures/value_markers.csv | 8 + .../govdata/tests/test_cache.py | 62 + .../govdata/tests/test_client.py | 45 + .../govdata/tests/test_discovery.py | 63 ++ .../govdata/tests/test_parse.py | 151 +++ .../govdata/tests/test_parse_hypothesis.py | 38 + .../govdata/tests/test_reader.py | 107 ++ .../feature_groups/govdata/tests/test_uba.py | 194 ++++ .../feature_groups/govdata/uba.py | 193 ++++ .../feature_groups/my_plugin/__init__.py | 5 - .../my_plugin/my_feature_group.py | 14 - .../my_plugin/tests/__init__.py | 1 - .../my_plugin/tests/test_my_feature_group.py | 15 - pyproject.toml | 15 +- uv.lock | 797 ++++++++++++- 37 files changed, 3530 insertions(+), 120 deletions(-) delete mode 100644 mloda_plugin_govdata/compute_frameworks/my_plugin/__init__.py delete mode 100644 mloda_plugin_govdata/compute_frameworks/my_plugin/my_compute_framework.py delete mode 100644 mloda_plugin_govdata/compute_frameworks/my_plugin/tests/__init__.py delete mode 100644 mloda_plugin_govdata/compute_frameworks/my_plugin/tests/test_my_compute_framework.py delete mode 100644 mloda_plugin_govdata/extenders/my_plugin/__init__.py delete mode 100644 mloda_plugin_govdata/extenders/my_plugin/my_extender.py delete mode 100644 mloda_plugin_govdata/extenders/my_plugin/tests/__init__.py delete mode 100644 mloda_plugin_govdata/extenders/my_plugin/tests/test_my_extender.py create mode 100644 mloda_plugin_govdata/feature_groups/govdata/__init__.py create mode 100644 mloda_plugin_govdata/feature_groups/govdata/cache.py create mode 100644 mloda_plugin_govdata/feature_groups/govdata/client.py create mode 100644 mloda_plugin_govdata/feature_groups/govdata/discovery.py create mode 100644 mloda_plugin_govdata/feature_groups/govdata/locator.py create mode 100644 mloda_plugin_govdata/feature_groups/govdata/parse.py create mode 100644 mloda_plugin_govdata/feature_groups/govdata/reader.py create mode 100644 mloda_plugin_govdata/feature_groups/govdata/tests/__init__.py create mode 100644 mloda_plugin_govdata/feature_groups/govdata/tests/conftest.py create mode 100644 mloda_plugin_govdata/feature_groups/govdata/tests/fixtures/kerg_sample.csv create mode 100644 mloda_plugin_govdata/feature_groups/govdata/tests/fixtures/package_show.json create mode 100644 mloda_plugin_govdata/feature_groups/govdata/tests/fixtures/population_sample.csv create mode 100644 mloda_plugin_govdata/feature_groups/govdata/tests/fixtures/uba_measures.json create mode 100644 mloda_plugin_govdata/feature_groups/govdata/tests/fixtures/value_markers.csv create mode 100644 mloda_plugin_govdata/feature_groups/govdata/tests/test_cache.py create mode 100644 mloda_plugin_govdata/feature_groups/govdata/tests/test_client.py create mode 100644 mloda_plugin_govdata/feature_groups/govdata/tests/test_discovery.py create mode 100644 mloda_plugin_govdata/feature_groups/govdata/tests/test_parse.py create mode 100644 mloda_plugin_govdata/feature_groups/govdata/tests/test_parse_hypothesis.py create mode 100644 mloda_plugin_govdata/feature_groups/govdata/tests/test_reader.py create mode 100644 mloda_plugin_govdata/feature_groups/govdata/tests/test_uba.py create mode 100644 mloda_plugin_govdata/feature_groups/govdata/uba.py delete mode 100644 mloda_plugin_govdata/feature_groups/my_plugin/__init__.py delete mode 100644 mloda_plugin_govdata/feature_groups/my_plugin/my_feature_group.py delete mode 100644 mloda_plugin_govdata/feature_groups/my_plugin/tests/__init__.py delete mode 100644 mloda_plugin_govdata/feature_groups/my_plugin/tests/test_my_feature_group.py diff --git a/README.md b/README.md index 1b1f055..3aad80a 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,60 @@ # mloda-plugin-govdata +Connectors for German open government data, built on [mloda](https://github.com/mloda-ai/mloda). Request the columns you want as mloda features; the plugin handles CKAN discovery, download with caching and retries, and parsing (German CSV or publisher JSON) into a typed Arrow table. + +Three example datasets cover the M1 themes: population (GovData CSV), elections (Bundeswahlleiterin `kerg.csv`), and environment (UBA Air Data JSON). + +## Usage + +Read the Stuttgart population dataset (via GovData) as a typed PyArrow table: + +```python +from mloda.user import Feature, mloda +from mloda_plugin_govdata.feature_groups.govdata import GovDataReader + +slug = "einwohner-nach-altersgruppen-und-stadtbezirken" +result = mloda.run_all( + [ + Feature("Einwohner", options={GovDataReader.__name__: slug}), + Feature("Stadtbezirk", options={GovDataReader.__name__: slug}), + ], + compute_frameworks=["PyArrowTable"], +) +table = result[0] # pyarrow.Table with the requested columns +``` + +The option value is a GovData dataset slug or a direct distribution URL. The license is read from the CKAN distribution metadata. Set `GovDataReader.cache_dir` to control where downloads are cached. + +The elections reader handles a direct CSV URL whose file has a multi-row merged header (Bundeswahlleiterin `kerg.csv`): + +```python +from mloda_plugin_govdata.feature_groups.govdata import BundeswahlleiterinReader + +kerg = "https://www.bundeswahlleiterin.de/bundestagswahlen/2025/ergebnisse/opendata/btw25/csv/kerg.csv" +result = mloda.run_all( + [Feature("Gebiet", options={BundeswahlleiterinReader.__name__: kerg})], + compute_frameworks=["PyArrowTable"], +) +``` + +The environment reader fetches the Umweltbundesamt (UBA) Air Data v4 `measures` endpoint (REST JSON) and flattens it to one typed row per station and timestamp. `uba_measures_url` builds the query (here: hourly ozone at station 143): + +```python +from mloda_plugin_govdata.feature_groups.govdata import UbaAirReader, uba_measures_url + +url = uba_measures_url(station=143, component=3, scope=2, date_from="2025-01-01", date_to="2025-01-01") +result = mloda.run_all( + [ + Feature("date_start", options={UbaAirReader.__name__: url}), + Feature("value", options={UbaAirReader.__name__: url}), + ], + compute_frameworks=["PyArrowTable"], +) +``` + +Columns are `station_id`, `date_start`, `component_id`, `scope_id`, `value`, `date_end`, and `index` (the air-quality index). Component and scope ids come from the UBA `components` and `scopes` endpoints. + ## Related Repositories - **[mloda](https://github.com/mloda-ai/mloda)**: The core library for open data access. Declaratively define what data you need, not how to get it. mloda handles feature resolution, dependency management, and compute framework abstraction automatically. diff --git a/mloda_plugin_govdata/compute_frameworks/my_plugin/__init__.py b/mloda_plugin_govdata/compute_frameworks/my_plugin/__init__.py deleted file mode 100644 index b310f9f..0000000 --- a/mloda_plugin_govdata/compute_frameworks/my_plugin/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""My Plugin ComputeFramework package.""" - -from mloda_plugin_govdata.compute_frameworks.my_plugin.my_compute_framework import MyComputeFramework - -__all__ = ["MyComputeFramework"] diff --git a/mloda_plugin_govdata/compute_frameworks/my_plugin/my_compute_framework.py b/mloda_plugin_govdata/compute_frameworks/my_plugin/my_compute_framework.py deleted file mode 100644 index 1cb5df4..0000000 --- a/mloda_plugin_govdata/compute_frameworks/my_plugin/my_compute_framework.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Example ComputeFramework implementation.""" - -from typing import Optional, Set -from uuid import UUID, uuid4 - -from mloda.core.abstract_plugins.components.parallelization_modes import ParallelizationMode -from mloda.core.abstract_plugins.function_extender import Extender -from mloda.provider import ComputeFramework - - -class MyComputeFramework(ComputeFramework): - """Example ComputeFramework - rename and customize for your use case.""" - - def __init__( - self, - mode: ParallelizationMode = ParallelizationMode.SYNC, - children_if_root: frozenset[UUID] = frozenset(), - uuid: UUID = uuid4(), - function_extender: Optional[Set[Extender]] = None, - ) -> None: - """Initialize with default values for minimal instantiation.""" - super().__init__(mode, children_if_root, uuid, function_extender) diff --git a/mloda_plugin_govdata/compute_frameworks/my_plugin/tests/__init__.py b/mloda_plugin_govdata/compute_frameworks/my_plugin/tests/__init__.py deleted file mode 100644 index d239d81..0000000 --- a/mloda_plugin_govdata/compute_frameworks/my_plugin/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for My Plugin ComputeFramework.""" diff --git a/mloda_plugin_govdata/compute_frameworks/my_plugin/tests/test_my_compute_framework.py b/mloda_plugin_govdata/compute_frameworks/my_plugin/tests/test_my_compute_framework.py deleted file mode 100644 index 513fba6..0000000 --- a/mloda_plugin_govdata/compute_frameworks/my_plugin/tests/test_my_compute_framework.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Tests for MyComputeFramework.""" - -from mloda_plugin_govdata.compute_frameworks.my_plugin import MyComputeFramework -from mloda.provider import ComputeFramework - - -def test_extends_base() -> None: - """MyComputeFramework should extend ComputeFramework.""" - assert issubclass(MyComputeFramework, ComputeFramework) - - -def test_instantiation() -> None: - """MyComputeFramework should instantiate with no arguments.""" - instance = MyComputeFramework() - assert instance is not None diff --git a/mloda_plugin_govdata/extenders/my_plugin/__init__.py b/mloda_plugin_govdata/extenders/my_plugin/__init__.py deleted file mode 100644 index ac22fae..0000000 --- a/mloda_plugin_govdata/extenders/my_plugin/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""My Plugin Extender package.""" - -from mloda_plugin_govdata.extenders.my_plugin.my_extender import MyExtender - -__all__ = ["MyExtender"] diff --git a/mloda_plugin_govdata/extenders/my_plugin/my_extender.py b/mloda_plugin_govdata/extenders/my_plugin/my_extender.py deleted file mode 100644 index f0d761d..0000000 --- a/mloda_plugin_govdata/extenders/my_plugin/my_extender.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Example Extender implementation.""" - -from typing import Any, Set - -from mloda.core.abstract_plugins.function_extender import Extender, ExtenderHook - - -class MyExtender(Extender): - """Example Extender - rename and customize for your use case.""" - - def wraps(self) -> Set[ExtenderHook]: - """Return the set of hooks this extender wraps.""" - return set() - - def __call__(self, func: Any, *args: Any, **kwargs: Any) -> Any: - """Execute the wrapped function without modification.""" - return func(*args, **kwargs) diff --git a/mloda_plugin_govdata/extenders/my_plugin/tests/__init__.py b/mloda_plugin_govdata/extenders/my_plugin/tests/__init__.py deleted file mode 100644 index 2e9e8e6..0000000 --- a/mloda_plugin_govdata/extenders/my_plugin/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for My Plugin Extender.""" diff --git a/mloda_plugin_govdata/extenders/my_plugin/tests/test_my_extender.py b/mloda_plugin_govdata/extenders/my_plugin/tests/test_my_extender.py deleted file mode 100644 index c388c54..0000000 --- a/mloda_plugin_govdata/extenders/my_plugin/tests/test_my_extender.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Tests for MyExtender.""" - -from mloda_plugin_govdata.extenders.my_plugin import MyExtender -from mloda.core.abstract_plugins.function_extender import Extender - - -def test_extends_base() -> None: - """MyExtender should extend Extender.""" - assert issubclass(MyExtender, Extender) - - -def test_instantiation() -> None: - """MyExtender should instantiate with no arguments.""" - instance = MyExtender() - assert instance is not None diff --git a/mloda_plugin_govdata/feature_groups/govdata/__init__.py b/mloda_plugin_govdata/feature_groups/govdata/__init__.py new file mode 100644 index 0000000..2aeb80a --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/__init__.py @@ -0,0 +1,28 @@ +"""GovData connector: read German open-government CSV distributions into mloda.""" + +from .discovery import Dataset, ResolvedDistribution, Resource, normalize_license, resolve_distribution +from .locator import GovDataLocator +from .parse import ColumnType, parse_german_csv, parse_german_csv_bytes, parse_multi_header_csv +from .reader import BundeswahlleiterinReader, GovDataFeature, GovDataReader, UbaAirReader +from .uba import UBA_AIR_BASE, parse_uba_measures, parse_uba_measures_bytes, uba_measures_url + +__all__ = [ + "UBA_AIR_BASE", + "BundeswahlleiterinReader", + "ColumnType", + "Dataset", + "GovDataFeature", + "GovDataLocator", + "GovDataReader", + "Resource", + "ResolvedDistribution", + "UbaAirReader", + "normalize_license", + "parse_german_csv", + "parse_german_csv_bytes", + "parse_multi_header_csv", + "parse_uba_measures", + "parse_uba_measures_bytes", + "resolve_distribution", + "uba_measures_url", +] diff --git a/mloda_plugin_govdata/feature_groups/govdata/cache.py b/mloda_plugin_govdata/feature_groups/govdata/cache.py new file mode 100644 index 0000000..73ddf12 --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/cache.py @@ -0,0 +1,112 @@ +"""Content-addressed download cache with conditional-GET revalidation.""" + +from __future__ import annotations + +import hashlib +import json +import os +from dataclasses import dataclass +from pathlib import Path +from types import TracebackType +from typing import Any + +import httpx + +from .client import build_client, request_with_retry + + +@dataclass(frozen=True) +class CachedFile: + path: Path + url: str + sha256: str + etag: str | None + + +class DownloadCache: + """Stores downloaded bodies addressed by content hash. + + Revalidates with ``If-None-Match`` / ``If-Modified-Since``; a ``304`` reuses + the stored body (sha256-verified). Safe to share across runs: entries are + keyed by URL and content hash. + """ + + def __init__(self, cache_dir: str | os.PathLike[str], client: httpx.Client | None = None) -> None: + self.cache_dir = Path(cache_dir) + self.cache_dir.mkdir(parents=True, exist_ok=True) + self._owns_client = client is None + self._client = client if client is not None else build_client() + + def __enter__(self) -> "DownloadCache": + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.close() + + def close(self) -> None: + if self._owns_client: + self._client.close() + + def _meta_path(self, url: str) -> Path: + key = hashlib.sha256(url.encode("utf-8")).hexdigest() + return self.cache_dir / f"{key}.meta.json" + + def _read_meta(self, url: str) -> dict[str, Any] | None: + meta_path = self._meta_path(url) + if not meta_path.exists(): + return None + try: + loaded = json.loads(meta_path.read_text(encoding="utf-8")) + except (json.JSONDecodeError, OSError): + return None # unreadable metadata: treat as a cache miss and re-download + return loaded if isinstance(loaded, dict) else None + + def get_or_download(self, url: str) -> CachedFile: + meta = self._read_meta(url) + cached = self._cached_from_meta(url, meta) if meta is not None else None + + # Only revalidate conditionally when there is a valid body to fall back on; + # otherwise request unconditionally so the server sends a full 200. + headers: dict[str, str] = {} + if cached is not None and meta is not None: + if meta.get("etag"): + headers["If-None-Match"] = meta["etag"] + if meta.get("last_modified"): + headers["If-Modified-Since"] = meta["last_modified"] + + response = request_with_retry(self._client, "GET", url, headers=headers) + if response.status_code == 304: + if cached is not None: + return cached + raise RuntimeError(f"server returned 304 for {url} but no cached body is available") + response.raise_for_status() + return self._store(url, response) + + def _cached_from_meta(self, url: str, meta: dict[str, Any]) -> CachedFile | None: + data_path = self.cache_dir / str(meta.get("data_file", "")) + if not data_path.exists(): + return None + digest = hashlib.sha256(data_path.read_bytes()).hexdigest() + if digest != meta.get("sha256"): + return None # corrupted cache; force re-download + return CachedFile(path=data_path, url=url, sha256=digest, etag=meta.get("etag")) + + def _store(self, url: str, response: httpx.Response) -> CachedFile: + body = response.content + digest = hashlib.sha256(body).hexdigest() + data_path = self.cache_dir / f"{digest}.bin" + data_path.write_bytes(body) + meta: dict[str, Any] = { + "url": url, + "etag": response.headers.get("ETag"), + "last_modified": response.headers.get("Last-Modified"), + "sha256": digest, + "data_file": data_path.name, + } + self._meta_path(url).write_text(json.dumps(meta), encoding="utf-8") + return CachedFile(path=data_path, url=url, sha256=digest, etag=meta["etag"]) diff --git a/mloda_plugin_govdata/feature_groups/govdata/client.py b/mloda_plugin_govdata/feature_groups/govdata/client.py new file mode 100644 index 0000000..533307b --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/client.py @@ -0,0 +1,72 @@ +"""HTTP client and retry policy for GovData / publisher requests.""" + +from __future__ import annotations + +import importlib.metadata +from typing import Any + +import httpx +from tenacity import RetryCallState, retry, retry_if_exception, stop_after_attempt, wait_exponential_jitter + +# Four-part timeout: distinct connect / read / write / pool budgets. +DEFAULT_TIMEOUT = httpx.Timeout(connect=10.0, read=60.0, write=30.0, pool=10.0) +RETRYABLE_STATUS: frozenset[int] = frozenset({429, 500, 502, 503, 504}) +MAX_ATTEMPTS = 5 +MAX_RETRY_AFTER = 60.0 # cap a server's Retry-After so it cannot stall the client for hours + +_BASE_WAIT = wait_exponential_jitter(initial=0.5, max=10.0) + + +def _user_agent() -> str: + try: + version = importlib.metadata.version("mloda-plugin-govdata") + except importlib.metadata.PackageNotFoundError: + version = "0.0.0" + return f"mloda-plugin-govdata/{version} (+https://github.com/mloda-ai/mloda-plugin-govdata)" + + +def build_client(*, timeout: httpx.Timeout = DEFAULT_TIMEOUT) -> httpx.Client: + """A pooled httpx client with the polite User-Agent and timeout budget.""" + return httpx.Client(timeout=timeout, headers={"User-Agent": _user_agent()}, follow_redirects=True) + + +class RetryableStatusError(Exception): + """Raised internally so a retryable status code triggers a tenacity retry.""" + + def __init__(self, response: httpx.Response) -> None: + self.response = response + self.retry_after = _parse_retry_after(response) + super().__init__(f"retryable status {response.status_code} for {response.request.url}") + + +def _parse_retry_after(response: httpx.Response) -> float | None: + raw = response.headers.get("Retry-After") + if raw is None: + return None + try: + return float(raw) # seconds form; HTTP-date form is not honored + except ValueError: + return None + + +def _is_retryable(exc: BaseException) -> bool: + return isinstance(exc, (httpx.TransportError, RetryableStatusError)) + + +def _wait(state: RetryCallState) -> float: + base = _BASE_WAIT(state) + outcome = state.outcome + if outcome is not None: + exc = outcome.exception() + if isinstance(exc, RetryableStatusError) and exc.retry_after is not None: + return max(base, min(exc.retry_after, MAX_RETRY_AFTER)) + return base + + +@retry(retry=retry_if_exception(_is_retryable), wait=_wait, stop=stop_after_attempt(MAX_ATTEMPTS), reraise=True) +def request_with_retry(client: httpx.Client, method: str, url: str, **kwargs: Any) -> httpx.Response: + """Issue a request, retrying transport errors and retryable status codes.""" + response = client.request(method, url, **kwargs) + if response.status_code in RETRYABLE_STATUS: + raise RetryableStatusError(response) + return response diff --git a/mloda_plugin_govdata/feature_groups/govdata/discovery.py b/mloda_plugin_govdata/feature_groups/govdata/discovery.py new file mode 100644 index 0000000..884e0f5 --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/discovery.py @@ -0,0 +1,127 @@ +"""CKAN discovery: resolve a GovData dataset to a distribution URL and license.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +import httpx +from pydantic import BaseModel, ConfigDict + +from .client import request_with_retry +from .locator import GovDataLocator + +# dcat-ap.de license URIs observed across GovData distributions -> short labels. +LICENSE_LABELS: dict[str, str] = { + "http://dcat-ap.de/def/licenses/cc-by/4.0": "CC-BY-4.0", + "http://dcat-ap.de/def/licenses/dl-by-de/2.0": "DL-DE-BY-2.0", + "http://dcat-ap.de/def/licenses/dl-zero-de/2.0": "DL-DE-Zero-2.0", + "http://dcat-ap.de/def/licenses/cc-zero": "CC0-1.0", + "http://dcat-ap.de/def/licenses/geonutz/20130319": "GeoNutzV", +} + + +def normalize_license(uri: str | None) -> str | None: + """Map a dcat-ap.de license URI to a short label, tolerating spelling drift. + + Returns the raw value for unknown / free-text licenses (warn, never refuse), + and None when no license is present. + """ + if not uri: + return None + candidate = uri.strip() + # dataset-level uses dl-by-de/2_0, resource-level dl-by-de/2.0; normalize the underscore. + normalized = candidate.replace("/2_0", "/2.0") + return LICENSE_LABELS.get(candidate) or LICENSE_LABELS.get(normalized) or candidate + + +class Resource(BaseModel): + model_config = ConfigDict(extra="ignore") + + url: str + format: str | None = None + license: str | None = None + mimetype: str | None = None + name: str | None = None + size: int | None = None + + +class Dataset(BaseModel): + model_config = ConfigDict(extra="ignore") + + name: str | None = None + title: str | None = None + notes: str | None = None + resources: list[Resource] = [] + extras: dict[str, str] = {} + + @classmethod + def from_ckan(cls, result: dict[str, Any]) -> "Dataset": + extras = {e["key"]: str(e.get("value", "")) for e in result.get("extras", []) if "key" in e} + resources = [Resource.model_validate(r) for r in result.get("resources", [])] + return cls( + name=result.get("name"), + title=result.get("title"), + notes=result.get("notes"), + resources=resources, + extras=extras, + ) + + +@dataclass(frozen=True) +class ResolvedDistribution: + url: str + license: str | None + dataset: Dataset | None + + +def fetch_dataset(client: httpx.Client, locator: GovDataLocator) -> Dataset: + """Call CKAN ``package_show`` for the locator's dataset id.""" + response = request_with_retry( + client, + "GET", + f"{locator.ckan_base}/package_show", + params={"id": locator.dataset_id}, + ) + response.raise_for_status() + payload: dict[str, Any] = response.json() + if not payload.get("success"): + raise ValueError(f"CKAN package_show was not successful for {locator.dataset_id!r}") + return Dataset.from_ckan(payload["result"]) + + +def _looks_like_csv(resource: Resource) -> bool: + """True when the resource advertises CSV via format, mimetype, or URL suffix.""" + fmt = (resource.format or "").strip().lower() + mimetype = (resource.mimetype or "").strip().lower() + return fmt == "csv" or mimetype == "text/csv" or resource.url.lower().endswith(".csv") + + +def _select_resource(resources: list[Resource], resource_index: int) -> Resource: + """Pick the distribution to read, preferring CSV since that is all the reader parses.""" + if not 0 <= resource_index < len(resources): + raise IndexError(f"resource_index {resource_index} is out of range ({len(resources)} resources)") + chosen = resources[resource_index] + if _looks_like_csv(chosen): + return chosen + for resource in resources: + if _looks_like_csv(resource): + return resource + return chosen + + +def resolve_distribution(locator: GovDataLocator, client: httpx.Client) -> ResolvedDistribution: + """Resolve a locator to a concrete distribution URL plus its license. + + A direct ``distribution_url`` is used as-is; a ``dataset_id`` is resolved via + ``package_show``, reading the license from the chosen distribution (the + distribution ``license`` is reliably populated; dataset ``license_id`` is not). + """ + if locator.distribution_url is not None: + return ResolvedDistribution(url=locator.distribution_url, license=None, dataset=None) + + dataset = fetch_dataset(client, locator) + if not dataset.resources: + raise ValueError(f"dataset {locator.dataset_id!r} exposes no resources") + resource = _select_resource(dataset.resources, locator.resource_index) + return ResolvedDistribution(url=resource.url, license=normalize_license(resource.license), dataset=dataset) diff --git a/mloda_plugin_govdata/feature_groups/govdata/locator.py b/mloda_plugin_govdata/feature_groups/govdata/locator.py new file mode 100644 index 0000000..f27627c --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/locator.py @@ -0,0 +1,42 @@ +"""GovData locator: identifies a dataset distribution to read.""" + +from __future__ import annotations + +from dataclasses import dataclass + +DEFAULT_CKAN_BASE = "https://ckan.govdata.de/api/3/action" + + +@dataclass(frozen=True) +class GovDataLocator: + """Points at one GovData distribution. + + Either a CKAN ``dataset_id`` (resolved to a distribution via ``package_show``) + or a direct ``distribution_url``. ``resource_index`` selects the distribution + when a dataset exposes several. + """ + + dataset_id: str | None = None + distribution_url: str | None = None + ckan_base: str = DEFAULT_CKAN_BASE + resource_index: int = 0 + + def __post_init__(self) -> None: + if not self.dataset_id and not self.distribution_url: + raise ValueError("GovDataLocator needs a dataset_id or a distribution_url.") + + @classmethod + def from_string(cls, value: str) -> "GovDataLocator": + """A URL becomes a direct distribution; anything else is a dataset id.""" + if value.startswith(("http://", "https://")): + return cls(distribution_url=value) + return cls(dataset_id=value) + + @classmethod + def coerce(cls, value: object) -> "GovDataLocator | None": + """Normalize a reader option value into a locator, or None if unusable.""" + if isinstance(value, GovDataLocator): + return value + if isinstance(value, str) and value: + return cls.from_string(value) + return None diff --git a/mloda_plugin_govdata/feature_groups/govdata/parse.py b/mloda_plugin_govdata/feature_groups/govdata/parse.py new file mode 100644 index 0000000..be7e2e8 --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/parse.py @@ -0,0 +1,273 @@ +"""Deterministic parser for German public-authority CSVs into a typed Arrow table. + +Handles the traits that break a naive ``pyarrow.csv`` read: cp1252/latin +encodings, semicolon delimiters, German number formatting (``.`` thousands, +``,`` decimal), German dates, and statistical value markers where ``-`` means +zero (not missing). Parsing into the wrong type fails loudly rather than +silently leaving a column as strings. +""" + +from __future__ import annotations + +import csv +import io +import os +from datetime import date, datetime +from enum import Enum + +import pandas as pd +import pyarrow as pa + +# Strict-decode first, latin-1 last because it never raises (guaranteed fallback). +ENCODING_LADDER: tuple[str, ...] = ("utf-8-sig", "cp1252", "iso-8859-15", "latin-1") + +GERMAN_DATE_FORMAT = "%d.%m.%Y" + +# German statistical value markers (Destatis / Genesis convention). +ZERO_MARKERS: frozenset[str] = frozenset({"-"}) # exactly zero, never null +NULL_MARKERS: frozenset[str] = frozenset({".", "...", "/", "x", "()", ""}) # unknown / blocked + + +class ColumnType(str, Enum): + STRING = "string" + INTEGER = "integer" + FLOAT = "float" + DATE = "date" + + +_ARROW_TYPE: dict[ColumnType, pa.DataType] = { + ColumnType.STRING: pa.string(), + ColumnType.INTEGER: pa.int64(), + ColumnType.FLOAT: pa.float64(), + ColumnType.DATE: pa.date32(), +} + + +def detect_encoding(data: bytes, ladder: tuple[str, ...] = ENCODING_LADDER) -> str: + """First encoding in the ladder that decodes the bytes without error.""" + for enc in ladder: + try: + data.decode(enc) + return enc + except UnicodeDecodeError: + continue + return ladder[-1] + + +def _clean_number(raw: str) -> str | None: + """Apply value markers, then de-Germanize the number, or None for null markers.""" + value = raw.strip() + if value in ZERO_MARKERS: + return "0" + if value in NULL_MARKERS: + return None + # German formatting: '.' is the thousands separator, ',' the decimal mark. + return value.replace(".", "").replace(",", ".") + + +def _to_int(raw: str) -> int | None: + cleaned = _clean_number(raw) + if cleaned is None: + return None + try: + # int() directly, never through float, so large values are not rounded. + return int(cleaned) + except ValueError: + raise ValueError(f"expected an integer, got {raw!r}") from None + + +def _to_float(raw: str) -> float | None: + cleaned = _clean_number(raw) + if cleaned is None: + return None + return float(cleaned) + + +def _to_date(raw: str) -> date | None: + value = raw.strip() + if value in NULL_MARKERS: + return None + return datetime.strptime(value, GERMAN_DATE_FORMAT).date() + + +def _to_string(raw: str) -> str | None: + value = raw.strip() + return None if value == "" else value + + +def _typed_table(cells: dict[str, list[str]], spec: dict[str, ColumnType]) -> pa.Table: + """Build a typed Arrow table from raw string columns and a name to type spec.""" + arrays: dict[str, pa.Array] = {} + fields: list[pa.Field] = [] + for name, ctype in spec.items(): + if name not in cells: + raise KeyError(f"column {name!r} not found; available: {list(cells)}") + raw = cells[name] + if ctype is ColumnType.STRING: + values: list[object] = [_to_string(v) for v in raw] + elif ctype is ColumnType.INTEGER: + values = [_to_int(v) for v in raw] + elif ctype is ColumnType.FLOAT: + values = [_to_float(v) for v in raw] + else: + values = [_to_date(v) for v in raw] + arrays[name] = pa.array(values, type=_ARROW_TYPE[ctype]) + fields.append(pa.field(name, _ARROW_TYPE[ctype])) + return pa.table(arrays, schema=pa.schema(fields)) + + +def parse_german_csv_bytes( + data: bytes, + columns: dict[str, ColumnType] | None = None, + *, + sep: str = ";", + skiprows: int = 0, + encoding: str | None = None, +) -> pa.Table: + """Parse single-header German-CSV ``data`` into a typed Arrow table. + + ``columns`` maps column name to type; when omitted every column is read as a + string. Unknown column names in ``columns`` raise ``KeyError``. + """ + enc = encoding or detect_encoding(data) + frame = pd.read_csv( + io.BytesIO(data), + sep=sep, + dtype=str, + keep_default_na=False, + na_values=[], + skiprows=skiprows, + encoding=enc, + ) + frame.columns = pd.Index([str(name).strip() for name in frame.columns]) + cells: dict[str, list[str]] = { + col: ["" if v is None else str(v) for v in frame[col].tolist()] for col in frame.columns + } + row_count = len(frame.index) + + # Drop fully-empty trailing rows (a trailing newline can yield one). + keep = [i for i in range(row_count) if any(cells[col][i].strip() != "" for col in cells)] + filtered = {name: [col[i] for i in keep] for name, col in cells.items()} + + spec = columns if columns is not None else {name: ColumnType.STRING for name in filtered} + return _typed_table(filtered, spec) + + +def parse_german_csv( + path: str | os.PathLike[str], + columns: dict[str, ColumnType] | None = None, + *, + sep: str = ";", + skiprows: int = 0, + encoding: str | None = None, +) -> pa.Table: + """Read ``path`` and parse it with :func:`parse_german_csv_bytes`.""" + with open(path, "rb") as handle: + data = handle.read() + return parse_german_csv_bytes(data, columns, sep=sep, skiprows=skiprows, encoding=encoding) + + +def _ffill_header_row(row: list[str]) -> list[str]: + """Forward-fill a header row so merged (empty) cells inherit the value to their left.""" + out: list[str] = [] + last = "" + for cell in row: + text = cell.strip() + if text: + last = text + out.append(last) + return out + + +def _flatten_header(header_rows: list[list[str]]) -> list[str]: + """Combine multi-row merged headers into one unique name per column. + + Trailing columns with no header cell in any row are dropped, so a universal + trailing delimiter does not add a phantom all-null column. + """ + ncols = 0 + for row in header_rows: + for j, cell in enumerate(row): + if cell.strip(): + ncols = max(ncols, j + 1) + # Forward-fill merged cells within each row, then pad/truncate to ncols so a + # writer's omitted trailing cells do not inherit a label. + filled = [(_ffill_header_row(row) + [""] * ncols)[:ncols] for row in header_rows] + names: list[str] = [] + used: set[str] = set() + for j in range(ncols): + parts: list[str] = [] + for row in filled: + part = row[j] + if part and (not parts or parts[-1] != part): + parts.append(part) + base = " ".join(parts) if parts else f"column_{j}" + name = base + suffix = 1 + while name in used: # guarantee uniqueness even against pre-suffixed names + suffix += 1 + name = f"{base} ({suffix})" + used.add(name) + names.append(name) + return names + + +def parse_multi_header_csv_bytes( + data: bytes, + *, + sep: str = ";", + skiprows: int = 0, + header_rows: int = 1, + label_columns: int = 0, + value_type: ColumnType = ColumnType.INTEGER, + encoding: str | None = None, +) -> pa.Table: + """Parse a publisher CSV with a multi-row merged header into a typed Arrow table. + + Skips ``skiprows`` preamble lines, flattens the next ``header_rows`` rows into + unique column names, drops blank/separator rows, and types the first + ``label_columns`` columns as strings with the rest as ``value_type``. Parsing + goes through the csv module, so quoted fields and line endings are handled. + """ + enc = encoding or detect_encoding(data) + rows = list(csv.reader(io.StringIO(data.decode(enc)), delimiter=sep)) + header_block = rows[skiprows : skiprows + header_rows] + if header_rows < 1 or len(header_block) < header_rows: + raise ValueError("not enough header rows for the requested header_rows") + names = _flatten_header(header_block) + ncols = len(names) + + cells: dict[str, list[str]] = {name: [] for name in names} + for row in rows[skiprows + header_rows :]: + if not any(cell.strip() for cell in row): + continue # blank or separator row (e.g. a lone ';') + fields = (row + [""] * ncols)[:ncols] + for name, value in zip(names, fields): + cells[name].append(value) + + spec = {name: (ColumnType.STRING if i < label_columns else value_type) for i, name in enumerate(names)} + return _typed_table(cells, spec) + + +def parse_multi_header_csv( + path: str | os.PathLike[str], + *, + sep: str = ";", + skiprows: int = 0, + header_rows: int = 1, + label_columns: int = 0, + value_type: ColumnType = ColumnType.INTEGER, + encoding: str | None = None, +) -> pa.Table: + """Read ``path`` and parse it with :func:`parse_multi_header_csv_bytes`.""" + with open(path, "rb") as handle: + data = handle.read() + return parse_multi_header_csv_bytes( + data, + sep=sep, + skiprows=skiprows, + header_rows=header_rows, + label_columns=label_columns, + value_type=value_type, + encoding=encoding, + ) diff --git a/mloda_plugin_govdata/feature_groups/govdata/reader.py b/mloda_plugin_govdata/feature_groups/govdata/reader.py new file mode 100644 index 0000000..82a7bfb --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/reader.py @@ -0,0 +1,117 @@ +"""mloda reader and FeatureGroup for GovData distributions. + +``GovDataReader`` follows the ``CsvReader`` pattern (a ``ReadFile`` subclass): +it resolves a locator to a distribution, downloads it through the cache, and +parses it into a typed Arrow table. ``GovDataFeature`` exposes it as a root +FeatureGroup on the PyArrow compute framework. +""" + +from __future__ import annotations + +import tempfile +from pathlib import Path +from typing import Any, ClassVar + +import pyarrow as pa + +from mloda.provider import BaseInputData, FeatureSet + +# Imported for its registration side effect so "PyArrowTable" resolves; the reader returns a pyarrow Table. +from mloda_plugins.compute_framework.base_implementations.pyarrow.table import PyArrowTable # noqa: F401 +from mloda_plugins.feature_group.input_data.read_file import ReadFile +from mloda_plugins.feature_group.input_data.read_file_feature import ReadFileFeature + +from .cache import DownloadCache +from .client import build_client +from .discovery import ResolvedDistribution, resolve_distribution +from .locator import GovDataLocator +from .parse import ColumnType, parse_german_csv, parse_multi_header_csv +from .uba import parse_uba_measures + +# The M1 population dataset (Stuttgart, via GovData) and its known typed schema. +POPULATION_SLUG = "einwohner-nach-altersgruppen-und-stadtbezirken" +POPULATION_URL_MARKER = "einwohner-in-stuttgart-nach-altersgruppen" +POPULATION_SCHEMA: dict[str, ColumnType] = { + "Stichtag": ColumnType.DATE, + "Stadtbezirk": ColumnType.STRING, + "Alter in 10 Gruppen": ColumnType.STRING, + "Einwohner": ColumnType.INTEGER, +} + + +class GovDataReader(ReadFile): + """Reads a GovData CSV distribution into a typed Arrow table.""" + + # Persistent, content-addressed cache location (override per environment). + cache_dir: ClassVar[str] = str(Path(tempfile.gettempdir()) / "mloda-govdata-cache") + + @classmethod + def suffix(cls) -> tuple[str, ...]: + return (".csv",) + + @classmethod + def match_subclass_data_access(cls, data_access: Any, feature_names: list[str], options: Any) -> Any: + return GovDataLocator.coerce(data_access) + + @classmethod + def load_data(cls, data_access: Any, features: FeatureSet) -> Any: + # Touch features first: the framework probes load_data(None, None) and + # only tolerates AttributeError to detect scoped-access support. + requested = sorted(features.get_all_names()) # deterministic column order + locator = GovDataLocator.coerce(data_access) + if locator is None: + raise ValueError(f"GovDataReader cannot handle data access {data_access!r}") + table = cls._read_table(locator) + return table.select(requested) + + @classmethod + def _read_table(cls, locator: GovDataLocator) -> pa.Table: + with build_client() as client: + distribution = resolve_distribution(locator, client) + cache = DownloadCache(cls.cache_dir, client=client) + cached = cache.get_or_download(distribution.url) + return cls._parse(cached.path, locator, distribution) + + @classmethod + def _parse(cls, path: Path, locator: GovDataLocator, distribution: ResolvedDistribution) -> pa.Table: + return parse_german_csv(path, cls._schema_for(locator, distribution)) + + @staticmethod + def _schema_for(locator: GovDataLocator, distribution: ResolvedDistribution) -> dict[str, ColumnType] | None: + if locator.dataset_id == POPULATION_SLUG or POPULATION_URL_MARKER in distribution.url: + return POPULATION_SCHEMA + return None # unknown dataset: read every column as a string + + +class BundeswahlleiterinReader(GovDataReader): + """Reads the Bundeswahlleiterin kerg.csv result file (5-line preamble, 3-row merged header).""" + + @classmethod + def _parse(cls, path: Path, locator: GovDataLocator, distribution: ResolvedDistribution) -> pa.Table: + return parse_multi_header_csv(path, skiprows=5, header_rows=3, label_columns=4, value_type=ColumnType.INTEGER) + + +class UbaAirReader(GovDataReader): + """Reads the UBA Air Data v4 ``measures`` JSON endpoint into a typed Arrow table. + + The option value is a full ``measures`` URL (build one with + :func:`~mloda_plugin_govdata.feature_groups.govdata.uba.uba_measures_url`); the response + is flattened to one row per station and measurement timestamp. Reuses the client, cache, + retry, and direct-URL resolution; only the parse seam differs from the CSV readers. + """ + + @classmethod + def suffix(cls) -> tuple[str, ...]: + return (".json",) + + @classmethod + def _parse(cls, path: Path, locator: GovDataLocator, distribution: ResolvedDistribution) -> pa.Table: + return parse_uba_measures(path) + + +class GovDataFeature(ReadFileFeature): + """Root FeatureGroup for GovData columns; inherits the any-framework rule to avoid resolver collisions.""" + + @classmethod + def input_data(cls) -> BaseInputData | None: + return GovDataReader() diff --git a/mloda_plugin_govdata/feature_groups/govdata/tests/__init__.py b/mloda_plugin_govdata/feature_groups/govdata/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mloda_plugin_govdata/feature_groups/govdata/tests/conftest.py b/mloda_plugin_govdata/feature_groups/govdata/tests/conftest.py new file mode 100644 index 0000000..74d8146 --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/tests/conftest.py @@ -0,0 +1,8 @@ +from pathlib import Path + +import pytest + + +@pytest.fixture +def fixtures_dir() -> Path: + return Path(__file__).parent / "fixtures" diff --git a/mloda_plugin_govdata/feature_groups/govdata/tests/fixtures/kerg_sample.csv b/mloda_plugin_govdata/feature_groups/govdata/tests/fixtures/kerg_sample.csv new file mode 100644 index 0000000..ede889d --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/tests/fixtures/kerg_sample.csv @@ -0,0 +1,25 @@ +(c) Die Bundeswahlleiterin, Wiesbaden 2025; +Datenlizenz Deutschland – Namensnennung – Version 2.0 (https://www.govdata.de/dl-de/by-2-0); +; +Bundestagswahl 2025; +Amtliches Endergebnis; +Nr;Gebiet;gehört zu;Gewählt;Wahlberechtigte;;;;Wählende;;;;Ungültige Stimmen;;;;Gültige Stimmen;;;;Sozialdemokratische Partei Deutschlands;;;;Christlich Demokratische Union Deutschlands;;;;BÜNDNIS 90/DIE GRÜNEN;;;;Freie Demokratische Partei;;;;Alternative für Deutschland;;;;Christlich-Soziale Union in Bayern e.V.;;;;Die Linke;;;;FREIE WÄHLER;;;;PARTEI MENSCH UMWELT TIERSCHUTZ;;;;Basisdemokratische Partei Deutschland;;;;Partei für Arbeit, Rechtsstaat, Tierschutz, Elitenförderung und basisdemokratische Initiative;;;;Die Gerechtigkeitspartei – Team Todenhöfer;;;;Piratenpartei Deutschland;;;;Volt Deutschland;;;;Ökologisch-Demokratische Partei - Die Naturschutzpartei;;;;Südschleswigscher Wählerverband;;;;Partei für Verjüngungsforschung;;;;Partei der Humanisten - Fakten, Freiheit, Fortschritt;;;;Bündnis C - Christen für Deutschland;;;;Bayernpartei;;;;Marxistisch-Leninistische Partei Deutschlands;;;;Menschliche Welt - für das Wohl und Glücklichsein aller;;;;Partei des Fortschritts;;;;Sozialistische Gleichheitspartei, Vierte Internationale;;;;Bürgerrechtsbewegung Solidarität;;;;BÜNDNIS DEUTSCHLAND;;;;Bündnis Sahra Wagenknecht - Vernunft und Gerechtigkeit;;;;MERA25 - Gemeinsam für Europäische Unabhängigkeit;;;;WerteUnion;;;;Übrige;;;; +;;;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;;Erststimmen;;Zweitstimmen;; +;;;;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode;Endgültig;Vorperiode; +001;Flensburg – Schleswig;01;–;232131;231536;232131;231536;192882;180112;192882;180112;1097;1537;988;1487;191785;178575;191894;178625;28376;38927;30558;45508;50838;41721;47686;36421;43298;50231;30595;33300;4487;12299;7067;19189;28827;9768;29272;10317;;;;;10375;6544;15912;7475;1547;2324;1137;1715;;;;1990;;3357;;2866;;;1212;1844;;;;318;;;;;1452;;1449;403;;;;132;21468;13020;20575;16415;;;;;;;;157;;;;;;;;;;;53;12;;;;;;;;;;;;;;;;;646;;328;;;;6050;;;;;;;;;;471;384;;563; +002;Nordfriesland – Dithmarschen Nord;01;Christlich Demokratische Union Deutschlands;186019;188267;186019;188267;153755;145387;153755;145387;1038;1337;840;1242;152717;144050;152915;144145;31169;40026;26639;37926;49866;43745;45302;35511;16628;20611;18764;22588;5750;13958;7618;18350;24474;8274;24564;8798;;;;;7920;4060;9442;4437;1484;1510;1099;1081;;;;1604;;2254;;1836;;;1067;1322;;;;239;;;;;1172;;1023;237;;;;121;14254;9410;12264;9516;;;;;;;;130;;;;;;;;;;;48;26;;;;;;;;;;;;;;;;;;;178;;;;4907;;;;;;;;;;;202;;423; +003;Steinburg – Dithmarschen Süd;01;Christlich Demokratische Union Deutschlands;175948;176899;175948;176899;146393;136299;146393;136299;1146;1319;801;1190;145247;134980;145592;135109;30702;39379;25602;37468;50887;39431;42561;32842;14449;16686;16071;19237;5625;19315;7435;18492;29674;10429;29653;11303;;;;;8665;4339;9656;4524;3111;2566;1542;1713;;;;1719;;2040;;1712;;;1213;1346;;;;283;;;;;2134;;1229;226;;;;105;;;4992;3366;;;;;;;;118;;;;;;;;;;;45;25;;;;;;;;;;;;;;;;;;;236;;;;5357;;;;;;;;;;;795;;630; +004;Rendsburg-Eckernförde;01;Christlich Demokratische Union Deutschlands;202649;202226;202649;202226;172504;162060;172504;162060;1137;1410;858;1105;171367;160650;171646;160955;36266;49474;30418;43148;56163;47688;50231;38517;20473;23832;25226;28828;8170;12903;7956;19322;26417;10200;27498;10564;;;;;8675;4416;10890;5163;1367;1651;992;1174;;;;1728;;2502;;2375;;2150;1161;1618;;;;244;;;;;1327;;974;253;;;;122;11075;5834;10497;7304;;;;;;;;136;;;;;;;;;;;42;33;;;;;;;;;;;;;;;;;632;;298;;;;5463;;;;;;;;;;802;0;;426; +005;Kiel;01;BÜNDNIS 90/DIE GRÜNEN;201508;202482;201508;202482;167176;155986;167176;155986;980;1170;867;918;166196;154816;166309;155068;36690;45709;32088;40338;34944;28416;32842;23920;43281;43532;38148;44089;4828;11445;6567;16110;17811;7147;17884;7654;;;;;15953;7275;22781;9430;777;1144;575;954;;;;1676;;2009;;1924;2021;2944;1457;2185;;;;747;;;;;2526;666;1927;556;;;;116;7365;4141;6343;4486;;;;;;;;299;;;;;;;;;;76;61;39;;;;;;;;;;;;;;;;;;;185;;;;5451;;;;;;;;;;;312;;545; +006;Plön – Neumünster;01;Christlich Demokratische Union Deutschlands;173740;174908;173740;174908;143576;133721;143576;133721;1863;1759;921;1194;141713;131962;142655;132527;33977;41497;27342;38670;46370;36870;40815;30290;18852;20740;19957;23051;4693;12420;6150;15901;25249;9404;25683;9741;;;;;7623;3718;9657;4232;3022;2386;1160;1275;;;;1446;;2508;;2007;;2196;992;1300;;;;415;;;;;1927;;1117;230;;;;94;;;4523;3203;;;;;;;;111;;;;;;;;;;;40;23;;;;;;;;;;;;;;;;;;;177;;;;5042;;;;;;;;;;;223;;538; +007;Pinneberg;01;Christlich Demokratische Union Deutschlands;236767;238388;236767;238388;200304;191512;200304;191512;1202;1443;985;1242;199102;190069;199319;190270;49714;59298;40326;55693;63232;49710;57531;42057;25395;32109;28967;34291;7675;20737;10087;25663;30417;12141;31098;13080;;;;;12683;5659;15091;6363;2190;2380;1313;1731;;;;2320;;2895;;2389;;2352;1360;1805;;;;799;;;;;2423;;2016;457;;;;202;4617;2622;3976;2527;;;;;;;;182;;;;;;;;;;;47;21;;;;;;;;;;;;;;;;;756;;385;;;;7122;;;;;;;;;;;166;;690; +008;Segeberg – Stormarn-Mitte;01;Christlich Demokratische Union Deutschlands;249219;249202;249219;249202;211551;199618;211551;199618;1504;1700;1042;1353;210047;197918;210509;198265;51437;63369;40174;56658;67880;55265;61180;44285;24800;27109;28808;33361;9417;20575;10974;27675;36071;14040;36208;14527;;;;;12636;6202;14888;6468;3261;4670;1722;2297;;;;2663;;3389;;2839;;3299;1692;2226;;;;586;;;;;3352;;2166;509;;;;194;;;4409;3108;;;;;;;;186;;;;;;;;;;;51;30;;;;;;;;;;;;;;;;;1193;;465;;;;7772;;;;;;;;;;;0;;653; +009;Ostholstein – Stormarn-Nord;01;Christlich Demokratische Union Deutschlands;181112;183138;181112;183138;150786;143092;150786;143092;1114;1301;791;1126;149672;141791;149995;141966;38070;47714;29511;42520;52024;42991;46722;35564;15636;19025;19093;22314;5423;13289;7656;17986;25929;9647;26495;10073;;;;;8232;4122;8935;4036;2636;2582;1355;1603;;;;1729;;2032;;1691;;;1064;1104;;;;182;;;;;1722;;1091;289;;;;102;;;3167;2241;;;;;;;;88;;;;;;;;;;;42;13;;;;;;;;;;;;;;;;;;;222;;;;4642;;;;;;;;;;;389;;431; +010;Herzogtum Lauenburg – Stormarn-Süd;01;Christlich Demokratische Union Deutschlands;245473;246277;245473;246277;208042;196903;208042;196903;1411;1576;1041;1316;206631;195327;207001;195587;48275;60593;39862;56422;67642;51872;61517;45364;29009;33949;28850;32475;7445;20550;11090;27725;35418;14053;35556;14682;;;;;12161;5168;14255;6325;3784;3517;1774;2204;;;;2283;;2544;;2237;;2956;1364;1791;;;;829;;;;;2897;;1975;447;;;;217;;;2728;1746;;;;;;;;160;;;;;;;;;;;57;29;;;;;;;;;;;;;;;;;;;309;;;;7664;;;;;;;;;;;125;;651; +011;Lübeck;01;Sozialdemokratische Partei Deutschlands;178245;179394;178245;179394;142999;131455;142999;131455;1247;1580;829;1218;141752;129875;142170;130237;39809;44315;30026;39704;33695;28266;32037;23628;27477;27809;25444;29229;4028;9700;5547;13626;21972;8538;22254;8827;;;;;10175;4020;14921;5785;1960;1788;1006;1296;;;;1495;;1594;;1531;;2193;1147;1329;;;;663;;;;;2350;765;1529;550;;;;134;;;2664;1666;;;;;;;;196;;;;;;;;;286;189;90;71;;;;;;;;;;;;;;;;;;;198;;;;5307;;;;;;;;;;;698;;507; +01;Schleswig-Holstein;99;;2262811;2272717;2262811;2272717;1889968;1776145;1889968;1776145;13739;16132;9963;13391;1876229;1760013;1880005;1762754;424485;530301;352546;494055;573541;465975;518424;388399;279298;315633;279923;322763;67541;167191;88147;220039;302259;113641;306165;119566;;;;;115098;55523;146428;64238;25139;26518;13675;17043;;;;20653;;27124;;23407;2021;18090;13729;17870;;;;5305;;;;;23282;1431;16496;4157;;;;1539;58779;35027;76138;55578;;;;;;;;1763;;;;;;;;;286;265;576;322;;;;;;;;;;;;;;;;;3227;;2981;;;;64777;;;;;;;;;;1273;3294;;6057; +; +012;Schwerin – Ludwigslust-Parchim I – Nordwestmecklenburg I;13;Alternative für Deutschland;212978;214853;212978;214853;171841;156565;171841;156565;2083;2957;1417;2402;169758;153608;170424;154163;34100;45189;24693;49271;35455;31754;32209;26383;5532;12061;9495;12419;4870;11769;5613;13242;60865;25393;54975;24185;;;;;21570;17906;19560;16604;4753;3552;1977;2476;;;2310;2645;;2417;;2207;;1910;;1252;;;;399;;1414;;1023;;;1050;381;;;;151;;;;;;;;;;;;175;;;;;;;;;661;243;183;117;;;;;;;;;;;;;;;;;1952;;706;;;;17653;;;;;;;;;;;0;;1233; +013;Ludwigslust-Parchim II – Nordwestmecklenburg II – Landkreis Rostock I;13;Alternative für Deutschland;202532;205042;202532;205042;162855;146362;162855;146362;1624;2316;1131;2258;161231;144046;161724;144104;34657;50736;23010;46846;32832;26208;29804;24621;5748;8163;7744;10147;4422;10322;4921;11638;59419;24760;56238;24673;;;;;18099;14949;17342;14979;6054;3531;1880;2384;;;2233;2562;;2796;;2610;;;;1019;;;;332;;;;624;;;997;254;;;;195;;;;;;;;;;;;157;;;;;;;;;;;95;65;;;;;;;;;;;;;;;;;;;385;;;;17075;;;;;;;;;;;2581;;998; +014;Rostock – Landkreis Rostock II;13;–;221164;222705;221164;222705;179109;165329;179109;165329;1541;2319;1089;2352;177568;163010;178020;162977;30034;43932;25368;47280;34403;27672;29910;23103;9400;16556;15929;20941;5037;12242;6057;14852;47597;19882;45612;20658;;;;;45518;29715;30743;21491;3431;2131;1502;1871;;4476;2784;3935;;4744;;3488;;;;2036;;;;408;;1304;;854;;;1702;501;;;;274;;;;;;;;;;;;290;;;;;;;;;623;356;240;152;;;;;;;;;;;;;;;;;1525;;549;;;;17624;;;;;;;;;;;0;;843; +015;Vorpommern-Rügen – Vorpommern-Greifswald I;13;Alternative für Deutschland;237125;241066;237125;241066;186435;168922;186435;168922;1771;2769;1350;3143;184664;166153;185085;165779;26885;40429;19150;41583;39237;33903;32674;30910;9066;12199;11467;14654;4814;11005;6205;13776;68848;33025;65651;31303;;;;;24633;22767;23214;18857;4982;1835;2130;1975;6199;5083;3844;5169;;2805;;2904;;1782;;1576;;;;392;;1094;;797;;;1372;354;;;;201;;;;;;;;;;;;295;;;;;;;;;;226;134;123;;;;;;;;;;;;;;;;;;;471;;;;18773;;;;;;;;;;;0;;910; diff --git a/mloda_plugin_govdata/feature_groups/govdata/tests/fixtures/package_show.json b/mloda_plugin_govdata/feature_groups/govdata/tests/fixtures/package_show.json new file mode 100644 index 0000000..3f1a572 --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/tests/fixtures/package_show.json @@ -0,0 +1 @@ +{"help": "https://ckan.govdata.de/api/3/action/help_show?name=package_show", "success": true, "result": {"author": "Bev\u00f6lkerung und Bildung (12-32)", "author_email": "Poststelle.12-opendata@stuttgart.de", "creator_user_id": "f512f73d-bed2-439d-8f6f-3fbc6048b24e", "id": "2c2694be-eee2-4bb7-a3c5-44445e1d778c", "isopen": false, "license_id": "", "license_title": "", "maintainer": "Statistisches Amt der Landeshauptstadt Stuttgart", "maintainer_email": "Poststelle.12-opendata@stuttgart.de", "metadata_created": "2025-07-18T02:05:52.278036", "metadata_modified": "2026-06-28T23:52:12.415828", "name": "einwohner-nach-altersgruppen-und-stadtbezirken", "notes": "Einwohner in Stuttgart seit 1995 nach Altersgruppen und Stadtbezirken", "num_resources": 1, "num_tags": 1, "organization": {"id": "969c87e5-7ea0-4829-bad2-ea1ad94e0c93", "name": "open-data-baden-wurttemberg", "title": "Open Data Baden-W\u00fcrttemberg", "type": "organization", "description": "", "image_url": "", "created": "2023-08-07T16:13:41.131036", "is_organization": true, "approval_status": "approved", "state": "active"}, "owner_org": "969c87e5-7ea0-4829-bad2-ea1ad94e0c93", "private": false, "state": "active", "title": "Einwohner nach Altersgruppen und Stadtbezirken", "type": "dataset", "url": "Einwohnerstatistik", "version": null, "extras": [{"key": "author_contacttype", "value": "Organization"}, {"key": "contact_email", "value": "Poststelle.12-opendata@stuttgart.de"}, {"key": "contact_name", "value": "Statistisches Amt der Landeshauptstadt Stuttgart"}, {"key": "contributorID", "value": "[\"http://dcat-ap.de/def/contributors/datenBW\"]"}, {"key": "creator_email", "value": "Poststelle.12-opendata@stuttgart.de"}, {"key": "creator_name", "value": "Bev\u00f6lkerung und Bildung (12-32)"}, {"key": "guid", "value": "https://opendata.stuttgart.de/dataset/85a06c70-d65e-46fd-861b-2de52c008579"}, {"key": "identifier", "value": "85a06c70-d65e-46fd-861b-2de52c008579"}, {"key": "issued", "value": "2023-06-22T07:58:34.985363"}, {"key": "maintainer_contacttype", "value": "Organization"}, {"key": "metadata_harvested_portal", "value": "https://www.daten-bw.de/"}, {"key": "metadata_original_html", "value": "Einwohnerstatistik"}, {"key": "modified", "value": "2024-07-19T08:39:49.359767"}, {"key": "publisher_name", "value": "Statistisches Amt der Landeshauptstadt Stuttgart"}, {"key": "publisher_uri", "value": "https://opendata.stuttgart.de/organization/cff29f70-5b6d-4fb9-aa8f-0f1ae66634e8"}, {"key": "theme", "value": "[\"http://publications.europa.eu/resource/authority/data-theme/SOCI\"]"}, {"key": "uri", "value": "https://opendata.stuttgart.de/dataset/85a06c70-d65e-46fd-861b-2de52c008579"}], "groups": [{"description": "", "display_name": "Bev\u00f6lkerung und Gesellschaft", "id": "soci", "image_display_url": "", "name": "soci", "title": "Bev\u00f6lkerung und Gesellschaft"}], "resources": [{"access_url": "https://opendata.stuttgart.de/dataset/85a06c70-d65e-46fd-861b-2de52c008579/resource/2842140d-8787-475d-b1b9-35a496b503e3/download/einwohner-in-stuttgart-nach-altersgruppen-und-stadtbezirken-seit-1986.csv", "cache_last_updated": null, "cache_url": null, "created": "2025-07-18T02:05:52.281707", "description": "### Erl\u00e4uterungen\r\nNachgewiesen werden: Einwohner \r\nGez\u00e4hlt werden alle Personen, die der Meldepflicht unterliegen und die mit alleiniger Wohnung bzw. mit Hauptwohnung in der Gemeinde gemeldet sind (\"Bev\u00f6lkerung am Ort der Hauptwohnung\").\r\n### Periodizit\u00e4t\r\nDie Statistik wird j\u00e4hrlich zum 31.12. erstellt und steht ab 28./29.02 des Folgejahres zur Verf\u00fcgung. \r\n### Rechtsgrundlage\r\n- Bundesmeldegesetz (BMG) vom 03.05.2013.\r\n- Satzung \u00fcber die regelm\u00e4\u00dfige Weitergabe von Daten an die kommunale Statistikstelle aus dem Gesch\u00e4ftsgang anderer Verwaltungsstellen der Landeshauptstadt Stuttgart (Kommunalstatistiksatzung) vom 27. Mai 1993\r\n### Raumbezug\r\nStadtbezirke\r\n### Quelle\r\nEinwohnerstatistik", "distribution_ref": "https://opendata.stuttgart.de/dataset/85a06c70-d65e-46fd-861b-2de52c008579/resource/2842140d-8787-475d-b1b9-35a496b503e3", "format": "CSV", "hash": "", "id": "17533b2a-4881-48c5-af83-4c8c17fbc828", "issued": "2023-06-26T06:28:32.682031", "last_modified": null, "license": "http://dcat-ap.de/def/licenses/cc-by/4.0", "metadata_modified": "2025-07-18T02:05:52.272939", "mimetype": "text/csv", "mimetype_inner": null, "modified": "2023-06-26T06:28:32.647720", "name": "Einwohner in Stuttgart nach Altersgruppen und Stadtbezirken seit 1986", "package_id": "2c2694be-eee2-4bb7-a3c5-44445e1d778c", "position": 0, "resource_type": null, "size": 1219018, "state": "active", "uri": "https://opendata.stuttgart.de/dataset/85a06c70-d65e-46fd-861b-2de52c008579/resource/2842140d-8787-475d-b1b9-35a496b503e3", "url": "https://opendata.stuttgart.de/dataset/85a06c70-d65e-46fd-861b-2de52c008579/resource/2842140d-8787-475d-b1b9-35a496b503e3/download/einwohner-in-stuttgart-nach-altersgruppen-und-stadtbezirken-seit-1986.csv", "url_type": null}], "tags": [{"display_name": "einwohner", "id": "bccaf67b-933a-403a-8a00-34a7d88e79d4", "name": "einwohner", "state": "active", "vocabulary_id": null}], "relationships_as_subject": [], "relationships_as_object": []}} \ No newline at end of file diff --git a/mloda_plugin_govdata/feature_groups/govdata/tests/fixtures/population_sample.csv b/mloda_plugin_govdata/feature_groups/govdata/tests/fixtures/population_sample.csv new file mode 100644 index 0000000..ecc7b96 --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/tests/fixtures/population_sample.csv @@ -0,0 +1,1001 @@ +Stichtag;Stadtbezirk;Alter in 10 Gruppen;Einwohner +30.06.1986;Mitte;0 bis unter 3 Jahre;494 +30.06.1986;Mitte;3 bis unter 6 Jahre;483 +30.06.1986;Mitte;6 bis unter 15 Jahre;1367 +30.06.1986;Mitte;15 bis unter 18 Jahre;645 +30.06.1986;Mitte;18 bis unter 30 Jahre;5325 +30.06.1986;Mitte;30 bis unter 45 Jahre;5554 +30.06.1986;Mitte;45 bis unter 60 Jahre;4782 +30.06.1986;Mitte;60 bis unter 65 Jahre;1048 +30.06.1986;Mitte;65 bis unter 75 Jahre;1477 +30.06.1986;Mitte;75 Jahre oder �lter;1318 +30.06.1986;Nord;0 bis unter 3 Jahre;509 +30.06.1986;Nord;3 bis unter 6 Jahre;470 +30.06.1986;Nord;6 bis unter 15 Jahre;1503 +30.06.1986;Nord;15 bis unter 18 Jahre;779 +30.06.1986;Nord;18 bis unter 30 Jahre;4600 +30.06.1986;Nord;30 bis unter 45 Jahre;4833 +30.06.1986;Nord;45 bis unter 60 Jahre;5213 +30.06.1986;Nord;60 bis unter 65 Jahre;1307 +30.06.1986;Nord;65 bis unter 75 Jahre;2245 +30.06.1986;Nord;75 Jahre oder �lter;2032 +30.06.1986;Ost;0 bis unter 3 Jahre;1214 +30.06.1986;Ost;3 bis unter 6 Jahre;1128 +30.06.1986;Ost;6 bis unter 15 Jahre;3211 +30.06.1986;Ost;15 bis unter 18 Jahre;1374 +30.06.1986;Ost;18 bis unter 30 Jahre;9763 +30.06.1986;Ost;30 bis unter 45 Jahre;9952 +30.06.1986;Ost;45 bis unter 60 Jahre;9296 +30.06.1986;Ost;60 bis unter 65 Jahre;2428 +30.06.1986;Ost;65 bis unter 75 Jahre;4279 +30.06.1986;Ost;75 Jahre oder �lter;4008 +30.06.1986;S�d;0 bis unter 3 Jahre;1113 +30.06.1986;S�d;3 bis unter 6 Jahre;1067 +30.06.1986;S�d;6 bis unter 15 Jahre;2971 +30.06.1986;S�d;15 bis unter 18 Jahre;1291 +30.06.1986;S�d;18 bis unter 30 Jahre;9125 +30.06.1986;S�d;30 bis unter 45 Jahre;9940 +30.06.1986;S�d;45 bis unter 60 Jahre;8439 +30.06.1986;S�d;60 bis unter 65 Jahre;1945 +30.06.1986;S�d;65 bis unter 75 Jahre;3442 +30.06.1986;S�d;75 Jahre oder �lter;3370 +30.06.1986;West;0 bis unter 3 Jahre;1249 +30.06.1986;West;3 bis unter 6 Jahre;1134 +30.06.1986;West;6 bis unter 15 Jahre;3109 +30.06.1986;West;15 bis unter 18 Jahre;1416 +30.06.1986;West;18 bis unter 30 Jahre;11213 +30.06.1986;West;30 bis unter 45 Jahre;11664 +30.06.1986;West;45 bis unter 60 Jahre;9799 +30.06.1986;West;60 bis unter 65 Jahre;2513 +30.06.1986;West;65 bis unter 75 Jahre;4491 +30.06.1986;West;75 Jahre oder �lter;4503 +30.06.1986;Bad Cannstatt;0 bis unter 3 Jahre;1552 +30.06.1986;Bad Cannstatt;3 bis unter 6 Jahre;1590 +30.06.1986;Bad Cannstatt;6 bis unter 15 Jahre;4628 +30.06.1986;Bad Cannstatt;15 bis unter 18 Jahre;2009 +30.06.1986;Bad Cannstatt;18 bis unter 30 Jahre;11755 +30.06.1986;Bad Cannstatt;30 bis unter 45 Jahre;12447 +30.06.1986;Bad Cannstatt;45 bis unter 60 Jahre;13219 +30.06.1986;Bad Cannstatt;60 bis unter 65 Jahre;3079 +30.06.1986;Bad Cannstatt;65 bis unter 75 Jahre;5220 +30.06.1986;Bad Cannstatt;75 Jahre oder �lter;4609 +30.06.1986;Birkach;0 bis unter 3 Jahre;146 +30.06.1986;Birkach;3 bis unter 6 Jahre;150 +30.06.1986;Birkach;6 bis unter 15 Jahre;403 +30.06.1986;Birkach;15 bis unter 18 Jahre;188 +30.06.1986;Birkach;18 bis unter 30 Jahre;1123 +30.06.1986;Birkach;30 bis unter 45 Jahre;1194 +30.06.1986;Birkach;45 bis unter 60 Jahre;1086 +30.06.1986;Birkach;60 bis unter 65 Jahre;327 +30.06.1986;Birkach;65 bis unter 75 Jahre;477 +30.06.1986;Birkach;75 Jahre oder �lter;447 +30.06.1986;Botnang;0 bis unter 3 Jahre;300 +30.06.1986;Botnang;3 bis unter 6 Jahre;350 +30.06.1986;Botnang;6 bis unter 15 Jahre;976 +30.06.1986;Botnang;15 bis unter 18 Jahre;459 +30.06.1986;Botnang;18 bis unter 30 Jahre;2209 +30.06.1986;Botnang;30 bis unter 45 Jahre;2839 +30.06.1986;Botnang;45 bis unter 60 Jahre;2890 +30.06.1986;Botnang;60 bis unter 65 Jahre;709 +30.06.1986;Botnang;65 bis unter 75 Jahre;1219 +30.06.1986;Botnang;75 Jahre oder �lter;1073 +30.06.1986;Degerloch;0 bis unter 3 Jahre;356 +30.06.1986;Degerloch;3 bis unter 6 Jahre;365 +30.06.1986;Degerloch;6 bis unter 15 Jahre;1078 +30.06.1986;Degerloch;15 bis unter 18 Jahre;521 +30.06.1986;Degerloch;18 bis unter 30 Jahre;2796 +30.06.1986;Degerloch;30 bis unter 45 Jahre;3291 +30.06.1986;Degerloch;45 bis unter 60 Jahre;3326 +30.06.1986;Degerloch;60 bis unter 65 Jahre;933 +30.06.1986;Degerloch;65 bis unter 75 Jahre;1816 +30.06.1986;Degerloch;75 Jahre oder �lter;2007 +30.06.1986;Feuerbach;0 bis unter 3 Jahre;678 +30.06.1986;Feuerbach;3 bis unter 6 Jahre;678 +30.06.1986;Feuerbach;6 bis unter 15 Jahre;2002 +30.06.1986;Feuerbach;15 bis unter 18 Jahre;970 +30.06.1986;Feuerbach;18 bis unter 30 Jahre;5309 +30.06.1986;Feuerbach;30 bis unter 45 Jahre;5904 +30.06.1986;Feuerbach;45 bis unter 60 Jahre;5966 +30.06.1986;Feuerbach;60 bis unter 65 Jahre;1417 +30.06.1986;Feuerbach;65 bis unter 75 Jahre;2428 +30.06.1986;Feuerbach;75 Jahre oder �lter;2176 +30.06.1986;Hedelfingen;0 bis unter 3 Jahre;243 +30.06.1986;Hedelfingen;3 bis unter 6 Jahre;236 +30.06.1986;Hedelfingen;6 bis unter 15 Jahre;706 +30.06.1986;Hedelfingen;15 bis unter 18 Jahre;294 +30.06.1986;Hedelfingen;18 bis unter 30 Jahre;1758 +30.06.1986;Hedelfingen;30 bis unter 45 Jahre;1955 +30.06.1986;Hedelfingen;45 bis unter 60 Jahre;1950 +30.06.1986;Hedelfingen;60 bis unter 65 Jahre;386 +30.06.1986;Hedelfingen;65 bis unter 75 Jahre;664 +30.06.1986;Hedelfingen;75 Jahre oder �lter;565 +30.06.1986;M�hringen;0 bis unter 3 Jahre;642 +30.06.1986;M�hringen;3 bis unter 6 Jahre;660 +30.06.1986;M�hringen;6 bis unter 15 Jahre;2085 +30.06.1986;M�hringen;15 bis unter 18 Jahre;998 +30.06.1986;M�hringen;18 bis unter 30 Jahre;5270 +30.06.1986;M�hringen;30 bis unter 45 Jahre;5536 +30.06.1986;M�hringen;45 bis unter 60 Jahre;6682 +30.06.1986;M�hringen;60 bis unter 65 Jahre;1757 +30.06.1986;M�hringen;65 bis unter 75 Jahre;2541 +30.06.1986;M�hringen;75 Jahre oder �lter;2285 +30.06.1986;M�hlhausen;0 bis unter 3 Jahre;637 +30.06.1986;M�hlhausen;3 bis unter 6 Jahre;643 +30.06.1986;M�hlhausen;6 bis unter 15 Jahre;2180 +30.06.1986;M�hlhausen;15 bis unter 18 Jahre;1164 +30.06.1986;M�hlhausen;18 bis unter 30 Jahre;4883 +30.06.1986;M�hlhausen;30 bis unter 45 Jahre;5233 +30.06.1986;M�hlhausen;45 bis unter 60 Jahre;5945 +30.06.1986;M�hlhausen;60 bis unter 65 Jahre;1352 +30.06.1986;M�hlhausen;65 bis unter 75 Jahre;1879 +30.06.1986;M�hlhausen;75 Jahre oder �lter;1566 +30.06.1986;M�nster;0 bis unter 3 Jahre;158 +30.06.1986;M�nster;3 bis unter 6 Jahre;137 +30.06.1986;M�nster;6 bis unter 15 Jahre;388 +30.06.1986;M�nster;15 bis unter 18 Jahre;199 +30.06.1986;M�nster;18 bis unter 30 Jahre;1297 +30.06.1986;M�nster;30 bis unter 45 Jahre;1270 +30.06.1986;M�nster;45 bis unter 60 Jahre;1321 +30.06.1986;M�nster;60 bis unter 65 Jahre;360 +30.06.1986;M�nster;65 bis unter 75 Jahre;633 +30.06.1986;M�nster;75 Jahre oder �lter;660 +30.06.1986;Obert�rkheim;0 bis unter 3 Jahre;250 +30.06.1986;Obert�rkheim;3 bis unter 6 Jahre;209 +30.06.1986;Obert�rkheim;6 bis unter 15 Jahre;578 +30.06.1986;Obert�rkheim;15 bis unter 18 Jahre;269 +30.06.1986;Obert�rkheim;18 bis unter 30 Jahre;1507 +30.06.1986;Obert�rkheim;30 bis unter 45 Jahre;1675 +30.06.1986;Obert�rkheim;45 bis unter 60 Jahre;1742 +30.06.1986;Obert�rkheim;60 bis unter 65 Jahre;337 +30.06.1986;Obert�rkheim;65 bis unter 75 Jahre;617 +30.06.1986;Obert�rkheim;75 Jahre oder �lter;565 +30.06.1986;Plieningen;0 bis unter 3 Jahre;268 +30.06.1986;Plieningen;3 bis unter 6 Jahre;264 +30.06.1986;Plieningen;6 bis unter 15 Jahre;691 +30.06.1986;Plieningen;15 bis unter 18 Jahre;353 +30.06.1986;Plieningen;18 bis unter 30 Jahre;2092 +30.06.1986;Plieningen;30 bis unter 45 Jahre;2134 +30.06.1986;Plieningen;45 bis unter 60 Jahre;2184 +30.06.1986;Plieningen;60 bis unter 65 Jahre;616 +30.06.1986;Plieningen;65 bis unter 75 Jahre;924 +30.06.1986;Plieningen;75 Jahre oder �lter;666 +30.06.1986;Sillenbuch;0 bis unter 3 Jahre;475 +30.06.1986;Sillenbuch;3 bis unter 6 Jahre;489 +30.06.1986;Sillenbuch;6 bis unter 15 Jahre;1588 +30.06.1986;Sillenbuch;15 bis unter 18 Jahre;702 +30.06.1986;Sillenbuch;18 bis unter 30 Jahre;3432 +30.06.1986;Sillenbuch;30 bis unter 45 Jahre;4393 +30.06.1986;Sillenbuch;45 bis unter 60 Jahre;4360 +30.06.1986;Sillenbuch;60 bis unter 65 Jahre;1110 +30.06.1986;Sillenbuch;65 bis unter 75 Jahre;2010 +30.06.1986;Sillenbuch;75 Jahre oder �lter;2257 +30.06.1986;Stammheim;0 bis unter 3 Jahre;288 +30.06.1986;Stammheim;3 bis unter 6 Jahre;302 +30.06.1986;Stammheim;6 bis unter 15 Jahre;705 +30.06.1986;Stammheim;15 bis unter 18 Jahre;340 +30.06.1986;Stammheim;18 bis unter 30 Jahre;2119 +30.06.1986;Stammheim;30 bis unter 45 Jahre;2102 +30.06.1986;Stammheim;45 bis unter 60 Jahre;2112 +30.06.1986;Stammheim;60 bis unter 65 Jahre;413 +30.06.1986;Stammheim;65 bis unter 75 Jahre;728 +30.06.1986;Stammheim;75 Jahre oder �lter;686 +30.06.1986;Untert�rkheim;0 bis unter 3 Jahre;383 +30.06.1986;Untert�rkheim;3 bis unter 6 Jahre;418 +30.06.1986;Untert�rkheim;6 bis unter 15 Jahre;1228 +30.06.1986;Untert�rkheim;15 bis unter 18 Jahre;573 +30.06.1986;Untert�rkheim;18 bis unter 30 Jahre;2945 +30.06.1986;Untert�rkheim;30 bis unter 45 Jahre;3401 +30.06.1986;Untert�rkheim;45 bis unter 60 Jahre;3373 +30.06.1986;Untert�rkheim;60 bis unter 65 Jahre;806 +30.06.1986;Untert�rkheim;65 bis unter 75 Jahre;1395 +30.06.1986;Untert�rkheim;75 Jahre oder �lter;1301 +30.06.1986;Vaihingen;0 bis unter 3 Jahre;921 +30.06.1986;Vaihingen;3 bis unter 6 Jahre;956 +30.06.1986;Vaihingen;6 bis unter 15 Jahre;2676 +30.06.1986;Vaihingen;15 bis unter 18 Jahre;1304 +30.06.1986;Vaihingen;18 bis unter 30 Jahre;7548 +30.06.1986;Vaihingen;30 bis unter 45 Jahre;7787 +30.06.1986;Vaihingen;45 bis unter 60 Jahre;7810 +30.06.1986;Vaihingen;60 bis unter 65 Jahre;2102 +30.06.1986;Vaihingen;65 bis unter 75 Jahre;3411 +30.06.1986;Vaihingen;75 Jahre oder �lter;3069 +30.06.1986;Wangen;0 bis unter 3 Jahre;266 +30.06.1986;Wangen;3 bis unter 6 Jahre;231 +30.06.1986;Wangen;6 bis unter 15 Jahre;647 +30.06.1986;Wangen;15 bis unter 18 Jahre;295 +30.06.1986;Wangen;18 bis unter 30 Jahre;1883 +30.06.1986;Wangen;30 bis unter 45 Jahre;1938 +30.06.1986;Wangen;45 bis unter 60 Jahre;1898 +30.06.1986;Wangen;60 bis unter 65 Jahre;428 +30.06.1986;Wangen;65 bis unter 75 Jahre;753 +30.06.1986;Wangen;75 Jahre oder �lter;656 +30.06.1986;Weilimdorf;0 bis unter 3 Jahre;718 +30.06.1986;Weilimdorf;3 bis unter 6 Jahre;859 +30.06.1986;Weilimdorf;6 bis unter 15 Jahre;2232 +30.06.1986;Weilimdorf;15 bis unter 18 Jahre;1026 +30.06.1986;Weilimdorf;18 bis unter 30 Jahre;4906 +30.06.1986;Weilimdorf;30 bis unter 45 Jahre;5760 +30.06.1986;Weilimdorf;45 bis unter 60 Jahre;6007 +30.06.1986;Weilimdorf;60 bis unter 65 Jahre;1679 +30.06.1986;Weilimdorf;65 bis unter 75 Jahre;2616 +30.06.1986;Weilimdorf;75 Jahre oder �lter;1970 +30.06.1986;Zuffenhausen;0 bis unter 3 Jahre;870 +30.06.1986;Zuffenhausen;3 bis unter 6 Jahre;864 +30.06.1986;Zuffenhausen;6 bis unter 15 Jahre;2418 +30.06.1986;Zuffenhausen;15 bis unter 18 Jahre;1131 +30.06.1986;Zuffenhausen;18 bis unter 30 Jahre;6414 +30.06.1986;Zuffenhausen;30 bis unter 45 Jahre;6565 +30.06.1986;Zuffenhausen;45 bis unter 60 Jahre;7246 +30.06.1986;Zuffenhausen;60 bis unter 65 Jahre;1975 +30.06.1986;Zuffenhausen;65 bis unter 75 Jahre;3314 +30.06.1986;Zuffenhausen;75 Jahre oder �lter;2902 +31.12.1986;Mitte;0 bis unter 3 Jahre;491 +31.12.1986;Mitte;3 bis unter 6 Jahre;451 +31.12.1986;Mitte;6 bis unter 15 Jahre;1377 +31.12.1986;Mitte;15 bis unter 18 Jahre;647 +31.12.1986;Mitte;18 bis unter 30 Jahre;5465 +31.12.1986;Mitte;30 bis unter 45 Jahre;5534 +31.12.1986;Mitte;45 bis unter 60 Jahre;4835 +31.12.1986;Mitte;60 bis unter 65 Jahre;1030 +31.12.1986;Mitte;65 bis unter 75 Jahre;1463 +31.12.1986;Mitte;75 Jahre oder �lter;1328 +31.12.1986;Nord;0 bis unter 3 Jahre;540 +31.12.1986;Nord;3 bis unter 6 Jahre;477 +31.12.1986;Nord;6 bis unter 15 Jahre;1475 +31.12.1986;Nord;15 bis unter 18 Jahre;732 +31.12.1986;Nord;18 bis unter 30 Jahre;4763 +31.12.1986;Nord;30 bis unter 45 Jahre;4778 +31.12.1986;Nord;45 bis unter 60 Jahre;5272 +31.12.1986;Nord;60 bis unter 65 Jahre;1294 +31.12.1986;Nord;65 bis unter 75 Jahre;2239 +31.12.1986;Nord;75 Jahre oder �lter;2074 +31.12.1986;Ost;0 bis unter 3 Jahre;1280 +31.12.1986;Ost;3 bis unter 6 Jahre;1114 +31.12.1986;Ost;6 bis unter 15 Jahre;3207 +31.12.1986;Ost;15 bis unter 18 Jahre;1333 +31.12.1986;Ost;18 bis unter 30 Jahre;9934 +31.12.1986;Ost;30 bis unter 45 Jahre;9941 +31.12.1986;Ost;45 bis unter 60 Jahre;9377 +31.12.1986;Ost;60 bis unter 65 Jahre;2368 +31.12.1986;Ost;65 bis unter 75 Jahre;4231 +31.12.1986;Ost;75 Jahre oder �lter;4029 +31.12.1986;S�d;0 bis unter 3 Jahre;1160 +31.12.1986;S�d;3 bis unter 6 Jahre;1008 +31.12.1986;S�d;6 bis unter 15 Jahre;2945 +31.12.1986;S�d;15 bis unter 18 Jahre;1266 +31.12.1986;S�d;18 bis unter 30 Jahre;9316 +31.12.1986;S�d;30 bis unter 45 Jahre;9935 +31.12.1986;S�d;45 bis unter 60 Jahre;8565 +31.12.1986;S�d;60 bis unter 65 Jahre;1890 +31.12.1986;S�d;65 bis unter 75 Jahre;3336 +31.12.1986;S�d;75 Jahre oder �lter;3384 +31.12.1986;West;0 bis unter 3 Jahre;1208 +31.12.1986;West;3 bis unter 6 Jahre;1118 +31.12.1986;West;6 bis unter 15 Jahre;3122 +31.12.1986;West;15 bis unter 18 Jahre;1394 +31.12.1986;West;18 bis unter 30 Jahre;11322 +31.12.1986;West;30 bis unter 45 Jahre;11537 +31.12.1986;West;45 bis unter 60 Jahre;9895 +31.12.1986;West;60 bis unter 65 Jahre;2456 +31.12.1986;West;65 bis unter 75 Jahre;4440 +31.12.1986;West;75 Jahre oder �lter;4553 +31.12.1986;Bad Cannstatt;0 bis unter 3 Jahre;1561 +31.12.1986;Bad Cannstatt;3 bis unter 6 Jahre;1515 +31.12.1986;Bad Cannstatt;6 bis unter 15 Jahre;4575 +31.12.1986;Bad Cannstatt;15 bis unter 18 Jahre;1951 +31.12.1986;Bad Cannstatt;18 bis unter 30 Jahre;11903 +31.12.1986;Bad Cannstatt;30 bis unter 45 Jahre;12364 +31.12.1986;Bad Cannstatt;45 bis unter 60 Jahre;13268 +31.12.1986;Bad Cannstatt;60 bis unter 65 Jahre;3030 +31.12.1986;Bad Cannstatt;65 bis unter 75 Jahre;5144 +31.12.1986;Bad Cannstatt;75 Jahre oder �lter;4693 +31.12.1986;Birkach;0 bis unter 3 Jahre;150 +31.12.1986;Birkach;3 bis unter 6 Jahre;158 +31.12.1986;Birkach;6 bis unter 15 Jahre;400 +31.12.1986;Birkach;15 bis unter 18 Jahre;181 +31.12.1986;Birkach;18 bis unter 30 Jahre;1163 +31.12.1986;Birkach;30 bis unter 45 Jahre;1211 +31.12.1986;Birkach;45 bis unter 60 Jahre;1087 +31.12.1986;Birkach;60 bis unter 65 Jahre;325 +31.12.1986;Birkach;65 bis unter 75 Jahre;486 +31.12.1986;Birkach;75 Jahre oder �lter;456 +31.12.1986;Botnang;0 bis unter 3 Jahre;310 +31.12.1986;Botnang;3 bis unter 6 Jahre;347 +31.12.1986;Botnang;6 bis unter 15 Jahre;965 +31.12.1986;Botnang;15 bis unter 18 Jahre;452 +31.12.1986;Botnang;18 bis unter 30 Jahre;2224 +31.12.1986;Botnang;30 bis unter 45 Jahre;2780 +31.12.1986;Botnang;45 bis unter 60 Jahre;2966 +31.12.1986;Botnang;60 bis unter 65 Jahre;722 +31.12.1986;Botnang;65 bis unter 75 Jahre;1229 +31.12.1986;Botnang;75 Jahre oder �lter;1092 +31.12.1986;Degerloch;0 bis unter 3 Jahre;350 +31.12.1986;Degerloch;3 bis unter 6 Jahre;359 +31.12.1986;Degerloch;6 bis unter 15 Jahre;1051 +31.12.1986;Degerloch;15 bis unter 18 Jahre;517 +31.12.1986;Degerloch;18 bis unter 30 Jahre;2860 +31.12.1986;Degerloch;30 bis unter 45 Jahre;3223 +31.12.1986;Degerloch;45 bis unter 60 Jahre;3399 +31.12.1986;Degerloch;60 bis unter 65 Jahre;902 +31.12.1986;Degerloch;65 bis unter 75 Jahre;1783 +31.12.1986;Degerloch;75 Jahre oder �lter;2026 +31.12.1986;Feuerbach;0 bis unter 3 Jahre;678 +31.12.1986;Feuerbach;3 bis unter 6 Jahre;670 +31.12.1986;Feuerbach;6 bis unter 15 Jahre;1966 +31.12.1986;Feuerbach;15 bis unter 18 Jahre;962 +31.12.1986;Feuerbach;18 bis unter 30 Jahre;5384 +31.12.1986;Feuerbach;30 bis unter 45 Jahre;5799 +31.12.1986;Feuerbach;45 bis unter 60 Jahre;6063 +31.12.1986;Feuerbach;60 bis unter 65 Jahre;1388 +31.12.1986;Feuerbach;65 bis unter 75 Jahre;2404 +31.12.1986;Feuerbach;75 Jahre oder �lter;2227 +31.12.1986;Hedelfingen;0 bis unter 3 Jahre;244 +31.12.1986;Hedelfingen;3 bis unter 6 Jahre;236 +31.12.1986;Hedelfingen;6 bis unter 15 Jahre;687 +31.12.1986;Hedelfingen;15 bis unter 18 Jahre;285 +31.12.1986;Hedelfingen;18 bis unter 30 Jahre;1802 +31.12.1986;Hedelfingen;30 bis unter 45 Jahre;1977 +31.12.1986;Hedelfingen;45 bis unter 60 Jahre;1974 +31.12.1986;Hedelfingen;60 bis unter 65 Jahre;400 +31.12.1986;Hedelfingen;65 bis unter 75 Jahre;656 +31.12.1986;Hedelfingen;75 Jahre oder �lter;569 +31.12.1986;M�hringen;0 bis unter 3 Jahre;675 +31.12.1986;M�hringen;3 bis unter 6 Jahre;658 +31.12.1986;M�hringen;6 bis unter 15 Jahre;2018 +31.12.1986;M�hringen;15 bis unter 18 Jahre;965 +31.12.1986;M�hringen;18 bis unter 30 Jahre;5242 +31.12.1986;M�hringen;30 bis unter 45 Jahre;5504 +31.12.1986;M�hringen;45 bis unter 60 Jahre;6697 +31.12.1986;M�hringen;60 bis unter 65 Jahre;1731 +31.12.1986;M�hringen;65 bis unter 75 Jahre;2553 +31.12.1986;M�hringen;75 Jahre oder �lter;2341 +31.12.1986;M�hlhausen;0 bis unter 3 Jahre;651 +31.12.1986;M�hlhausen;3 bis unter 6 Jahre;643 +31.12.1986;M�hlhausen;6 bis unter 15 Jahre;2141 +31.12.1986;M�hlhausen;15 bis unter 18 Jahre;1086 +31.12.1986;M�hlhausen;18 bis unter 30 Jahre;4924 +31.12.1986;M�hlhausen;30 bis unter 45 Jahre;5174 +31.12.1986;M�hlhausen;45 bis unter 60 Jahre;5970 +31.12.1986;M�hlhausen;60 bis unter 65 Jahre;1371 +31.12.1986;M�hlhausen;65 bis unter 75 Jahre;1871 +31.12.1986;M�hlhausen;75 Jahre oder �lter;1616 +31.12.1986;M�nster;0 bis unter 3 Jahre;159 +31.12.1986;M�nster;3 bis unter 6 Jahre;122 +31.12.1986;M�nster;6 bis unter 15 Jahre;399 +31.12.1986;M�nster;15 bis unter 18 Jahre;185 +31.12.1986;M�nster;18 bis unter 30 Jahre;1328 +31.12.1986;M�nster;30 bis unter 45 Jahre;1267 +31.12.1986;M�nster;45 bis unter 60 Jahre;1314 +31.12.1986;M�nster;60 bis unter 65 Jahre;356 +31.12.1986;M�nster;65 bis unter 75 Jahre;634 +31.12.1986;M�nster;75 Jahre oder �lter;662 +31.12.1986;Obert�rkheim;0 bis unter 3 Jahre;250 +31.12.1986;Obert�rkheim;3 bis unter 6 Jahre;216 +31.12.1986;Obert�rkheim;6 bis unter 15 Jahre;580 +31.12.1986;Obert�rkheim;15 bis unter 18 Jahre;274 +31.12.1986;Obert�rkheim;18 bis unter 30 Jahre;1556 +31.12.1986;Obert�rkheim;30 bis unter 45 Jahre;1648 +31.12.1986;Obert�rkheim;45 bis unter 60 Jahre;1739 +31.12.1986;Obert�rkheim;60 bis unter 65 Jahre;350 +31.12.1986;Obert�rkheim;65 bis unter 75 Jahre;608 +31.12.1986;Obert�rkheim;75 Jahre oder �lter;551 +31.12.1986;Plieningen;0 bis unter 3 Jahre;280 +31.12.1986;Plieningen;3 bis unter 6 Jahre;245 +31.12.1986;Plieningen;6 bis unter 15 Jahre;690 +31.12.1986;Plieningen;15 bis unter 18 Jahre;344 +31.12.1986;Plieningen;18 bis unter 30 Jahre;2124 +31.12.1986;Plieningen;30 bis unter 45 Jahre;2118 +31.12.1986;Plieningen;45 bis unter 60 Jahre;2222 +31.12.1986;Plieningen;60 bis unter 65 Jahre;608 +31.12.1986;Plieningen;65 bis unter 75 Jahre;938 +31.12.1986;Plieningen;75 Jahre oder �lter;691 +31.12.1986;Sillenbuch;0 bis unter 3 Jahre;480 +31.12.1986;Sillenbuch;3 bis unter 6 Jahre;456 +31.12.1986;Sillenbuch;6 bis unter 15 Jahre;1558 +31.12.1986;Sillenbuch;15 bis unter 18 Jahre;687 +31.12.1986;Sillenbuch;18 bis unter 30 Jahre;3478 +31.12.1986;Sillenbuch;30 bis unter 45 Jahre;4339 +31.12.1986;Sillenbuch;45 bis unter 60 Jahre;4423 +31.12.1986;Sillenbuch;60 bis unter 65 Jahre;1127 +31.12.1986;Sillenbuch;65 bis unter 75 Jahre;1991 +31.12.1986;Sillenbuch;75 Jahre oder �lter;2303 +31.12.1986;Stammheim;0 bis unter 3 Jahre;340 +31.12.1986;Stammheim;3 bis unter 6 Jahre;344 +31.12.1986;Stammheim;6 bis unter 15 Jahre;755 +31.12.1986;Stammheim;15 bis unter 18 Jahre;322 +31.12.1986;Stammheim;18 bis unter 30 Jahre;2171 +31.12.1986;Stammheim;30 bis unter 45 Jahre;2248 +31.12.1986;Stammheim;45 bis unter 60 Jahre;2126 +31.12.1986;Stammheim;60 bis unter 65 Jahre;423 +31.12.1986;Stammheim;65 bis unter 75 Jahre;719 +31.12.1986;Stammheim;75 Jahre oder �lter;689 +31.12.1986;Untert�rkheim;0 bis unter 3 Jahre;385 +31.12.1986;Untert�rkheim;3 bis unter 6 Jahre;415 +31.12.1986;Untert�rkheim;6 bis unter 15 Jahre;1222 +31.12.1986;Untert�rkheim;15 bis unter 18 Jahre;558 +31.12.1986;Untert�rkheim;18 bis unter 30 Jahre;2998 +31.12.1986;Untert�rkheim;30 bis unter 45 Jahre;3362 +31.12.1986;Untert�rkheim;45 bis unter 60 Jahre;3429 +31.12.1986;Untert�rkheim;60 bis unter 65 Jahre;782 +31.12.1986;Untert�rkheim;65 bis unter 75 Jahre;1367 +31.12.1986;Untert�rkheim;75 Jahre oder �lter;1346 +31.12.1986;Vaihingen;0 bis unter 3 Jahre;963 +31.12.1986;Vaihingen;3 bis unter 6 Jahre;946 +31.12.1986;Vaihingen;6 bis unter 15 Jahre;2643 +31.12.1986;Vaihingen;15 bis unter 18 Jahre;1271 +31.12.1986;Vaihingen;18 bis unter 30 Jahre;7661 +31.12.1986;Vaihingen;30 bis unter 45 Jahre;7753 +31.12.1986;Vaihingen;45 bis unter 60 Jahre;7905 +31.12.1986;Vaihingen;60 bis unter 65 Jahre;2087 +31.12.1986;Vaihingen;65 bis unter 75 Jahre;3389 +31.12.1986;Vaihingen;75 Jahre oder �lter;3112 +31.12.1986;Wangen;0 bis unter 3 Jahre;260 +31.12.1986;Wangen;3 bis unter 6 Jahre;245 +31.12.1986;Wangen;6 bis unter 15 Jahre;651 +31.12.1986;Wangen;15 bis unter 18 Jahre;267 +31.12.1986;Wangen;18 bis unter 30 Jahre;1816 +31.12.1986;Wangen;30 bis unter 45 Jahre;1893 +31.12.1986;Wangen;45 bis unter 60 Jahre;1933 +31.12.1986;Wangen;60 bis unter 65 Jahre;406 +31.12.1986;Wangen;65 bis unter 75 Jahre;735 +31.12.1986;Wangen;75 Jahre oder �lter;671 +31.12.1986;Weilimdorf;0 bis unter 3 Jahre;726 +31.12.1986;Weilimdorf;3 bis unter 6 Jahre;808 +31.12.1986;Weilimdorf;6 bis unter 15 Jahre;2252 +31.12.1986;Weilimdorf;15 bis unter 18 Jahre;1015 +31.12.1986;Weilimdorf;18 bis unter 30 Jahre;4979 +31.12.1986;Weilimdorf;30 bis unter 45 Jahre;5676 +31.12.1986;Weilimdorf;45 bis unter 60 Jahre;6044 +31.12.1986;Weilimdorf;60 bis unter 65 Jahre;1705 +31.12.1986;Weilimdorf;65 bis unter 75 Jahre;2604 +31.12.1986;Weilimdorf;75 Jahre oder �lter;2028 +31.12.1986;Zuffenhausen;0 bis unter 3 Jahre;869 +31.12.1986;Zuffenhausen;3 bis unter 6 Jahre;864 +31.12.1986;Zuffenhausen;6 bis unter 15 Jahre;2396 +31.12.1986;Zuffenhausen;15 bis unter 18 Jahre;1122 +31.12.1986;Zuffenhausen;18 bis unter 30 Jahre;6516 +31.12.1986;Zuffenhausen;30 bis unter 45 Jahre;6537 +31.12.1986;Zuffenhausen;45 bis unter 60 Jahre;7281 +31.12.1986;Zuffenhausen;60 bis unter 65 Jahre;1952 +31.12.1986;Zuffenhausen;65 bis unter 75 Jahre;3284 +31.12.1986;Zuffenhausen;75 Jahre oder �lter;2944 +30.06.1987;Mitte;0 bis unter 3 Jahre;471 +30.06.1987;Mitte;3 bis unter 6 Jahre;475 +30.06.1987;Mitte;6 bis unter 15 Jahre;1382 +30.06.1987;Mitte;15 bis unter 18 Jahre;619 +30.06.1987;Mitte;18 bis unter 30 Jahre;5589 +30.06.1987;Mitte;30 bis unter 45 Jahre;5556 +30.06.1987;Mitte;45 bis unter 60 Jahre;4904 +30.06.1987;Mitte;60 bis unter 65 Jahre;1044 +30.06.1987;Mitte;65 bis unter 75 Jahre;1463 +30.06.1987;Mitte;75 Jahre oder �lter;1343 +30.06.1987;Nord;0 bis unter 3 Jahre;534 +30.06.1987;Nord;3 bis unter 6 Jahre;488 +30.06.1987;Nord;6 bis unter 15 Jahre;1479 +30.06.1987;Nord;15 bis unter 18 Jahre;696 +30.06.1987;Nord;18 bis unter 30 Jahre;4757 +30.06.1987;Nord;30 bis unter 45 Jahre;4774 +30.06.1987;Nord;45 bis unter 60 Jahre;5292 +30.06.1987;Nord;60 bis unter 65 Jahre;1307 +30.06.1987;Nord;65 bis unter 75 Jahre;2201 +30.06.1987;Nord;75 Jahre oder �lter;2159 +30.06.1987;Ost;0 bis unter 3 Jahre;1288 +30.06.1987;Ost;3 bis unter 6 Jahre;1096 +30.06.1987;Ost;6 bis unter 15 Jahre;3242 +30.06.1987;Ost;15 bis unter 18 Jahre;1308 +30.06.1987;Ost;18 bis unter 30 Jahre;9987 +30.06.1987;Ost;30 bis unter 45 Jahre;10028 +30.06.1987;Ost;45 bis unter 60 Jahre;9464 +30.06.1987;Ost;60 bis unter 65 Jahre;2336 +30.06.1987;Ost;65 bis unter 75 Jahre;4170 +30.06.1987;Ost;75 Jahre oder �lter;4065 +30.06.1987;S�d;0 bis unter 3 Jahre;1200 +30.06.1987;S�d;3 bis unter 6 Jahre;993 +30.06.1987;S�d;6 bis unter 15 Jahre;2961 +30.06.1987;S�d;15 bis unter 18 Jahre;1213 +30.06.1987;S�d;18 bis unter 30 Jahre;9343 +30.06.1987;S�d;30 bis unter 45 Jahre;9961 +30.06.1987;S�d;45 bis unter 60 Jahre;8703 +30.06.1987;S�d;60 bis unter 65 Jahre;1881 +30.06.1987;S�d;65 bis unter 75 Jahre;3278 +30.06.1987;S�d;75 Jahre oder �lter;3410 +30.06.1987;West;0 bis unter 3 Jahre;1268 +30.06.1987;West;3 bis unter 6 Jahre;1096 +30.06.1987;West;6 bis unter 15 Jahre;3101 +30.06.1987;West;15 bis unter 18 Jahre;1336 +30.06.1987;West;18 bis unter 30 Jahre;11297 +30.06.1987;West;30 bis unter 45 Jahre;11490 +30.06.1987;West;45 bis unter 60 Jahre;9946 +30.06.1987;West;60 bis unter 65 Jahre;2419 +30.06.1987;West;65 bis unter 75 Jahre;4350 +30.06.1987;West;75 Jahre oder �lter;4527 +30.06.1987;Bad Cannstatt;0 bis unter 3 Jahre;1543 +30.06.1987;Bad Cannstatt;3 bis unter 6 Jahre;1493 +30.06.1987;Bad Cannstatt;6 bis unter 15 Jahre;4549 +30.06.1987;Bad Cannstatt;15 bis unter 18 Jahre;1874 +30.06.1987;Bad Cannstatt;18 bis unter 30 Jahre;12006 +30.06.1987;Bad Cannstatt;30 bis unter 45 Jahre;12309 +30.06.1987;Bad Cannstatt;45 bis unter 60 Jahre;13414 +30.06.1987;Bad Cannstatt;60 bis unter 65 Jahre;2994 +30.06.1987;Bad Cannstatt;65 bis unter 75 Jahre;5095 +30.06.1987;Bad Cannstatt;75 Jahre oder �lter;4791 +30.06.1987;Birkach;0 bis unter 3 Jahre;145 +30.06.1987;Birkach;3 bis unter 6 Jahre;163 +30.06.1987;Birkach;6 bis unter 15 Jahre;412 +30.06.1987;Birkach;15 bis unter 18 Jahre;171 +30.06.1987;Birkach;18 bis unter 30 Jahre;1186 +30.06.1987;Birkach;30 bis unter 45 Jahre;1243 +30.06.1987;Birkach;45 bis unter 60 Jahre;1108 +30.06.1987;Birkach;60 bis unter 65 Jahre;342 +30.06.1987;Birkach;65 bis unter 75 Jahre;480 +30.06.1987;Birkach;75 Jahre oder �lter;447 +30.06.1987;Botnang;0 bis unter 3 Jahre;319 +30.06.1987;Botnang;3 bis unter 6 Jahre;329 +30.06.1987;Botnang;6 bis unter 15 Jahre;985 +30.06.1987;Botnang;15 bis unter 18 Jahre;422 +30.06.1987;Botnang;18 bis unter 30 Jahre;2257 +30.06.1987;Botnang;30 bis unter 45 Jahre;2739 +30.06.1987;Botnang;45 bis unter 60 Jahre;3012 +30.06.1987;Botnang;60 bis unter 65 Jahre;746 +30.06.1987;Botnang;65 bis unter 75 Jahre;1203 +30.06.1987;Botnang;75 Jahre oder �lter;1121 +30.06.1987;Degerloch;0 bis unter 3 Jahre;364 +30.06.1987;Degerloch;3 bis unter 6 Jahre;365 +30.06.1987;Degerloch;6 bis unter 15 Jahre;1064 +30.06.1987;Degerloch;15 bis unter 18 Jahre;473 +30.06.1987;Degerloch;18 bis unter 30 Jahre;2923 +30.06.1987;Degerloch;30 bis unter 45 Jahre;3236 +30.06.1987;Degerloch;45 bis unter 60 Jahre;3414 +30.06.1987;Degerloch;60 bis unter 65 Jahre;911 +30.06.1987;Degerloch;65 bis unter 75 Jahre;1731 +30.06.1987;Degerloch;75 Jahre oder �lter;2052 +30.06.1987;Feuerbach;0 bis unter 3 Jahre;687 +30.06.1987;Feuerbach;3 bis unter 6 Jahre;655 +30.06.1987;Feuerbach;6 bis unter 15 Jahre;1970 +30.06.1987;Feuerbach;15 bis unter 18 Jahre;923 +30.06.1987;Feuerbach;18 bis unter 30 Jahre;5407 +30.06.1987;Feuerbach;30 bis unter 45 Jahre;5773 +30.06.1987;Feuerbach;45 bis unter 60 Jahre;6152 +30.06.1987;Feuerbach;60 bis unter 65 Jahre;1330 +30.06.1987;Feuerbach;65 bis unter 75 Jahre;2434 +30.06.1987;Feuerbach;75 Jahre oder �lter;2255 +30.06.1987;Hedelfingen;0 bis unter 3 Jahre;256 +30.06.1987;Hedelfingen;3 bis unter 6 Jahre;242 +30.06.1987;Hedelfingen;6 bis unter 15 Jahre;663 +30.06.1987;Hedelfingen;15 bis unter 18 Jahre;276 +30.06.1987;Hedelfingen;18 bis unter 30 Jahre;1826 +30.06.1987;Hedelfingen;30 bis unter 45 Jahre;1969 +30.06.1987;Hedelfingen;45 bis unter 60 Jahre;1994 +30.06.1987;Hedelfingen;60 bis unter 65 Jahre;407 +30.06.1987;Hedelfingen;65 bis unter 75 Jahre;649 +30.06.1987;Hedelfingen;75 Jahre oder �lter;583 +30.06.1987;M�hringen;0 bis unter 3 Jahre;677 +30.06.1987;M�hringen;3 bis unter 6 Jahre;654 +30.06.1987;M�hringen;6 bis unter 15 Jahre;1991 +30.06.1987;M�hringen;15 bis unter 18 Jahre;908 +30.06.1987;M�hringen;18 bis unter 30 Jahre;5257 +30.06.1987;M�hringen;30 bis unter 45 Jahre;5532 +30.06.1987;M�hringen;45 bis unter 60 Jahre;6700 +30.06.1987;M�hringen;60 bis unter 65 Jahre;1720 +30.06.1987;M�hringen;65 bis unter 75 Jahre;2554 +30.06.1987;M�hringen;75 Jahre oder �lter;2384 +30.06.1987;M�hlhausen;0 bis unter 3 Jahre;664 +30.06.1987;M�hlhausen;3 bis unter 6 Jahre;647 +30.06.1987;M�hlhausen;6 bis unter 15 Jahre;2144 +30.06.1987;M�hlhausen;15 bis unter 18 Jahre;1054 +30.06.1987;M�hlhausen;18 bis unter 30 Jahre;4993 +30.06.1987;M�hlhausen;30 bis unter 45 Jahre;5221 +30.06.1987;M�hlhausen;45 bis unter 60 Jahre;6010 +30.06.1987;M�hlhausen;60 bis unter 65 Jahre;1397 +30.06.1987;M�hlhausen;65 bis unter 75 Jahre;1896 +30.06.1987;M�hlhausen;75 Jahre oder �lter;1659 +30.06.1987;M�nster;0 bis unter 3 Jahre;172 +30.06.1987;M�nster;3 bis unter 6 Jahre;134 +30.06.1987;M�nster;6 bis unter 15 Jahre;378 +30.06.1987;M�nster;15 bis unter 18 Jahre;180 +30.06.1987;M�nster;18 bis unter 30 Jahre;1360 +30.06.1987;M�nster;30 bis unter 45 Jahre;1254 +30.06.1987;M�nster;45 bis unter 60 Jahre;1309 +30.06.1987;M�nster;60 bis unter 65 Jahre;354 +30.06.1987;M�nster;65 bis unter 75 Jahre;615 +30.06.1987;M�nster;75 Jahre oder �lter;674 +30.06.1987;Obert�rkheim;0 bis unter 3 Jahre;234 +30.06.1987;Obert�rkheim;3 bis unter 6 Jahre;209 +30.06.1987;Obert�rkheim;6 bis unter 15 Jahre;598 +30.06.1987;Obert�rkheim;15 bis unter 18 Jahre;260 +30.06.1987;Obert�rkheim;18 bis unter 30 Jahre;1540 +30.06.1987;Obert�rkheim;30 bis unter 45 Jahre;1648 +30.06.1987;Obert�rkheim;45 bis unter 60 Jahre;1730 +30.06.1987;Obert�rkheim;60 bis unter 65 Jahre;365 +30.06.1987;Obert�rkheim;65 bis unter 75 Jahre;580 +30.06.1987;Obert�rkheim;75 Jahre oder �lter;571 +30.06.1987;Plieningen;0 bis unter 3 Jahre;280 +30.06.1987;Plieningen;3 bis unter 6 Jahre;256 +30.06.1987;Plieningen;6 bis unter 15 Jahre;683 +30.06.1987;Plieningen;15 bis unter 18 Jahre;321 +30.06.1987;Plieningen;18 bis unter 30 Jahre;2116 +30.06.1987;Plieningen;30 bis unter 45 Jahre;2131 +30.06.1987;Plieningen;45 bis unter 60 Jahre;2243 +30.06.1987;Plieningen;60 bis unter 65 Jahre;604 +30.06.1987;Plieningen;65 bis unter 75 Jahre;945 +30.06.1987;Plieningen;75 Jahre oder �lter;712 +30.06.1987;Sillenbuch;0 bis unter 3 Jahre;468 +30.06.1987;Sillenbuch;3 bis unter 6 Jahre;454 +30.06.1987;Sillenbuch;6 bis unter 15 Jahre;1546 +30.06.1987;Sillenbuch;15 bis unter 18 Jahre;652 +30.06.1987;Sillenbuch;18 bis unter 30 Jahre;3495 +30.06.1987;Sillenbuch;30 bis unter 45 Jahre;4281 +30.06.1987;Sillenbuch;45 bis unter 60 Jahre;4484 +30.06.1987;Sillenbuch;60 bis unter 65 Jahre;1113 +30.06.1987;Sillenbuch;65 bis unter 75 Jahre;1986 +30.06.1987;Sillenbuch;75 Jahre oder �lter;2346 +30.06.1987;Stammheim;0 bis unter 3 Jahre;373 +30.06.1987;Stammheim;3 bis unter 6 Jahre;374 +30.06.1987;Stammheim;6 bis unter 15 Jahre;778 +30.06.1987;Stammheim;15 bis unter 18 Jahre;331 +30.06.1987;Stammheim;18 bis unter 30 Jahre;2222 +30.06.1987;Stammheim;30 bis unter 45 Jahre;2378 +30.06.1987;Stammheim;45 bis unter 60 Jahre;2174 +30.06.1987;Stammheim;60 bis unter 65 Jahre;413 +30.06.1987;Stammheim;65 bis unter 75 Jahre;713 +30.06.1987;Stammheim;75 Jahre oder �lter;712 +30.06.1987;Untert�rkheim;0 bis unter 3 Jahre;401 +30.06.1987;Untert�rkheim;3 bis unter 6 Jahre;410 +30.06.1987;Untert�rkheim;6 bis unter 15 Jahre;1204 +30.06.1987;Untert�rkheim;15 bis unter 18 Jahre;536 +30.06.1987;Untert�rkheim;18 bis unter 30 Jahre;3084 +30.06.1987;Untert�rkheim;30 bis unter 45 Jahre;3333 +30.06.1987;Untert�rkheim;45 bis unter 60 Jahre;3470 +30.06.1987;Untert�rkheim;60 bis unter 65 Jahre;780 +30.06.1987;Untert�rkheim;65 bis unter 75 Jahre;1368 +30.06.1987;Untert�rkheim;75 Jahre oder �lter;1347 +30.06.1987;Vaihingen;0 bis unter 3 Jahre;984 +30.06.1987;Vaihingen;3 bis unter 6 Jahre;925 +30.06.1987;Vaihingen;6 bis unter 15 Jahre;2631 +30.06.1987;Vaihingen;15 bis unter 18 Jahre;1200 +30.06.1987;Vaihingen;18 bis unter 30 Jahre;7719 +30.06.1987;Vaihingen;30 bis unter 45 Jahre;7802 +30.06.1987;Vaihingen;45 bis unter 60 Jahre;7925 +30.06.1987;Vaihingen;60 bis unter 65 Jahre;2089 +30.06.1987;Vaihingen;65 bis unter 75 Jahre;3374 +30.06.1987;Vaihingen;75 Jahre oder �lter;3189 +30.06.1987;Wangen;0 bis unter 3 Jahre;242 +30.06.1987;Wangen;3 bis unter 6 Jahre;270 +30.06.1987;Wangen;6 bis unter 15 Jahre;641 +30.06.1987;Wangen;15 bis unter 18 Jahre;259 +30.06.1987;Wangen;18 bis unter 30 Jahre;1818 +30.06.1987;Wangen;30 bis unter 45 Jahre;1896 +30.06.1987;Wangen;45 bis unter 60 Jahre;1961 +30.06.1987;Wangen;60 bis unter 65 Jahre;400 +30.06.1987;Wangen;65 bis unter 75 Jahre;716 +30.06.1987;Wangen;75 Jahre oder �lter;670 +30.06.1987;Weilimdorf;0 bis unter 3 Jahre;741 +30.06.1987;Weilimdorf;3 bis unter 6 Jahre;804 +30.06.1987;Weilimdorf;6 bis unter 15 Jahre;2269 +30.06.1987;Weilimdorf;15 bis unter 18 Jahre;972 +30.06.1987;Weilimdorf;18 bis unter 30 Jahre;4991 +30.06.1987;Weilimdorf;30 bis unter 45 Jahre;5684 +30.06.1987;Weilimdorf;45 bis unter 60 Jahre;6044 +30.06.1987;Weilimdorf;60 bis unter 65 Jahre;1693 +30.06.1987;Weilimdorf;65 bis unter 75 Jahre;2639 +30.06.1987;Weilimdorf;75 Jahre oder �lter;2025 +30.06.1987;Zuffenhausen;0 bis unter 3 Jahre;885 +30.06.1987;Zuffenhausen;3 bis unter 6 Jahre;879 +30.06.1987;Zuffenhausen;6 bis unter 15 Jahre;2437 +30.06.1987;Zuffenhausen;15 bis unter 18 Jahre;1059 +30.06.1987;Zuffenhausen;18 bis unter 30 Jahre;6565 +30.06.1987;Zuffenhausen;30 bis unter 45 Jahre;6578 +30.06.1987;Zuffenhausen;45 bis unter 60 Jahre;7334 +30.06.1987;Zuffenhausen;60 bis unter 65 Jahre;1938 +30.06.1987;Zuffenhausen;65 bis unter 75 Jahre;3259 +30.06.1987;Zuffenhausen;75 Jahre oder �lter;2986 +31.12.1987;Mitte;0 bis unter 3 Jahre;472 +31.12.1987;Mitte;3 bis unter 6 Jahre;467 +31.12.1987;Mitte;6 bis unter 15 Jahre;1403 +31.12.1987;Mitte;15 bis unter 18 Jahre;627 +31.12.1987;Mitte;18 bis unter 30 Jahre;5684 +31.12.1987;Mitte;30 bis unter 45 Jahre;5542 +31.12.1987;Mitte;45 bis unter 60 Jahre;4907 +31.12.1987;Mitte;60 bis unter 65 Jahre;1031 +31.12.1987;Mitte;65 bis unter 75 Jahre;1485 +31.12.1987;Mitte;75 Jahre oder �lter;1360 +31.12.1987;Nord;0 bis unter 3 Jahre;533 +31.12.1987;Nord;3 bis unter 6 Jahre;486 +31.12.1987;Nord;6 bis unter 15 Jahre;1467 +31.12.1987;Nord;15 bis unter 18 Jahre;674 +31.12.1987;Nord;18 bis unter 30 Jahre;4922 +31.12.1987;Nord;30 bis unter 45 Jahre;4815 +31.12.1987;Nord;45 bis unter 60 Jahre;5344 +31.12.1987;Nord;60 bis unter 65 Jahre;1290 +31.12.1987;Nord;65 bis unter 75 Jahre;2171 +31.12.1987;Nord;75 Jahre oder �lter;2174 +31.12.1987;Ost;0 bis unter 3 Jahre;1303 +31.12.1987;Ost;3 bis unter 6 Jahre;1133 +31.12.1987;Ost;6 bis unter 15 Jahre;3247 +31.12.1987;Ost;15 bis unter 18 Jahre;1331 +31.12.1987;Ost;18 bis unter 30 Jahre;10075 +31.12.1987;Ost;30 bis unter 45 Jahre;9924 +31.12.1987;Ost;45 bis unter 60 Jahre;9547 +31.12.1987;Ost;60 bis unter 65 Jahre;2319 +31.12.1987;Ost;65 bis unter 75 Jahre;4090 +31.12.1987;Ost;75 Jahre oder �lter;4085 +31.12.1987;S�d;0 bis unter 3 Jahre;1225 +31.12.1987;S�d;3 bis unter 6 Jahre;1034 +31.12.1987;S�d;6 bis unter 15 Jahre;2959 +31.12.1987;S�d;15 bis unter 18 Jahre;1174 +31.12.1987;S�d;18 bis unter 30 Jahre;9681 +31.12.1987;S�d;30 bis unter 45 Jahre;10043 +31.12.1987;S�d;45 bis unter 60 Jahre;8687 +31.12.1987;S�d;60 bis unter 65 Jahre;1848 +31.12.1987;S�d;65 bis unter 75 Jahre;3186 +31.12.1987;S�d;75 Jahre oder �lter;3470 +31.12.1987;West;0 bis unter 3 Jahre;1310 +31.12.1987;West;3 bis unter 6 Jahre;1080 +31.12.1987;West;6 bis unter 15 Jahre;3094 +31.12.1987;West;15 bis unter 18 Jahre;1314 +31.12.1987;West;18 bis unter 30 Jahre;11444 +31.12.1987;West;30 bis unter 45 Jahre;11586 +31.12.1987;West;45 bis unter 60 Jahre;9955 +31.12.1987;West;60 bis unter 65 Jahre;2356 +31.12.1987;West;65 bis unter 75 Jahre;4313 +31.12.1987;West;75 Jahre oder �lter;4518 +31.12.1987;Bad Cannstatt;0 bis unter 3 Jahre;1599 +31.12.1987;Bad Cannstatt;3 bis unter 6 Jahre;1452 +31.12.1987;Bad Cannstatt;6 bis unter 15 Jahre;4554 +31.12.1987;Bad Cannstatt;15 bis unter 18 Jahre;1845 +31.12.1987;Bad Cannstatt;18 bis unter 30 Jahre;12040 +31.12.1987;Bad Cannstatt;30 bis unter 45 Jahre;12336 +31.12.1987;Bad Cannstatt;45 bis unter 60 Jahre;13449 +31.12.1987;Bad Cannstatt;60 bis unter 65 Jahre;2987 +31.12.1987;Bad Cannstatt;65 bis unter 75 Jahre;4982 +31.12.1987;Bad Cannstatt;75 Jahre oder �lter;4846 +31.12.1987;Birkach;0 bis unter 3 Jahre;152 +31.12.1987;Birkach;3 bis unter 6 Jahre;154 +31.12.1987;Birkach;6 bis unter 15 Jahre;430 +31.12.1987;Birkach;15 bis unter 18 Jahre;165 +31.12.1987;Birkach;18 bis unter 30 Jahre;1239 +31.12.1987;Birkach;30 bis unter 45 Jahre;1261 +31.12.1987;Birkach;45 bis unter 60 Jahre;1132 +31.12.1987;Birkach;60 bis unter 65 Jahre;345 +31.12.1987;Birkach;65 bis unter 75 Jahre;471 +31.12.1987;Birkach;75 Jahre oder �lter;461 +31.12.1987;Botnang;0 bis unter 3 Jahre;322 +31.12.1987;Botnang;3 bis unter 6 Jahre;313 +31.12.1987;Botnang;6 bis unter 15 Jahre;1008 +31.12.1987;Botnang;15 bis unter 18 Jahre;388 +31.12.1987;Botnang;18 bis unter 30 Jahre;2310 +31.12.1987;Botnang;30 bis unter 45 Jahre;2724 +31.12.1987;Botnang;45 bis unter 60 Jahre;3039 +31.12.1987;Botnang;60 bis unter 65 Jahre;752 +31.12.1987;Botnang;65 bis unter 75 Jahre;1190 +31.12.1987;Botnang;75 Jahre oder �lter;1168 +31.12.1987;Degerloch;0 bis unter 3 Jahre;368 +31.12.1987;Degerloch;3 bis unter 6 Jahre;361 +31.12.1987;Degerloch;6 bis unter 15 Jahre;1042 +31.12.1987;Degerloch;15 bis unter 18 Jahre;452 +31.12.1987;Degerloch;18 bis unter 30 Jahre;2988 +31.12.1987;Degerloch;30 bis unter 45 Jahre;3234 +31.12.1987;Degerloch;45 bis unter 60 Jahre;3459 +31.12.1987;Degerloch;60 bis unter 65 Jahre;900 +31.12.1987;Degerloch;65 bis unter 75 Jahre;1680 +31.12.1987;Degerloch;75 Jahre oder �lter;2074 +31.12.1987;Feuerbach;0 bis unter 3 Jahre;728 +31.12.1987;Feuerbach;3 bis unter 6 Jahre;649 +31.12.1987;Feuerbach;6 bis unter 15 Jahre;1955 +31.12.1987;Feuerbach;15 bis unter 18 Jahre;900 +31.12.1987;Feuerbach;18 bis unter 30 Jahre;5452 +31.12.1987;Feuerbach;30 bis unter 45 Jahre;5756 +31.12.1987;Feuerbach;45 bis unter 60 Jahre;6124 +31.12.1987;Feuerbach;60 bis unter 65 Jahre;1354 +31.12.1987;Feuerbach;65 bis unter 75 Jahre;2366 +31.12.1987;Feuerbach;75 Jahre oder �lter;2280 +31.12.1987;Hedelfingen;0 bis unter 3 Jahre;280 +31.12.1987;Hedelfingen;3 bis unter 6 Jahre;249 +31.12.1987;Hedelfingen;6 bis unter 15 Jahre;665 +31.12.1987;Hedelfingen;15 bis unter 18 Jahre;278 +31.12.1987;Hedelfingen;18 bis unter 30 Jahre;1843 +31.12.1987;Hedelfingen;30 bis unter 45 Jahre;1985 +31.12.1987;Hedelfingen;45 bis unter 60 Jahre;2024 +31.12.1987;Hedelfingen;60 bis unter 65 Jahre;411 +31.12.1987;Hedelfingen;65 bis unter 75 Jahre;630 +31.12.1987;Hedelfingen;75 Jahre oder �lter;597 +31.12.1987;M�hringen;0 bis unter 3 Jahre;701 +31.12.1987;M�hringen;3 bis unter 6 Jahre;619 +31.12.1987;M�hringen;6 bis unter 15 Jahre;1938 +31.12.1987;M�hringen;15 bis unter 18 Jahre;874 +31.12.1987;M�hringen;18 bis unter 30 Jahre;5286 +31.12.1987;M�hringen;30 bis unter 45 Jahre;5564 +31.12.1987;M�hringen;45 bis unter 60 Jahre;6672 +31.12.1987;M�hringen;60 bis unter 65 Jahre;1695 +31.12.1987;M�hringen;65 bis unter 75 Jahre;2550 +31.12.1987;M�hringen;75 Jahre oder �lter;2406 +31.12.1987;M�hlhausen;0 bis unter 3 Jahre;655 +31.12.1987;M�hlhausen;3 bis unter 6 Jahre;694 +31.12.1987;M�hlhausen;6 bis unter 15 Jahre;2187 +31.12.1987;M�hlhausen;15 bis unter 18 Jahre;986 +31.12.1987;M�hlhausen;18 bis unter 30 Jahre;5092 +31.12.1987;M�hlhausen;30 bis unter 45 Jahre;5264 +31.12.1987;M�hlhausen;45 bis unter 60 Jahre;6082 +31.12.1987;M�hlhausen;60 bis unter 65 Jahre;1418 +31.12.1987;M�hlhausen;65 bis unter 75 Jahre;1895 +31.12.1987;M�hlhausen;75 Jahre oder �lter;1719 +31.12.1987;M�nster;0 bis unter 3 Jahre;178 +31.12.1987;M�nster;3 bis unter 6 Jahre;143 +31.12.1987;M�nster;6 bis unter 15 Jahre;373 +31.12.1987;M�nster;15 bis unter 18 Jahre;179 +31.12.1987;M�nster;18 bis unter 30 Jahre;1379 +31.12.1987;M�nster;30 bis unter 45 Jahre;1241 +31.12.1987;M�nster;45 bis unter 60 Jahre;1316 +31.12.1987;M�nster;60 bis unter 65 Jahre;352 +31.12.1987;M�nster;65 bis unter 75 Jahre;591 +31.12.1987;M�nster;75 Jahre oder �lter;685 +31.12.1987;Obert�rkheim;0 bis unter 3 Jahre;253 +31.12.1987;Obert�rkheim;3 bis unter 6 Jahre;213 +31.12.1987;Obert�rkheim;6 bis unter 15 Jahre;593 +31.12.1987;Obert�rkheim;15 bis unter 18 Jahre;260 +31.12.1987;Obert�rkheim;18 bis unter 30 Jahre;1550 +31.12.1987;Obert�rkheim;30 bis unter 45 Jahre;1666 +31.12.1987;Obert�rkheim;45 bis unter 60 Jahre;1744 +31.12.1987;Obert�rkheim;60 bis unter 65 Jahre;360 +31.12.1987;Obert�rkheim;65 bis unter 75 Jahre;562 +31.12.1987;Obert�rkheim;75 Jahre oder �lter;578 +31.12.1987;Plieningen;0 bis unter 3 Jahre;277 +31.12.1987;Plieningen;3 bis unter 6 Jahre;245 +31.12.1987;Plieningen;6 bis unter 15 Jahre;670 +31.12.1987;Plieningen;15 bis unter 18 Jahre;321 +31.12.1987;Plieningen;18 bis unter 30 Jahre;2182 +31.12.1987;Plieningen;30 bis unter 45 Jahre;2109 +31.12.1987;Plieningen;45 bis unter 60 Jahre;2254 +31.12.1987;Plieningen;60 bis unter 65 Jahre;594 +31.12.1987;Plieningen;65 bis unter 75 Jahre;959 +31.12.1987;Plieningen;75 Jahre oder �lter;730 +31.12.1987;Sillenbuch;0 bis unter 3 Jahre;487 +31.12.1987;Sillenbuch;3 bis unter 6 Jahre;451 +31.12.1987;Sillenbuch;6 bis unter 15 Jahre;1513 +31.12.1987;Sillenbuch;15 bis unter 18 Jahre;641 +31.12.1987;Sillenbuch;18 bis unter 30 Jahre;3526 +31.12.1987;Sillenbuch;30 bis unter 45 Jahre;4235 +31.12.1987;Sillenbuch;45 bis unter 60 Jahre;4516 +31.12.1987;Sillenbuch;60 bis unter 65 Jahre;1128 +31.12.1987;Sillenbuch;65 bis unter 75 Jahre;1981 +31.12.1987;Sillenbuch;75 Jahre oder �lter;2362 +31.12.1987;Stammheim;0 bis unter 3 Jahre;416 +31.12.1987;Stammheim;3 bis unter 6 Jahre;403 +31.12.1987;Stammheim;6 bis unter 15 Jahre;834 +31.12.1987;Stammheim;15 bis unter 18 Jahre;326 +31.12.1987;Stammheim;18 bis unter 30 Jahre;2247 +31.12.1987;Stammheim;30 bis unter 45 Jahre;2512 +31.12.1987;Stammheim;45 bis unter 60 Jahre;2192 +31.12.1987;Stammheim;60 bis unter 65 Jahre;416 +31.12.1987;Stammheim;65 bis unter 75 Jahre;699 +31.12.1987;Stammheim;75 Jahre oder �lter;728 +31.12.1987;Untert�rkheim;0 bis unter 3 Jahre;417 +31.12.1987;Untert�rkheim;3 bis unter 6 Jahre;395 +31.12.1987;Untert�rkheim;6 bis unter 15 Jahre;1188 +31.12.1987;Untert�rkheim;15 bis unter 18 Jahre;486 +31.12.1987;Untert�rkheim;18 bis unter 30 Jahre;3150 +31.12.1987;Untert�rkheim;30 bis unter 45 Jahre;3314 +31.12.1987;Untert�rkheim;45 bis unter 60 Jahre;3480 +31.12.1987;Untert�rkheim;60 bis unter 65 Jahre;778 +31.12.1987;Untert�rkheim;65 bis unter 75 Jahre;1344 +31.12.1987;Untert�rkheim;75 Jahre oder �lter;1354 +31.12.1987;Vaihingen;0 bis unter 3 Jahre;1048 +31.12.1987;Vaihingen;3 bis unter 6 Jahre;902 +31.12.1987;Vaihingen;6 bis unter 15 Jahre;2671 +31.12.1987;Vaihingen;15 bis unter 18 Jahre;1135 +31.12.1987;Vaihingen;18 bis unter 30 Jahre;7970 +31.12.1987;Vaihingen;30 bis unter 45 Jahre;7818 +31.12.1987;Vaihingen;45 bis unter 60 Jahre;8000 +31.12.1987;Vaihingen;60 bis unter 65 Jahre;2079 +31.12.1987;Vaihingen;65 bis unter 75 Jahre;3315 +31.12.1987;Vaihingen;75 Jahre oder �lter;3229 +31.12.1987;Wangen;0 bis unter 3 Jahre;244 +31.12.1987;Wangen;3 bis unter 6 Jahre;256 +31.12.1987;Wangen;6 bis unter 15 Jahre;611 +31.12.1987;Wangen;15 bis unter 18 Jahre;275 +31.12.1987;Wangen;18 bis unter 30 Jahre;1958 +31.12.1987;Wangen;30 bis unter 45 Jahre;1896 +31.12.1987;Wangen;45 bis unter 60 Jahre;1983 +31.12.1987;Wangen;60 bis unter 65 Jahre;412 +31.12.1987;Wangen;65 bis unter 75 Jahre;708 +31.12.1987;Wangen;75 Jahre oder �lter;667 +31.12.1987;Weilimdorf;0 bis unter 3 Jahre;767 +31.12.1987;Weilimdorf;3 bis unter 6 Jahre;776 +31.12.1987;Weilimdorf;6 bis unter 15 Jahre;2261 +31.12.1987;Weilimdorf;15 bis unter 18 Jahre;921 +31.12.1987;Weilimdorf;18 bis unter 30 Jahre;5022 +31.12.1987;Weilimdorf;30 bis unter 45 Jahre;5704 +31.12.1987;Weilimdorf;45 bis unter 60 Jahre;6011 +31.12.1987;Weilimdorf;60 bis unter 65 Jahre;1688 +31.12.1987;Weilimdorf;65 bis unter 75 Jahre;2627 +31.12.1987;Weilimdorf;75 Jahre oder �lter;2027 +31.12.1987;Zuffenhausen;0 bis unter 3 Jahre;947 +31.12.1987;Zuffenhausen;3 bis unter 6 Jahre;863 +31.12.1987;Zuffenhausen;6 bis unter 15 Jahre;2498 +31.12.1987;Zuffenhausen;15 bis unter 18 Jahre;1032 +31.12.1987;Zuffenhausen;18 bis unter 30 Jahre;6627 +31.12.1987;Zuffenhausen;30 bis unter 45 Jahre;6608 +31.12.1987;Zuffenhausen;45 bis unter 60 Jahre;7331 +31.12.1987;Zuffenhausen;60 bis unter 65 Jahre;1912 +31.12.1987;Zuffenhausen;65 bis unter 75 Jahre;3212 +31.12.1987;Zuffenhausen;75 Jahre oder �lter;3040 +30.06.1988;Mitte;0 bis unter 3 Jahre;485 +30.06.1988;Mitte;3 bis unter 6 Jahre;447 +30.06.1988;Mitte;6 bis unter 15 Jahre;1385 +30.06.1988;Mitte;15 bis unter 18 Jahre;622 +30.06.1988;Mitte;18 bis unter 30 Jahre;5678 +30.06.1988;Mitte;30 bis unter 45 Jahre;5688 +30.06.1988;Mitte;45 bis unter 60 Jahre;4919 +30.06.1988;Mitte;60 bis unter 65 Jahre;1046 +30.06.1988;Mitte;65 bis unter 75 Jahre;1473 +30.06.1988;Mitte;75 Jahre oder �lter;1360 +30.06.1988;Nord;0 bis unter 3 Jahre;524 +30.06.1988;Nord;3 bis unter 6 Jahre;471 +30.06.1988;Nord;6 bis unter 15 Jahre;1474 +30.06.1988;Nord;15 bis unter 18 Jahre;664 +30.06.1988;Nord;18 bis unter 30 Jahre;4927 +30.06.1988;Nord;30 bis unter 45 Jahre;4788 +30.06.1988;Nord;45 bis unter 60 Jahre;5351 +30.06.1988;Nord;60 bis unter 65 Jahre;1324 +30.06.1988;Nord;65 bis unter 75 Jahre;2151 +30.06.1988;Nord;75 Jahre oder �lter;2190 +30.06.1988;Ost;0 bis unter 3 Jahre;1360 +30.06.1988;Ost;3 bis unter 6 Jahre;1156 +30.06.1988;Ost;6 bis unter 15 Jahre;3327 +30.06.1988;Ost;15 bis unter 18 Jahre;1306 +30.06.1988;Ost;18 bis unter 30 Jahre;10049 +30.06.1988;Ost;30 bis unter 45 Jahre;10102 +30.06.1988;Ost;45 bis unter 60 Jahre;9637 +30.06.1988;Ost;60 bis unter 65 Jahre;2339 +30.06.1988;Ost;65 bis unter 75 Jahre;4003 +30.06.1988;Ost;75 Jahre oder �lter;4147 +30.06.1988;S�d;0 bis unter 3 Jahre;1280 +30.06.1988;S�d;3 bis unter 6 Jahre;1021 +30.06.1988;S�d;6 bis unter 15 Jahre;2988 +30.06.1988;S�d;15 bis unter 18 Jahre;1197 +30.06.1988;S�d;18 bis unter 30 Jahre;9654 +30.06.1988;S�d;30 bis unter 45 Jahre;10139 +30.06.1988;S�d;45 bis unter 60 Jahre;8765 +30.06.1988;S�d;60 bis unter 65 Jahre;1902 +30.06.1988;S�d;65 bis unter 75 Jahre;3092 +30.06.1988;S�d;75 Jahre oder �lter;3494 +30.06.1988;West;0 bis unter 3 Jahre;1327 +30.06.1988;West;3 bis unter 6 Jahre;1108 +30.06.1988;West;6 bis unter 15 Jahre;3094 +30.06.1988;West;15 bis unter 18 Jahre;1227 +30.06.1988;West;18 bis unter 30 Jahre;11506 +30.06.1988;West;30 bis unter 45 Jahre;11678 +30.06.1988;West;45 bis unter 60 Jahre;10069 +30.06.1988;West;60 bis unter 65 Jahre;2309 +30.06.1988;West;65 bis unter 75 Jahre;4272 +30.06.1988;West;75 Jahre oder �lter;4559 +30.06.1988;Bad Cannstatt;0 bis unter 3 Jahre;1621 +30.06.1988;Bad Cannstatt;3 bis unter 6 Jahre;1455 +30.06.1988;Bad Cannstatt;6 bis unter 15 Jahre;4562 +30.06.1988;Bad Cannstatt;15 bis unter 18 Jahre;1793 +30.06.1988;Bad Cannstatt;18 bis unter 30 Jahre;11959 +30.06.1988;Bad Cannstatt;30 bis unter 45 Jahre;12357 +30.06.1988;Bad Cannstatt;45 bis unter 60 Jahre;13401 +30.06.1988;Bad Cannstatt;60 bis unter 65 Jahre;3064 +30.06.1988;Bad Cannstatt;65 bis unter 75 Jahre;4886 +30.06.1988;Bad Cannstatt;75 Jahre oder �lter;4925 +30.06.1988;Birkach;0 bis unter 3 Jahre;146 +30.06.1988;Birkach;3 bis unter 6 Jahre;159 +30.06.1988;Birkach;6 bis unter 15 Jahre;425 +30.06.1988;Birkach;15 bis unter 18 Jahre;171 +30.06.1988;Birkach;18 bis unter 30 Jahre;1247 +30.06.1988;Birkach;30 bis unter 45 Jahre;1266 +30.06.1988;Birkach;45 bis unter 60 Jahre;1134 +30.06.1988;Birkach;60 bis unter 65 Jahre;342 +30.06.1988;Birkach;65 bis unter 75 Jahre;475 +30.06.1988;Birkach;75 Jahre oder �lter;472 +30.06.1988;Botnang;0 bis unter 3 Jahre;338 +30.06.1988;Botnang;3 bis unter 6 Jahre;295 +30.06.1988;Botnang;6 bis unter 15 Jahre;1014 +30.06.1988;Botnang;15 bis unter 18 Jahre;375 +30.06.1988;Botnang;18 bis unter 30 Jahre;2338 +30.06.1988;Botnang;30 bis unter 45 Jahre;2729 +30.06.1988;Botnang;45 bis unter 60 Jahre;3084 +30.06.1988;Botnang;60 bis unter 65 Jahre;758 +30.06.1988;Botnang;65 bis unter 75 Jahre;1176 +30.06.1988;Botnang;75 Jahre oder �lter;1183 diff --git a/mloda_plugin_govdata/feature_groups/govdata/tests/fixtures/uba_measures.json b/mloda_plugin_govdata/feature_groups/govdata/tests/fixtures/uba_measures.json new file mode 100644 index 0000000..396def8 --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/tests/fixtures/uba_measures.json @@ -0,0 +1 @@ +{"request":{"component":"3","scope":"2","station":"143","date_from":"2025-01-01","date_to":"2025-01-01","time_from":"01:00:00","time_to":"24:00:00","lang":"en","recent":false,"index":"id","datetime_from":"2025-01-01 00:00:00","datetime_to":"2025-01-01 23:00:00"},"indices":{"data":{"station id":{"date start":["component id","scope id","value","date end","index"]}}},"data":{"143":{"2025-01-01 00:00:00":[3,2,37,"2025-01-01 01:00:00","2"],"2025-01-01 01:00:00":[3,2,35,"2025-01-01 02:00:00","1"],"2025-01-01 02:00:00":[3,2,35,"2025-01-01 03:00:00","1"],"2025-01-01 03:00:00":[3,2,36,"2025-01-01 04:00:00","1"],"2025-01-01 04:00:00":[3,2,36,"2025-01-01 05:00:00","1"],"2025-01-01 05:00:00":[3,2,36,"2025-01-01 06:00:00","1"],"2025-01-01 06:00:00":[3,2,41,"2025-01-01 07:00:00","2"],"2025-01-01 07:00:00":[3,2,44,"2025-01-01 08:00:00","2"],"2025-01-01 08:00:00":[3,2,46,"2025-01-01 09:00:00","2"],"2025-01-01 09:00:00":[3,2,48,"2025-01-01 10:00:00","2"],"2025-01-01 10:00:00":[3,2,52,"2025-01-01 11:00:00","2"],"2025-01-01 11:00:00":[3,2,54,"2025-01-01 12:00:00","2"],"2025-01-01 12:00:00":[3,2,57,"2025-01-01 13:00:00","3"],"2025-01-01 13:00:00":[3,2,59,"2025-01-01 14:00:00","3"],"2025-01-01 14:00:00":[3,2,60,"2025-01-01 15:00:00","3"],"2025-01-01 15:00:00":[3,2,61,"2025-01-01 16:00:00","3"],"2025-01-01 16:00:00":[3,2,61,"2025-01-01 17:00:00","3"],"2025-01-01 17:00:00":[3,2,60,"2025-01-01 18:00:00","3"],"2025-01-01 18:00:00":[3,2,60,"2025-01-01 19:00:00","3"],"2025-01-01 19:00:00":[3,2,62,"2025-01-01 20:00:00","3"],"2025-01-01 20:00:00":[3,2,65,"2025-01-01 21:00:00","3"],"2025-01-01 21:00:00":[3,2,66,"2025-01-01 22:00:00","3"],"2025-01-01 22:00:00":[3,2,65,"2025-01-01 23:00:00","3"],"2025-01-01 23:00:00":[3,2,64,"2025-01-01 24:00:00","3"]}}} \ No newline at end of file diff --git a/mloda_plugin_govdata/feature_groups/govdata/tests/fixtures/value_markers.csv b/mloda_plugin_govdata/feature_groups/govdata/tests/fixtures/value_markers.csv new file mode 100644 index 0000000..99551b6 --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/tests/fixtures/value_markers.csv @@ -0,0 +1,8 @@ +Stichtag;Gebiet;Wert +30.06.2020;M�hringen;- +30.06.2020;Bad Cannstatt;123 +30.06.2020;S�d;. +30.06.2020;Ost;... +30.06.2020;Feuerbach;/ +30.06.2020;W�rttemberg;x +30.06.2020;Zuffenhausen;1.234 diff --git a/mloda_plugin_govdata/feature_groups/govdata/tests/test_cache.py b/mloda_plugin_govdata/feature_groups/govdata/tests/test_cache.py new file mode 100644 index 0000000..7486895 --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/tests/test_cache.py @@ -0,0 +1,62 @@ +"""Level 1: content-addressed download cache and conditional GET.""" + +import hashlib +from pathlib import Path + +import httpx +import respx + +from mloda_plugin_govdata.feature_groups.govdata.cache import DownloadCache + +URL = "https://example.org/data.csv" +BODY = b"Stichtag;Wert\r\n30.06.2020;1\r\n" +ETAG = '"v1"' + + +@respx.mock +def test_download_and_store(tmp_path: Path) -> None: + respx.get(URL).mock(return_value=httpx.Response(200, content=BODY, headers={"ETag": ETAG})) + with DownloadCache(tmp_path) as cache: + cached = cache.get_or_download(URL) + assert cached.path.read_bytes() == BODY + assert cached.sha256 == hashlib.sha256(BODY).hexdigest() + assert cached.etag == ETAG + + +@respx.mock +def test_conditional_get_reuses_on_304(tmp_path: Path) -> None: + calls: dict[str, int] = {"n": 0} + + def handler(request: httpx.Request) -> httpx.Response: + calls["n"] += 1 + if request.headers.get("If-None-Match") == ETAG: + return httpx.Response(304) + return httpx.Response(200, content=BODY, headers={"ETag": ETAG}) + + respx.get(URL).mock(side_effect=handler) + with DownloadCache(tmp_path) as cache: + first = cache.get_or_download(URL) + second = cache.get_or_download(URL) + assert calls["n"] == 2 # second call revalidated and got a 304 + assert first.sha256 == second.sha256 + assert second.path.read_bytes() == BODY + + +@respx.mock +def test_corrupted_cache_redownloads(tmp_path: Path) -> None: + respx.get(URL).mock(return_value=httpx.Response(200, content=BODY, headers={"ETag": ETAG})) + with DownloadCache(tmp_path) as cache: + first = cache.get_or_download(URL) + first.path.write_bytes(b"corrupted") # tamper the stored body + second = cache.get_or_download(URL) + assert second.path.read_bytes() == BODY + + +@respx.mock +def test_unreadable_metadata_redownloads(tmp_path: Path) -> None: + respx.get(URL).mock(return_value=httpx.Response(200, content=BODY, headers={"ETag": ETAG})) + with DownloadCache(tmp_path) as cache: + cache.get_or_download(URL) + cache._meta_path(URL).write_text("{ not valid json", encoding="utf-8") # truncated write + second = cache.get_or_download(URL) + assert second.path.read_bytes() == BODY diff --git a/mloda_plugin_govdata/feature_groups/govdata/tests/test_client.py b/mloda_plugin_govdata/feature_groups/govdata/tests/test_client.py new file mode 100644 index 0000000..3d5a2fd --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/tests/test_client.py @@ -0,0 +1,45 @@ +"""Level 1: retry policy for transient transport / status failures.""" + +import httpx +import pytest +import respx + +from mloda_plugin_govdata.feature_groups.govdata.client import RetryableStatusError, build_client, request_with_retry + +URL = "https://example.org/x" + + +@pytest.fixture(autouse=True) +def _instant_backoff(monkeypatch: pytest.MonkeyPatch) -> None: + # Collapse tenacity's backoff to zero so retry tests run instantly. + monkeypatch.setattr( + "mloda_plugin_govdata.feature_groups.govdata.client._BASE_WAIT", + lambda _state: 0.0, + ) + + +@respx.mock +def test_retries_on_503_then_succeeds() -> None: + responses = [httpx.Response(503), httpx.Response(503), httpx.Response(200, content=b"ok")] + route = respx.get(URL).mock(side_effect=responses) + with build_client() as client: + response = request_with_retry(client, "GET", URL) + assert response.status_code == 200 + assert route.call_count == 3 + + +@respx.mock +def test_no_retry_on_404() -> None: + route = respx.get(URL).mock(return_value=httpx.Response(404)) + with build_client() as client: + response = request_with_retry(client, "GET", URL) + assert response.status_code == 404 + assert route.call_count == 1 + + +@respx.mock +def test_gives_up_after_max_attempts() -> None: + route = respx.get(URL).mock(return_value=httpx.Response(503)) + with build_client() as client, pytest.raises(RetryableStatusError): + request_with_retry(client, "GET", URL) + assert route.call_count == 5 diff --git a/mloda_plugin_govdata/feature_groups/govdata/tests/test_discovery.py b/mloda_plugin_govdata/feature_groups/govdata/tests/test_discovery.py new file mode 100644 index 0000000..db7550d --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/tests/test_discovery.py @@ -0,0 +1,63 @@ +"""Level 1/2: CKAN discovery, metadata model, and license resolution.""" + +import json +from pathlib import Path + +import httpx +import respx + +from mloda_plugin_govdata.feature_groups.govdata.client import build_client +from mloda_plugin_govdata.feature_groups.govdata.discovery import ( + Dataset, + Resource, + _select_resource, + normalize_license, + resolve_distribution, +) +from mloda_plugin_govdata.feature_groups.govdata.locator import GovDataLocator + +PACKAGE_SHOW = "https://ckan.govdata.de/api/3/action/package_show" +SLUG = "einwohner-nach-altersgruppen-und-stadtbezirken" + + +def test_normalize_license_known_and_free_text() -> None: + assert normalize_license("http://dcat-ap.de/def/licenses/cc-by/4.0") == "CC-BY-4.0" + # dataset-level spelling drift (underscore) normalizes to the resource-level label. + assert normalize_license("http://dcat-ap.de/def/licenses/dl-by-de/2_0") == "DL-DE-BY-2.0" + assert normalize_license("Es gelten keine Zugriffsbeschränkungen") == "Es gelten keine Zugriffsbeschränkungen" + assert normalize_license(None) is None + + +def test_dataset_from_ckan_fixture(fixtures_dir: Path) -> None: + payload = json.loads((fixtures_dir / "package_show.json").read_text(encoding="utf-8")) + dataset = Dataset.from_ckan(payload["result"]) + assert dataset.name == SLUG + assert dataset.resources[0].license == "http://dcat-ap.de/def/licenses/cc-by/4.0" + assert dataset.resources[0].format == "CSV" + assert "modified" in dataset.extras # DCAT freshness extra is preserved + + +@respx.mock +def test_resolve_distribution_reads_license_from_distribution(fixtures_dir: Path) -> None: + payload = (fixtures_dir / "package_show.json").read_text(encoding="utf-8") + route = respx.get(PACKAGE_SHOW).mock(return_value=httpx.Response(200, text=payload)) + with build_client() as client: + resolved = resolve_distribution(GovDataLocator(dataset_id=SLUG), client) + assert route.called + assert resolved.license == "CC-BY-4.0" + assert resolved.url.endswith(".csv") + + +def test_resolve_direct_url_skips_discovery() -> None: + with build_client() as client: + resolved = resolve_distribution(GovDataLocator(distribution_url="https://example.org/data.csv"), client) + assert resolved.url == "https://example.org/data.csv" + assert resolved.license is None + + +def test_select_resource_prefers_csv_over_first_non_csv() -> None: + resources = [ + Resource(url="https://example.org/page.html", format="HTML"), + Resource(url="https://example.org/data.csv", format="CSV"), + ] + assert _select_resource(resources, 0).url.endswith(".csv") diff --git a/mloda_plugin_govdata/feature_groups/govdata/tests/test_parse.py b/mloda_plugin_govdata/feature_groups/govdata/tests/test_parse.py new file mode 100644 index 0000000..cbdaaa2 --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/tests/test_parse.py @@ -0,0 +1,151 @@ +"""Level 1: deterministic German-CSV parsing on captured byte fixtures.""" + +import datetime +from pathlib import Path + +import pyarrow as pa +import pytest + +from mloda_plugin_govdata.feature_groups.govdata.parse import ( + ColumnType, + detect_encoding, + parse_german_csv, + parse_german_csv_bytes, + parse_multi_header_csv, + parse_multi_header_csv_bytes, +) + +POPULATION_COLUMNS: dict[str, ColumnType] = { + "Stichtag": ColumnType.DATE, + "Stadtbezirk": ColumnType.STRING, + "Alter in 10 Gruppen": ColumnType.STRING, + "Einwohner": ColumnType.INTEGER, +} + +VALUE_MARKER_COLUMNS: dict[str, ColumnType] = { + "Stichtag": ColumnType.DATE, + "Gebiet": ColumnType.STRING, + "Wert": ColumnType.INTEGER, +} + + +def test_detect_encoding_population(fixtures_dir: Path) -> None: + data = (fixtures_dir / "population_sample.csv").read_bytes() + assert detect_encoding(data) == "cp1252" + with pytest.raises(UnicodeDecodeError): + data.decode("utf-8") + + +def test_detect_encoding_prefers_utf8_sig_with_bom() -> None: + assert detect_encoding("Wert\r\n123\r\n".encode("utf-8-sig")) == "utf-8-sig" + + +def test_detect_encoding_falls_back_for_cp1252_undefined_byte() -> None: + # 0x81 is undefined in cp1252, so the ladder must reach an 8859 fallback. + assert detect_encoding(b"\x81") in {"iso-8859-15", "latin-1"} + + +def test_parse_population_sample(fixtures_dir: Path) -> None: + table = parse_german_csv(fixtures_dir / "population_sample.csv", POPULATION_COLUMNS) + assert table.schema.names == ["Stichtag", "Stadtbezirk", "Alter in 10 Gruppen", "Einwohner"] + assert table.num_rows == 1000 + assert table.schema.field("Stichtag").type == pa.date32() + assert table.schema.field("Einwohner").type == pa.int64() + first = table.slice(0, 1).to_pylist()[0] + assert first["Stichtag"] == datetime.date(1986, 6, 30) + assert first["Stadtbezirk"] == "Mitte" + assert first["Einwohner"] == 494 + + +def test_value_markers_map_correctly(fixtures_dir: Path) -> None: + table = parse_german_csv(fixtures_dir / "value_markers.csv", VALUE_MARKER_COLUMNS) + # '-' -> 0 (never null); '.', '...', '/', 'x' -> null; '1.234' -> 1234. + assert table.column("Wert").to_pylist() == [0, 123, None, None, None, None, 1234] + # cp1252 umlauts decode correctly. + assert "Möhringen" in table.column("Gebiet").to_pylist() + + +def test_unknown_columns_default_to_string(fixtures_dir: Path) -> None: + table = parse_german_csv(fixtures_dir / "value_markers.csv") + assert all(field_type == pa.string() for field_type in table.schema.types) + # Untyped: the '-' marker is preserved verbatim as a string. + assert table.column("Wert").to_pylist()[0] == "-" + + +def test_missing_column_raises(fixtures_dir: Path) -> None: + with pytest.raises(KeyError): + parse_german_csv(fixtures_dir / "value_markers.csv", {"DoesNotExist": ColumnType.STRING}) + + +def test_integer_validation_raises_on_non_numeric() -> None: + data = "Wert\r\nnotanumber\r\n".encode("cp1252") + with pytest.raises(ValueError): + parse_german_csv_bytes(data, {"Wert": ColumnType.INTEGER}) + + +def test_date_validation_raises_on_bad_date() -> None: + data = "Stichtag\r\n99.99.9999\r\n".encode("cp1252") + with pytest.raises(ValueError): + parse_german_csv_bytes(data, {"Stichtag": ColumnType.DATE}) + + +def test_large_integer_parses_exactly() -> None: + # Values above 2**53 must not be rounded through float. + big = 9007199254740993 + data = f"Wert\r\n{big}\r\n".encode("cp1252") + table = parse_german_csv_bytes(data, {"Wert": ColumnType.INTEGER}) + assert table.column("Wert").to_pylist() == [big] + + +def test_multi_header_flattens_and_types_kerg(fixtures_dir: Path) -> None: + table = parse_multi_header_csv( + fixtures_dir / "kerg_sample.csv", skiprows=5, header_rows=3, label_columns=4, value_type=ColumnType.INTEGER + ) + assert table.num_columns == 140 # 140 real columns; the trailing ';' phantom column is dropped + assert table.num_rows == 16 # 17 data lines minus one ';' separator row + assert table.schema.names[:4] == ["Nr", "Gebiet", "gehört zu", "Gewählt"] + # the 3 merged header rows flatten into one combined name + assert "Wahlberechtigte Erststimmen Endgültig" in table.schema.names + assert table.schema.field("Gebiet").type == pa.string() + assert table.schema.field("Wahlberechtigte Erststimmen Endgültig").type == pa.int64() + assert table.column("Gebiet").to_pylist()[0] == "Flensburg – Schleswig" + assert table.column("Wahlberechtigte Erststimmen Endgültig").to_pylist()[0] == 232131 + + +def test_multi_header_forward_fills_merged_cells() -> None: + data = "Group;;Other\r\na;b;c\r\n1;2;3\r\n".encode("utf-8") + table = parse_multi_header_csv_bytes(data, skiprows=0, header_rows=2, label_columns=3, value_type=ColumnType.STRING) + assert table.schema.names == ["Group a", "Group b", "Other c"] + + +def test_multi_header_dedupes_duplicate_names() -> None: + data = "skip\r\nA;A\r\n1;2\r\n3;4\r\n".encode("utf-8") + table = parse_multi_header_csv_bytes( + data, skiprows=1, header_rows=1, label_columns=0, value_type=ColumnType.INTEGER + ) + assert table.schema.names == ["A", "A (2)"] + assert table.column("A").to_pylist() == [1, 3] + assert table.column("A (2)").to_pylist() == [2, 4] + + +def test_multi_header_does_not_fill_padded_cells() -> None: + # The top header row omits a trailing cell; padding must not forward-fill into it. + data = "A;B\r\nx;y;z\r\n1;2;3\r\n".encode("utf-8") + table = parse_multi_header_csv_bytes(data, skiprows=0, header_rows=2, label_columns=3, value_type=ColumnType.STRING) + assert table.schema.names == ["A x", "B y", "z"] + + +def test_multi_header_dedupe_handles_existing_suffix() -> None: + data = "A;A (2);A\r\n1;2;3\r\n".encode("utf-8") + table = parse_multi_header_csv_bytes( + data, skiprows=0, header_rows=1, label_columns=0, value_type=ColumnType.INTEGER + ) + assert table.schema.names == ["A", "A (2)", "A (3)"] + + +def test_multi_header_empty_measure_is_null() -> None: + data = "Region;Votes\r\nBerlin;\r\nHamburg;5\r\n".encode("utf-8") + table = parse_multi_header_csv_bytes( + data, skiprows=0, header_rows=1, label_columns=1, value_type=ColumnType.INTEGER + ) + assert table.column("Votes").to_pylist() == [None, 5] diff --git a/mloda_plugin_govdata/feature_groups/govdata/tests/test_parse_hypothesis.py b/mloda_plugin_govdata/feature_groups/govdata/tests/test_parse_hypothesis.py new file mode 100644 index 0000000..2e5f8f8 --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/tests/test_parse_hypothesis.py @@ -0,0 +1,38 @@ +"""Level 1: property-based round-trip invariants for the German-CSV parser.""" + +from hypothesis import given +from hypothesis import strategies as st + +from mloda_plugin_govdata.feature_groups.govdata.parse import ColumnType, parse_german_csv_bytes + + +def _german_thousands(value: int) -> str: + """Format an int with '.' as the German thousands separator (e.g. 1234 -> '1.234').""" + return f"{value:,}".replace(",", ".") + + +@given(values=st.lists(st.integers(min_value=0, max_value=10_000_000), min_size=1, max_size=40)) +def test_integer_thousands_roundtrip(values: list[int]) -> None: + lines = ["Wert", *[_german_thousands(v) for v in values]] + data = ("\r\n".join(lines) + "\r\n").encode("cp1252") + table = parse_german_csv_bytes(data, {"Wert": ColumnType.INTEGER}) + assert table.column("Wert").to_pylist() == values + + +_LABELS = ( + st.text( + alphabet="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 äöüÄÖÜß.-", + min_size=1, + max_size=20, + ) + .map(str.strip) + .filter(lambda s: s != "") +) + + +@given(labels=st.lists(_LABELS, min_size=1, max_size=20)) +def test_string_cp1252_roundtrip(labels: list[str]) -> None: + lines = ["Gebiet", *labels] + data = ("\r\n".join(lines) + "\r\n").encode("cp1252") + table = parse_german_csv_bytes(data, {"Gebiet": ColumnType.STRING}) + assert table.column("Gebiet").to_pylist() == labels diff --git a/mloda_plugin_govdata/feature_groups/govdata/tests/test_reader.py b/mloda_plugin_govdata/feature_groups/govdata/tests/test_reader.py new file mode 100644 index 0000000..0e634ba --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/tests/test_reader.py @@ -0,0 +1,107 @@ +"""Level 2 (recorded) and Level 3 (live) tests for the GovData reader.""" + +import json +from pathlib import Path + +import httpx +import pyarrow as pa +import pytest +import respx + +from mloda.user import Feature, mloda + +from mloda_plugin_govdata.feature_groups.govdata.reader import ( + BundeswahlleiterinReader, + GovDataFeature, + GovDataReader, +) + +SLUG = "einwohner-nach-altersgruppen-und-stadtbezirken" +PACKAGE_SHOW = "https://ckan.govdata.de/api/3/action/package_show" +KERG_URL = "https://www.bundeswahlleiterin.de/bundestagswahlen/2025/ergebnisse/opendata/btw25/csv/kerg.csv" +KERG_MEASURE = "Wahlberechtigte Erststimmen Endgültig" + + +def test_feature_group_uses_govdata_reader() -> None: + assert isinstance(GovDataFeature.input_data(), GovDataReader) + + +@respx.mock +def test_load_data_level2(fixtures_dir: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(GovDataReader, "cache_dir", str(tmp_path)) + package_show = (fixtures_dir / "package_show.json").read_text(encoding="utf-8") + csv_bytes = (fixtures_dir / "population_sample.csv").read_bytes() + distribution_url = json.loads(package_show)["result"]["resources"][0]["url"] + + respx.get(PACKAGE_SHOW).mock(return_value=httpx.Response(200, text=package_show)) + respx.get(distribution_url).mock(return_value=httpx.Response(200, content=csv_bytes, headers={"ETag": '"v1"'})) + + result = mloda.run_all( + [ + Feature("Einwohner", options={GovDataReader.__name__: SLUG}), + Feature("Stadtbezirk", options={GovDataReader.__name__: SLUG}), + ], + compute_frameworks=["PyArrowTable"], + ) + table = result[0] + assert set(table.schema.names) == {"Einwohner", "Stadtbezirk"} + assert table.num_rows == 1000 + assert table.schema.field("Einwohner").type == pa.int64() + + +@respx.mock +def test_resolves_without_explicit_compute_framework( + fixtures_dir: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + # Regression: GovDataFeature must not collide with the built-in ReadFileFeature, + # so a run_all call with no compute_frameworks pin still resolves to one group. + monkeypatch.setattr(GovDataReader, "cache_dir", str(tmp_path)) + package_show = (fixtures_dir / "package_show.json").read_text(encoding="utf-8") + csv_bytes = (fixtures_dir / "population_sample.csv").read_bytes() + distribution_url = json.loads(package_show)["result"]["resources"][0]["url"] + respx.get(PACKAGE_SHOW).mock(return_value=httpx.Response(200, text=package_show)) + respx.get(distribution_url).mock(return_value=httpx.Response(200, content=csv_bytes, headers={"ETag": '"v1"'})) + + result = mloda.run_all([Feature("Einwohner", options={GovDataReader.__name__: SLUG})]) + assert result[0].num_rows == 1000 + + +@pytest.mark.live +def test_live_end_to_end() -> None: + result = mloda.run_all( + [Feature("Einwohner", options={GovDataReader.__name__: SLUG})], + compute_frameworks=["PyArrowTable"], + ) + table = result[0] + assert table.num_rows > 20_000 + assert table.schema.field("Einwohner").type == pa.int64() + + +@respx.mock +def test_elections_reader_level2(fixtures_dir: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(BundeswahlleiterinReader, "cache_dir", str(tmp_path)) + kerg_bytes = (fixtures_dir / "kerg_sample.csv").read_bytes() + respx.get(KERG_URL).mock(return_value=httpx.Response(200, content=kerg_bytes, headers={"ETag": '"k1"'})) + result = mloda.run_all( + [ + Feature("Gebiet", options={BundeswahlleiterinReader.__name__: KERG_URL}), + Feature(KERG_MEASURE, options={BundeswahlleiterinReader.__name__: KERG_URL}), + ], + compute_frameworks=["PyArrowTable"], + ) + table = result[0] + assert set(table.schema.names) == {"Gebiet", KERG_MEASURE} + assert table.num_rows == 16 + assert table.column("Gebiet").to_pylist()[0] == "Flensburg – Schleswig" + assert table.schema.field(KERG_MEASURE).type == pa.int64() + + +@pytest.mark.live +def test_elections_live_end_to_end() -> None: + result = mloda.run_all( + [Feature("Gebiet", options={BundeswahlleiterinReader.__name__: KERG_URL})], + compute_frameworks=["PyArrowTable"], + ) + table = result[0] + assert table.num_rows > 300 + assert table.column("Gebiet").to_pylist()[-1] == "Bundesgebiet" diff --git a/mloda_plugin_govdata/feature_groups/govdata/tests/test_uba.py b/mloda_plugin_govdata/feature_groups/govdata/tests/test_uba.py new file mode 100644 index 0000000..179091f --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/tests/test_uba.py @@ -0,0 +1,194 @@ +"""Tests for the UBA Air Data reader: Level 1 (flatten / URL), Level 2 (recorded), Level 3 (live).""" + +import json +from pathlib import Path +from typing import Any + +import httpx +import pyarrow as pa +import pytest +import respx +from hypothesis import given +from hypothesis import strategies as st + +from mloda.user import Feature, mloda + +from mloda_plugin_govdata.feature_groups.govdata.reader import UbaAirReader +from mloda_plugin_govdata.feature_groups.govdata.uba import ( + UBA_AIR_BASE, + parse_uba_measures_bytes, + uba_measures_url, +) + +MEASURE_COLUMNS = ["station_id", "date_start", "component_id", "scope_id", "value", "date_end", "index"] +INDICES = {"data": {"station id": {"date start": ["component id", "scope id", "value", "date end", "index"]}}} + + +def _demo_url() -> str: + return uba_measures_url(station=143, component=3, scope=2, date_from="2025-01-01", date_to="2025-01-01") + + +# --- Level 1: URL builder --------------------------------------------------------------- + + +def test_uba_measures_url_is_exact_and_ordered() -> None: + # Assert the full string: param order is fixed, which keeps the cache key stable. + expected = ( + f"{UBA_AIR_BASE}/measures/json?" + "date_from=2025-01-01&time_from=1&date_to=2025-01-01&time_to=24" + "&station=143&component=3&scope=2&lang=en" + ) + assert _demo_url() == expected + + +# --- Level 1: flatten ------------------------------------------------------------------- + + +def test_flatten_real_fixture(fixtures_dir: Path) -> None: + table = parse_uba_measures_bytes((fixtures_dir / "uba_measures.json").read_bytes()) + assert table.schema.names == MEASURE_COLUMNS + assert table.num_rows == 24 + assert table.column("station_id").to_pylist()[0] == 143 + assert table.column("date_start").to_pylist()[0] == "2025-01-01 00:00:00" + assert table.column("date_end").to_pylist()[0] == "2025-01-01 01:00:00" + assert table.column("component_id").to_pylist()[0] == 3 + assert table.column("scope_id").to_pylist()[0] == 2 + assert table.column("value").to_pylist()[0] == 37.0 + assert table.column("index").to_pylist()[0] == 2 + assert table.schema.field("station_id").type == pa.int64() + assert table.schema.field("value").type == pa.float64() + + +def test_flatten_multi_station() -> None: + payload = { + "indices": INDICES, + "data": { + "143": {"2025-01-01 00:00:00": [3, 2, 37, "2025-01-01 01:00:00", "2"]}, + "144": {"2025-01-01 00:00:00": [3, 2, 40, "2025-01-01 01:00:00", "3"]}, + }, + } + table = parse_uba_measures_bytes(json.dumps(payload).encode()) + assert table.num_rows == 2 + assert set(table.column("station_id").to_pylist()) == {143, 144} + + +def test_flatten_handles_null_value_and_index() -> None: + payload = {"indices": INDICES, "data": {"143": {"2025-01-01 00:00:00": [3, 2, None, "2025-01-01 01:00:00", None]}}} + table = parse_uba_measures_bytes(json.dumps(payload).encode()) + assert table.column("value").to_pylist() == [None] + assert table.column("index").to_pylist() == [None] + + +def test_flatten_without_indices_uses_canonical_schema() -> None: + # The schema is canonical and does not depend on the response self-describing its layout. + payload = {"data": {"143": {"2025-01-01 00:00:00": [3, 2, 37, "2025-01-01 01:00:00", "2"]}}} + table = parse_uba_measures_bytes(json.dumps(payload).encode()) + assert table.schema.names == MEASURE_COLUMNS + assert table.column("value").to_pylist() == [37.0] + + +def test_flatten_keeps_canonical_names_for_localized_labels() -> None: + # Even if the server sends localized labels, the public column names stay canonical. + localized = {"data": {"Stationskennung": {"Startdatum": ["Komponente", "Bereich", "Wert", "Enddatum", "Index"]}}} + payload = {"indices": localized, "data": {"143": {"2025-01-01 00:00:00": [3, 2, 37, "2025-01-01 01:00:00", "2"]}}} + table = parse_uba_measures_bytes(json.dumps(payload).encode()) + assert table.schema.names == MEASURE_COLUMNS + assert table.schema.field("value").type == pa.float64() + + +def test_flatten_rejects_changed_leaf_layout() -> None: + # A different leaf width would shift columns under positional mapping; reject it loudly. + changed = {"data": {"station id": {"date start": ["component id", "scope id", "value", "date end"]}}} + payload = {"indices": changed, "data": {"143": {"2025-01-01 00:00:00": [3, 2, 37, "2025-01-01 01:00:00"]}}} + with pytest.raises(ValueError): + parse_uba_measures_bytes(json.dumps(payload).encode()) + + +def test_flatten_rejects_payload_without_data() -> None: + with pytest.raises(ValueError): + parse_uba_measures_bytes(b'{"request": {}}') + + +def test_flatten_empty_data_yields_zero_rows() -> None: + table = parse_uba_measures_bytes(json.dumps({"indices": INDICES, "data": {}}).encode()) + assert table.num_rows == 0 + assert table.schema.names == MEASURE_COLUMNS + + +def test_flatten_skips_non_dict_station() -> None: + payload = { + "indices": INDICES, + "data": {"143": {"2025-01-01 00:00:00": [3, 2, 37, "2025-01-01 01:00:00", "2"]}, "999": None}, + } + table = parse_uba_measures_bytes(json.dumps(payload).encode()) + assert table.column("station_id").to_pylist() == [143] + + +def test_flatten_pads_short_leaf() -> None: + payload = {"data": {"143": {"2025-01-01 00:00:00": [3, 2, 37]}}} + table = parse_uba_measures_bytes(json.dumps(payload).encode()) + assert table.column("value").to_pylist() == [37.0] + assert table.column("date_end").to_pylist() == [None] + assert table.column("index").to_pylist() == [None] + + +def test_flatten_rejects_non_numeric_station_id() -> None: + # station_id is typed integer; a non-numeric key fails loudly rather than silently mistyping. + payload = {"data": {"DEBW118": {"2025-01-01 00:00:00": [3, 2, 37, "2025-01-01 01:00:00", "2"]}}} + with pytest.raises(ValueError): + parse_uba_measures_bytes(json.dumps(payload).encode()) + + +_LEAF = st.just([3, 2, 10, "2025-01-01 01:00:00", "1"]) +_SERIES = st.dictionaries( + keys=st.integers(min_value=0, max_value=23).map(lambda h: f"2025-01-01 {h:02d}:00:00"), + values=_LEAF, + max_size=24, +) +_STATIONS = st.dictionaries(keys=st.integers(min_value=1, max_value=999).map(str), values=_SERIES, max_size=4) + + +@given(stations=_STATIONS) +def test_flatten_row_count_matches_leaves(stations: dict[str, dict[str, list[Any]]]) -> None: + payload = {"indices": INDICES, "data": stations} + table = parse_uba_measures_bytes(json.dumps(payload).encode()) + assert table.num_rows == sum(len(series) for series in stations.values()) + + +# --- Level 2: recorded reader (no network) ---------------------------------------------- + + +@respx.mock +def test_uba_reader_level2(fixtures_dir: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(UbaAirReader, "cache_dir", str(tmp_path)) + measures_bytes = (fixtures_dir / "uba_measures.json").read_bytes() + url = _demo_url() + respx.get(url).mock(return_value=httpx.Response(200, content=measures_bytes)) + + result = mloda.run_all( + [ + Feature("value", options={UbaAirReader.__name__: url}), + Feature("date_start", options={UbaAirReader.__name__: url}), + ], + compute_frameworks=["PyArrowTable"], + ) + table = result[0] + assert set(table.schema.names) == {"value", "date_start"} + assert table.num_rows == 24 + assert table.schema.field("value").type == pa.float64() + assert table.column("value").to_pylist()[0] == 37.0 + + +# --- Level 3: live (deselected by default) ---------------------------------------------- + + +@pytest.mark.live +def test_uba_live_end_to_end() -> None: + url = _demo_url() + result = mloda.run_all( + [Feature("value", options={UbaAirReader.__name__: url})], + compute_frameworks=["PyArrowTable"], + ) + table = result[0] + assert table.num_rows >= 20 + assert table.schema.field("value").type == pa.float64() diff --git a/mloda_plugin_govdata/feature_groups/govdata/uba.py b/mloda_plugin_govdata/feature_groups/govdata/uba.py new file mode 100644 index 0000000..9f6e024 --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/uba.py @@ -0,0 +1,193 @@ +"""Umweltbundesamt (UBA) Air Data v4 ``measures`` endpoint: URL builder and JSON flatten. + +The environment dataset is publisher-direct REST JSON, not a CSV distribution, so it +has a distinct shape from the GovData / Bundeswahlleiterin CSV readers. The response is +``{request, indices, data}`` where ``data`` is keyed by station then by measurement +start datetime, and each leaf is ``[component id, scope id, value, date end, index]``. +The leaf order is fixed by the v4 contract, so the flatten maps it to a canonical schema by +position and uses the self-describing ``indices.data`` block only to detect a changed layout. +""" + +from __future__ import annotations + +import json +import os +from typing import Any +from urllib.parse import urlencode + +import pyarrow as pa + +from .parse import ColumnType + +UBA_AIR_BASE = "https://luftdaten.umweltbundesamt.de/api/air-data/v4" + +# Canonical, stable output schema. The v4 measures leaf array is fixed by the API contract as +# [component id, scope id, value, date end, index]; the outer keys prepend the station id and the +# measurement start datetime. Names are canonical (not read from the response) so feature selection +# and column typing never depend on server-side labels, which can be revised or localized. The +# response's self-describing ``indices.data`` block is used only to detect a changed leaf width. +MEASURE_COLUMNS: tuple[str, ...] = ( + "station_id", + "date_start", + "component_id", + "scope_id", + "value", + "date_end", + "index", +) + +# Fields in each leaf array (the columns after the station id and start datetime). +_VALUE_FIELD_COUNT = len(MEASURE_COLUMNS) - 2 + +# Column types; value is a float so a component reporting fractional concentrations is not +# truncated, ids and the air-quality index are integers, and the datetimes stay ISO strings. +MEASURE_COLUMN_TYPES: dict[str, ColumnType] = { + "station_id": ColumnType.INTEGER, + "date_start": ColumnType.STRING, + "component_id": ColumnType.INTEGER, + "scope_id": ColumnType.INTEGER, + "value": ColumnType.FLOAT, + "date_end": ColumnType.STRING, + "index": ColumnType.INTEGER, +} + +_ARROW_TYPE: dict[ColumnType, pa.DataType] = { + ColumnType.STRING: pa.string(), + ColumnType.INTEGER: pa.int64(), + ColumnType.FLOAT: pa.float64(), +} + + +def uba_measures_url( + *, + station: int | str, + component: int | str, + scope: int | str, + date_from: str, + date_to: str, + time_from: int = 1, + time_to: int = 24, + lang: str = "en", + base: str = UBA_AIR_BASE, +) -> str: + """Build a UBA Air Data v4 ``measures`` URL. + + Dates are ``YYYY-MM-DD``; ``time_from``/``time_to`` are hour slots 1-24. Parameter + order is fixed so the same query always yields the same URL (a stable cache key). + """ + params = { + "date_from": date_from, + "time_from": time_from, + "date_to": date_to, + "time_to": time_to, + "station": station, + "component": component, + "scope": scope, + "lang": lang, + } + return f"{base}/measures/json?{urlencode(params)}" + + +def _check_layout(payload: dict[str, Any]) -> None: + """Fail loud if the response self-describes a leaf width other than the fixed one. + + ``indices.data`` is ``{: {: []}}``. Leaves are + mapped by position, so a changed field count would silently shift columns; detect it instead. + A same-width reordering is not detected; the v4 leaf order is treated as a stable contract. + """ + indices = payload.get("indices") + if not isinstance(indices, dict): + return + outer = indices.get("data") + if not isinstance(outer, dict) or not outer: + return + inner = outer[next(iter(outer))] + if not isinstance(inner, dict) or not inner: + return + value_labels = inner[next(iter(inner))] + if isinstance(value_labels, list) and len(value_labels) != _VALUE_FIELD_COUNT: + raise ValueError( + f"UBA measures leaf layout changed: expected {_VALUE_FIELD_COUNT} value fields, " + f"got {len(value_labels)}: {value_labels}" + ) + + +def _to_int(value: Any) -> int | None: + if value is None or value == "": + return None + try: + return int(value) + except (TypeError, ValueError): + raise ValueError(f"expected an integer, got {value!r}") from None + + +def _to_float(value: Any) -> float | None: + if value is None or value == "": + return None + try: + return float(value) + except (TypeError, ValueError): + raise ValueError(f"expected a float, got {value!r}") from None + + +def _to_string(value: Any) -> str | None: + if value is None: + return None + text = str(value).strip() + return text or None + + +def _typed_table(columns: dict[str, list[Any]], names: list[str]) -> pa.Table: + arrays: dict[str, pa.Array] = {} + fields: list[pa.Field] = [] + for name in names: + ctype = MEASURE_COLUMN_TYPES.get(name, ColumnType.STRING) + raw = columns[name] + if ctype is ColumnType.INTEGER: + values: list[object] = [_to_int(v) for v in raw] + elif ctype is ColumnType.FLOAT: + values = [_to_float(v) for v in raw] + else: + values = [_to_string(v) for v in raw] + arrays[name] = pa.array(values, type=_ARROW_TYPE[ctype]) + fields.append(pa.field(name, _ARROW_TYPE[ctype])) + return pa.table(arrays, schema=pa.schema(fields)) + + +def parse_uba_measures_bytes(data: bytes) -> pa.Table: + """Flatten a UBA ``measures`` JSON response into a typed Arrow table (one row per reading). + + One row per (station, measurement start) pair. Missing leaf cells and ``null`` values become + nulls. Raises ``ValueError`` if the payload has no ``data`` object or self-describes a leaf + width other than the expected one. + """ + payload = json.loads(data) + if not isinstance(payload, dict): + raise ValueError("UBA measures payload is not a JSON object") + series_by_station = payload.get("data") + if not isinstance(series_by_station, dict): + raise ValueError("UBA measures payload has no 'data' object") + _check_layout(payload) + + station_col, datetime_col = MEASURE_COLUMNS[0], MEASURE_COLUMNS[1] + value_cols = MEASURE_COLUMNS[2:] + columns: dict[str, list[Any]] = {name: [] for name in MEASURE_COLUMNS} + + for station_id, series in series_by_station.items(): + if not isinstance(series, dict): + continue + for date_start, leaf in series.items(): + cells = list(leaf) if isinstance(leaf, list) else [] + columns[station_col].append(station_id) + columns[datetime_col].append(date_start) + for i, col in enumerate(value_cols): + columns[col].append(cells[i] if i < len(cells) else None) + + return _typed_table(columns, list(MEASURE_COLUMNS)) + + +def parse_uba_measures(path: str | os.PathLike[str]) -> pa.Table: + """Read ``path`` and flatten it with :func:`parse_uba_measures_bytes`.""" + with open(path, "rb") as handle: + data = handle.read() + return parse_uba_measures_bytes(data) diff --git a/mloda_plugin_govdata/feature_groups/my_plugin/__init__.py b/mloda_plugin_govdata/feature_groups/my_plugin/__init__.py deleted file mode 100644 index 488aa1d..0000000 --- a/mloda_plugin_govdata/feature_groups/my_plugin/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""My Plugin FeatureGroup package.""" - -from mloda_plugin_govdata.feature_groups.my_plugin.my_feature_group import MyFeatureGroup - -__all__ = ["MyFeatureGroup"] diff --git a/mloda_plugin_govdata/feature_groups/my_plugin/my_feature_group.py b/mloda_plugin_govdata/feature_groups/my_plugin/my_feature_group.py deleted file mode 100644 index 4c36e54..0000000 --- a/mloda_plugin_govdata/feature_groups/my_plugin/my_feature_group.py +++ /dev/null @@ -1,14 +0,0 @@ -"""Example FeatureGroup implementation.""" - -from typing import Any - -from mloda.provider import FeatureGroup - - -class MyFeatureGroup(FeatureGroup): - """Example FeatureGroup - rename and customize for your use case.""" - - @classmethod - def calculate_feature(cls, data: Any, features: Any) -> Any: - """Calculate and return feature data.""" - return {"example": "data"} diff --git a/mloda_plugin_govdata/feature_groups/my_plugin/tests/__init__.py b/mloda_plugin_govdata/feature_groups/my_plugin/tests/__init__.py deleted file mode 100644 index 3e05d09..0000000 --- a/mloda_plugin_govdata/feature_groups/my_plugin/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for My Plugin FeatureGroup.""" diff --git a/mloda_plugin_govdata/feature_groups/my_plugin/tests/test_my_feature_group.py b/mloda_plugin_govdata/feature_groups/my_plugin/tests/test_my_feature_group.py deleted file mode 100644 index f432b1d..0000000 --- a/mloda_plugin_govdata/feature_groups/my_plugin/tests/test_my_feature_group.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Tests for MyFeatureGroup.""" - -from mloda_plugin_govdata.feature_groups.my_plugin import MyFeatureGroup -from mloda.provider import FeatureGroup - - -def test_extends_base() -> None: - """MyFeatureGroup should extend FeatureGroup.""" - assert issubclass(MyFeatureGroup, FeatureGroup) - - -def test_calculate_feature() -> None: - """calculate_feature should return example data.""" - result = MyFeatureGroup.calculate_feature(None, None) - assert result == {"example": "data"} diff --git a/pyproject.toml b/pyproject.toml index b959a4e..603972f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,15 @@ version = "0.3.4" description = "mloda plugin providing connectors and harmonization for German government data (GovData.de CKAN API, Destatis GENESIS API v3)." license = "Apache-2.0" authors = [{ name = "Tom Kaltofen", email = "tomkaltofen@mloda.ai" }] -dependencies = ["mloda>=0.8.0", "mloda-testing>=0.3.1"] +dependencies = [ + "mloda>=0.8.0", + "mloda-testing>=0.3.1", + "httpx>=0.27", + "pandas>=2.0", + "pyarrow>=17.0", + "pydantic>=2.0", + "tenacity>=8.0", +] requires-python = ">=3.10" [project.optional-dependencies] @@ -22,6 +30,9 @@ dev = [ "ruff", "mypy", "bandit", + "hypothesis", + "respx", + "pandas-stubs", ] [tool.setuptools.packages.find] @@ -31,6 +42,8 @@ include = ["mloda_plugin_govdata*"] [tool.pytest.ini_options] testpaths = ["mloda_plugin_govdata", "tests"] python_files = ["test_*.py"] +markers = ["live: tests that reach the live GovData / publisher network (deselected by default)"] +addopts = "-m 'not live'" [tool.ruff] line-length = 120 diff --git a/uv.lock b/uv.lock index 13b0956..6c386ff 100644 --- a/uv.lock +++ b/uv.lock @@ -2,8 +2,19 @@ version = 1 revision = 3 requires-python = ">=3.10" resolution-markers = [ - "python_full_version >= '3.15'", - "python_full_version < '3.15'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.11'", ] [options] @@ -15,6 +26,29 @@ mloda-testing = "2027-01-01T00:00:00Z" mloda = "2027-01-01T00:00:00Z" mloda-registry = "2027-01-01T00:00:00Z" +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/b5/001890774a9552aff22502b8da382593109ce0c95314abaebbb116567545/anyio-4.14.0.tar.gz", hash = "sha256:b47c1f9ccf73e67021df785332508f99379c68fa7d0684e8e3492cb1d4b23f89", size = 253586, upload-time = "2026-06-15T22:00:49.021Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/16/9826f089383c593cdfc4a6e5aca94d9e91ae1692c57af82c3b2aa5e810f7/anyio-4.14.0-py3-none-any.whl", hash = "sha256:dd9b7a2a9799ed6552fde617b2c5df02b7fdd7d88392fc48101e51bae46164d9", size = 123506, upload-time = "2026-06-15T22:00:47.595Z" }, +] + [[package]] name = "ast-serialize" version = "0.5.0" @@ -79,6 +113,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/06/f3/39cf3367b8107baa44f861dc802cbf16263c945b62d8265d36034fc07bea/cachetools-7.0.5-py3-none-any.whl", hash = "sha256:46bc8ebefbe485407621d0a4264b23c080cedd913921bad7ac3ed2f26c183114", size = 13918, upload-time = "2026-03-09T20:51:27.33Z" }, ] +[[package]] +name = "certifi" +version = "2026.6.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -102,7 +145,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -118,6 +161,65 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70", size = 26759, upload-time = "2026-03-11T20:45:37.437Z" }, ] +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "hypothesis" +version = "6.155.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "sortedcontainers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/55/983b6bc1b6b343a5ff6020388f9d0680ab477be59a731517e6c4a0387100/hypothesis-6.155.7.tar.gz", hash = "sha256:d8d6091753d0669db3c90c5e5b346cb37c72f3dd9378c8413acb1fd5da63f7ea", size = 478291, upload-time = "2026-06-21T05:54:31.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/f8/c151e196d4f397ed9436a071e52666c70a2f021138dea828b0a461e245db/hypothesis-6.155.7-py3-none-any.whl", hash = "sha256:9f634bdb1f9e9b8ab6ba09431cf2deedb750c96978125a6fb3c5a0f6c6db4131", size = 544762, upload-time = "2026-06-21T05:54:29.506Z" }, +] + +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, +] + [[package]] name = "iniconfig" version = "2.3.0" @@ -246,15 +348,25 @@ name = "mloda-plugin-govdata" version = "0.3.4" source = { editable = "." } dependencies = [ + { name = "httpx" }, { name = "mloda" }, { name = "mloda-testing" }, + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "pandas", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pyarrow" }, + { name = "pydantic" }, + { name = "tenacity" }, ] [package.optional-dependencies] dev = [ { name = "bandit" }, + { name = "hypothesis" }, { name = "mypy" }, + { name = "pandas-stubs", version = "2.3.3.260113", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "pandas-stubs", version = "3.0.3.260530", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pytest" }, + { name = "respx" }, { name = "ruff" }, { name = "tox" }, { name = "tox-uv" }, @@ -264,11 +376,19 @@ dev = [ [package.metadata] requires-dist = [ { name = "bandit", marker = "extra == 'dev'" }, + { name = "httpx", specifier = ">=0.27" }, + { name = "hypothesis", marker = "extra == 'dev'" }, { name = "mloda", specifier = ">=0.8.0" }, { name = "mloda-testing", specifier = ">=0.3.1" }, { name = "mypy", marker = "extra == 'dev'" }, + { name = "pandas", specifier = ">=2.0" }, + { name = "pandas-stubs", marker = "extra == 'dev'" }, + { name = "pyarrow", specifier = ">=17.0" }, + { name = "pydantic", specifier = ">=2.0" }, { name = "pytest", marker = "extra == 'dev'" }, + { name = "respx", marker = "extra == 'dev'" }, { name = "ruff", marker = "extra == 'dev'" }, + { name = "tenacity", specifier = ">=8.0" }, { name = "tox", marker = "extra == 'dev'" }, { name = "tox-uv", marker = "extra == 'dev'" }, { name = "uv", marker = "extra == 'dev'", specifier = ">=0.11.15" }, @@ -355,6 +475,217 @@ 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 = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" }, + { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" }, + { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" }, + { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" }, + { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" }, + { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" }, + { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" }, + { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, + { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, + { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, + { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, + { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, + { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, + { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, + { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, + { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, + { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, + { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, + { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, + { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, + { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, + { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, + { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, + { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, + { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, + { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, + { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, + { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" }, + { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, +] + +[[package]] +name = "numpy" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/05/3d27272d30698dc0ecb7fdfaa41ad70303b444f81722bb99bce1d818638a/numpy-2.5.0.tar.gz", hash = "sha256:5a129578019311b6e56bdd714250f19b518f7dceeeb8d1af5490f4942d3f891c", size = 20652461, upload-time = "2026-06-21T20:57:51.95Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/0a/11486d02add7b1384dff7374d124b1cfbb0ee864dcc9f6a2c0380638cf84/numpy-2.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:489780423903667933b4ed6197b6ec3b75ea5dd17d1d8f0f38d798feb6921561", size = 16789987, upload-time = "2026-06-21T20:56:16.657Z" }, + { url = "https://files.pythonhosted.org/packages/55/b2/285f48640a181947b4587a3766d21ec1eaa7fea833d4b49957e09da467a2/numpy-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ece55976ced6bca95a03ae2839e2e5ccffe8eb6a3e7022415645eb154a81e4e6", size = 11760322, upload-time = "2026-06-21T20:56:19.813Z" }, + { url = "https://files.pythonhosted.org/packages/dd/67/b032db1eb03ca30d16eda3b0c22aaa615338b9263c2fd559d0f29451aca4/numpy-2.5.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:c83b664b0e6eee9594fa920cf0639d8af796606d3fad6cc70180c87e4b97c7be", size = 5319605, upload-time = "2026-06-21T20:56:22.173Z" }, + { url = "https://files.pythonhosted.org/packages/b9/83/03fc7300c7c6b6c84c487b1dc80d322817b95fbd1f4dd57a85e23b7198de/numpy-2.5.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bf80333980bf37f523341ddd72c783f39d6829ec7736b9eb99086388a2d52cc2", size = 6653628, upload-time = "2026-06-21T20:56:23.914Z" }, + { url = "https://files.pythonhosted.org/packages/82/49/2ec21730bc63ccfda829323f7040a8ed4715b3852ce658689cf74ee96a8c/numpy-2.5.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1a4874217b36d5ac8fc876f52e39df56f8182c88463e9e2dceabf7ca8b7efb8", size = 15153691, upload-time = "2026-06-21T20:56:25.631Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6b/f4a3d0637692c49da8ef99d72d52526f92e0a8d6ac4f0ca9f31441b9d9ea/numpy-2.5.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaa760137137e8d3c920d27927748215b56014f92667dc9b6c27dfc61249255a", size = 16660066, upload-time = "2026-06-21T20:56:28.009Z" }, + { url = "https://files.pythonhosted.org/packages/3a/2f/c354ec86d1f3f5c19649463b0d39652e160736e5b0a4cd18dff0576715c4/numpy-2.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7174ce8265fc7f7417d171c9ea8fe905220748893ea67a2a7abe726ec331c4b0", size = 16514638, upload-time = "2026-06-21T20:56:30.26Z" }, + { url = "https://files.pythonhosted.org/packages/06/34/43efdcb319988648580f93c11f1ae82cf7e2faa74925e98e454ae3aa95f8/numpy-2.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b8c3daaf99de52415d20b42f8e8155c78642cb04207d02f9d317a0dcf1b3fb54", size = 18419647, upload-time = "2026-06-21T20:56:32.41Z" }, + { url = "https://files.pythonhosted.org/packages/71/e2/f5d1676b1d7fb682eb5e9a1641e7ebd2414b3216c370661d1029778908b4/numpy-2.5.0-cp312-cp312-win32.whl", hash = "sha256:6206db0af545d73d068add6d992279145f158428d1da6cc49adc4b630c5d6ee5", size = 6056688, upload-time = "2026-06-21T20:56:34.657Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7c/48f115d1c58a34032facebcd51fdf2d02df2c51d4a46a81dd1197bb2ea6b/numpy-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:6f2d6873e2940c860a309d21e25b1e69af6aaffdd80aa056b04c16380db1c4f2", size = 12419237, upload-time = "2026-06-21T20:56:36.24Z" }, + { url = "https://files.pythonhosted.org/packages/86/26/2e0882f4044d1b1a1b63e875151fb2393389032022a8b7f5657a7996d3b2/numpy-2.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:a55e1eb2bca2cfd17a16b213c99dfc8502d47b0d494224d2122277d0400935ca", size = 10339912, upload-time = "2026-06-21T20:56:38.733Z" }, + { url = "https://files.pythonhosted.org/packages/8a/33/07675aaad7f26ea013d5e884d9a0d784b79c6bd7566c333f5a52fa3c610b/numpy-2.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:520e6b8be0a4b65840ac8090d4f51cef4bed66e2b0894d5a520f099adc24a9b2", size = 16784890, upload-time = "2026-06-21T20:56:40.799Z" }, + { url = "https://files.pythonhosted.org/packages/85/4b/953118a730ee3b35e28645e0eb4cf9beec5bdbb954e1ac2f5fcefba6bbc3/numpy-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:146b81cdd3967fdb6beca8ba25f00c58741d8f3cbd797f55af0fbe0bfec3469c", size = 11754584, upload-time = "2026-06-21T20:56:43.094Z" }, + { url = "https://files.pythonhosted.org/packages/44/9b/56dd530c367c74ae17411027cea4135ca57e1e0583bf5594cee18bd83217/numpy-2.5.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:126b88d95e8ff9b00c9e717aa540469f21d6180162f84c0caec51b16215d49cd", size = 5313904, upload-time = "2026-06-21T20:56:45.503Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b0/bcd672edad27ecca7da1f7bb0ce72cd1706a4f2d79ae94990afc97c13e1c/numpy-2.5.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d4313cef1594c5ce46c31b6e54e918338f63f16ee9322304e8c9114d6d81c8bd", size = 6648504, upload-time = "2026-06-21T20:56:47.567Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/15cdfcbd30a1544a46c9e487a00df331c4672450216538705a9e51fa6710/numpy-2.5.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:750fb097caf26fa878746d9d119f6f9da12dedcbff1eea966c3e3447647c4a9e", size = 15150086, upload-time = "2026-06-21T20:56:49.352Z" }, + { url = "https://files.pythonhosted.org/packages/32/4e/8d7656ccaab3e81e97258b8a9bc5f0c8502513a92fb4ceb0a2cbfebc17bf/numpy-2.5.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3893adc2dc7c0412ba76777db55a049215d99c9aa3113003be8f49f4f1290ab9", size = 16647250, upload-time = "2026-06-21T20:56:51.542Z" }, + { url = "https://files.pythonhosted.org/packages/3c/81/97060281b602ed07f21b12f4ec409eac1f75a2f91fbc829ed8b2becf3ad4/numpy-2.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:835e454dd99b238cdc5a3f63bce2371296f5ebc53ca1e0f8e6ddbb6d92a29aab", size = 16512864, upload-time = "2026-06-21T20:56:55.401Z" }, + { url = "https://files.pythonhosted.org/packages/33/ab/4496208146911f8d8ddb54f68a972aafa6c8d44babcb2ea03b0e5cc87c9d/numpy-2.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f9836778081a0a3c02a6a21493f3e9f5b311f8d2541934f31f05583dc999ea4", size = 18408407, upload-time = "2026-06-21T20:56:57.75Z" }, + { url = "https://files.pythonhosted.org/packages/d4/9f/a4df67c181e4ee8b467aa3332dc2db10fd5c515136831302f3ca48bc0a01/numpy-2.5.0-cp313-cp313-win32.whl", hash = "sha256:0b525be4744b60bb0557ac872d53ef07d085b5f39622bc579c98d3809d05b988", size = 6054431, upload-time = "2026-06-21T20:57:00.016Z" }, + { url = "https://files.pythonhosted.org/packages/30/53/491e1c47c55b62ccc6a63c1c5b8635c73fc2258dddeb9bda27cae4a0ae96/numpy-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:44353e2878930039db472b99dc353d749826e4010bd4d2a7f835e94a97a5c748", size = 12414420, upload-time = "2026-06-21T20:57:01.815Z" }, + { url = "https://files.pythonhosted.org/packages/eb/4a/25c2906f541e9d9f4c5769764db732e6627be91a13f4724fa10634d77db4/numpy-2.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:48f54b00711f83a5f796b70c518e8c2b3c5848dda03a54911f23eb68519b9b60", size = 10339533, upload-time = "2026-06-21T20:57:03.961Z" }, + { url = "https://files.pythonhosted.org/packages/86/ad/abc44aaceaf7b17ee1edde2bbb4458da591bc79574cffff50c4bb35f00d1/numpy-2.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f27582c55ba4c750b7c58c8faf021d2cd9324a662b466229db8a417b41368af9", size = 16783807, upload-time = "2026-06-21T20:57:06.253Z" }, + { url = "https://files.pythonhosted.org/packages/5d/39/b72e168daf9c00fb20c9fc996d00437ccecdef3102387775d29d7a62576d/numpy-2.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:28e7137057d551e4a83c4ae414e3451f50568409db7569aacc7f9811ee06a446", size = 11765215, upload-time = "2026-06-21T20:57:08.547Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a0/8400a9c0e3625182347593f5e1f57da9a617a534794805c8df5518154ddc/numpy-2.5.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:e1da54b53e75cd9fcfc23efcc7edab2c6aecf97b6037566d8a0fe804af8ec57c", size = 5324493, upload-time = "2026-06-21T20:57:11.012Z" }, + { url = "https://files.pythonhosted.org/packages/f6/8c/0d104deaa0401c93395a629ec902891618a2eff76d19229139cb5a887bfc/numpy-2.5.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:694d8f74e156f7fd01179f1aa8faa2f648ab6ae0f70b6c3fe57a03249aea2303", size = 6645211, upload-time = "2026-06-21T20:57:12.919Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d9/4a4a628c812750363786afc3d33492709a5cd64b215469c16b0f6c7bb811/numpy-2.5.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a7569a7b53c77716f036bb28cb1c91f166a26ec7d9502cd1e4bdfe502fdec22", size = 15166004, upload-time = "2026-06-21T20:57:14.717Z" }, + { url = "https://files.pythonhosted.org/packages/a0/5e/2a902317d7fc4aa93236e80c932662dadfc459b323d758329e01775125e1/numpy-2.5.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39a0433bd4086ebd462960cf375e19195bb07b53dc1d87dd5fcf47ad78576f03", size = 16650797, upload-time = "2026-06-21T20:57:16.906Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a0/a0090e6329f4ca5992c07847bb579c5259a19953dc57255bb08793142ffb/numpy-2.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:929f0c79ac38bcbd7154fe631dc907abfeddbcc5027a896bd1f7767323271e7a", size = 16524647, upload-time = "2026-06-21T20:57:19.165Z" }, + { url = "https://files.pythonhosted.org/packages/5e/7d/6caf27734c42b65837e7461ed0dbbd6b6fc835060c9714ec59d673bb383a/numpy-2.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cc4f247a47bbf070bfd70be53ccdcf47b800af563535e7bbe172322197c30e21", size = 18411841, upload-time = "2026-06-21T20:57:21.638Z" }, + { url = "https://files.pythonhosted.org/packages/13/dc/26edadbd812536769a82c2e9e002234e33feb5da43061d47a044f6d309b7/numpy-2.5.0-cp314-cp314-win32.whl", hash = "sha256:5dc71423499fab3f46f7a7201155ade1669ea101f2f429d332df9e72f8161731", size = 6106361, upload-time = "2026-06-21T20:57:23.844Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9e/4dd1459282229a72d92dece2ae9138e5cac94a72263a7ceb48f37434c925/numpy-2.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:ebb81d9d5443e0309d6c54894c3fbed74ad7da0714352a67b6d773cd189eae73", size = 12551749, upload-time = "2026-06-21T20:57:25.945Z" }, + { url = "https://files.pythonhosted.org/packages/05/a7/6bc6384c080b86c7f6c85c5bc5b540b24f4f679cd144791d99574e90d462/numpy-2.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:3b94d0d0deceebfad3e67ae5c0e5eb87371e8f7a0581cd04a779928c2450cf1e", size = 10617072, upload-time = "2026-06-21T20:57:28.175Z" }, + { url = "https://files.pythonhosted.org/packages/86/6b/4a2b71d66ada5608ae02b63f150dfad520f6940721cb7f029ad270befc0e/numpy-2.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:22f3d43e362d650bc39db1f17851302874a148ca95ba6981c1dfb5fa6862f35b", size = 11881067, upload-time = "2026-06-21T20:57:30.104Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b2/d365eb40a20efb49d67e9feb90494ed8511282ee1f5fa16006675c65397d/numpy-2.5.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:243563efb4cd7528a264567e9fd206c87826457322521d06206a00bfa316c927", size = 5440290, upload-time = "2026-06-21T20:57:32.193Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5e/e9c03188de5f9b767e46a8fe988bcfd3efad066a4a3fda8b9cb11a93f895/numpy-2.5.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:84881d825ca75249b189bbee875fcfe3238aa5c479e6100893cda566e8e86826", size = 6748371, upload-time = "2026-06-21T20:57:33.933Z" }, + { url = "https://files.pythonhosted.org/packages/fd/1d/68c186a38a5027bae2c4ddd5ea681fdaf8b4d30fb7301def6d8ad270390f/numpy-2.5.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cda12aa4779d42b8771180aba759c96f527d43446d8f380ab59e2b35e8489efd", size = 15214643, upload-time = "2026-06-21T20:57:35.677Z" }, + { url = "https://files.pythonhosted.org/packages/8c/67/73f67b7c7e20635baae9c4c3ead4ae7326a005900297a6110971abd62eb5/numpy-2.5.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c0121101093d2bd74981b10f8837d78e794a8ff57834eb27179f49e1ba11ac6", size = 16690128, upload-time = "2026-06-21T20:57:38.159Z" }, + { url = "https://files.pythonhosted.org/packages/eb/05/d4c1fb0c46d02a27d6b2b8b319a78c90937acec8631c1641874670b31e6f/numpy-2.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d371c92cfa09da00022f501ab67fafaea813d752eb30ac44336d45b1e5b0268a", size = 16577902, upload-time = "2026-06-21T20:57:40.447Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1d/771c797d50fa26e4888989cccf1d50ee51f530d4e455ad2692dcb64fa711/numpy-2.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9990713e9c38154c6861e7547f1e3fc7a87e75ff09bab24ef1cc81d81c2835e9", size = 18452814, upload-time = "2026-06-21T20:57:42.875Z" }, + { url = "https://files.pythonhosted.org/packages/e8/46/52fc0d2a68d7643f0f149eeea5a5d8ea2a3507056ac8afa83c9212606e8b/numpy-2.5.0-cp314-cp314t-win32.whl", hash = "sha256:edadfbd4794b1086c0d822f81863e8a68fc129d132fd0bb9e31e955d7fbbbdb7", size = 6253168, upload-time = "2026-06-21T20:57:45.101Z" }, + { url = "https://files.pythonhosted.org/packages/2a/be/6c8d1118b5f13b2881dc095d5b345de19c6638b8959c17409b6eff84c8aa/numpy-2.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f7e5fa4382967ae6548bd2f174219afb908e294b0d5f625af01166edd5f7d9aa", size = 12736286, upload-time = "2026-06-21T20:57:46.935Z" }, + { url = "https://files.pythonhosted.org/packages/fd/6a/d3a169aaf8536cf228d56a09e04bcb713a2fe4410d4e2105b9419b5a9c89/numpy-2.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:016623417bb330d719d579daf2d6b9a01ddc52e41a9ed61a47f39fde46dcd865", size = 10686451, upload-time = "2026-06-21T20:57:49.313Z" }, +] + [[package]] name = "packaging" version = "26.0" @@ -364,6 +695,188 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "python-dateutil", marker = "python_full_version < '3.11'" }, + { name = "pytz", marker = "python_full_version < '3.11'" }, + { name = "tzdata", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763, upload-time = "2025-09-29T23:16:53.287Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217, upload-time = "2025-09-29T23:17:04.522Z" }, + { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791, upload-time = "2025-09-29T23:17:18.444Z" }, + { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373, upload-time = "2025-09-29T23:17:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" }, + { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, +] + +[[package]] +name = "pandas" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, + { name = "tzdata", marker = "(python_full_version >= '3.11' and sys_platform == 'emscripten') or (python_full_version >= '3.11' and sys_platform == 'win32')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/87/4341c6252d1c47b08768c3d25ac487362bf403f0313ddae4a2a26c9b1b4c/pandas-3.0.3.tar.gz", hash = "sha256:696a4a00a2a2a35d4e5deb3fc946641b96c944f02230e4f76137fe35d806c4fc", size = 4651414, upload-time = "2026-05-11T18:54:29.21Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/16/b5c76b838fd9bf6ce84d3a53346b8874ec05c5f0040d75ef2c320100cd2a/pandas-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:455f6f8139d4282188f526868dbc3c828470e88a3d9d59a891bd46a455f21b98", size = 10338495, upload-time = "2026-05-11T18:52:11.558Z" }, + { url = "https://files.pythonhosted.org/packages/5a/b0/a4ffc4ae74d2d822200dcc46898987d8eb6032d1e2b219cae39da6f5cbcc/pandas-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4e15135e2ee5df1063313e2425ceef8ac0f4ae775893815b0923651b806a5639", size = 9938250, upload-time = "2026-05-11T18:52:17.005Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b2/3323601a52caee42c019e370090ca4544b241437240ca04f786cce82b0cf/pandas-3.0.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05f1f1752b8533ea03f7f39a9c15b1a058d067bb48f4748948e7a8691e0510f2", size = 10770558, upload-time = "2026-05-11T18:52:19.865Z" }, + { url = "https://files.pythonhosted.org/packages/32/f1/bbecd2f867b97abebe0f9b53d750f862251b40337e061b36676ded3d920f/pandas-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a1e45c80cceb3b4a21bc5939d52e8cbd8d9b7305309219d59e9754d9ce09e27", size = 11274611, upload-time = "2026-05-11T18:52:22.622Z" }, + { url = "https://files.pythonhosted.org/packages/7f/4f/eafabf2d5fae5adf143b4d18d3706c5efdc368a7c4eb1ee8a3eddabbd0f6/pandas-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:14da8316da4d0c5a77618425996bfb1248ca87fc2c1486e6fde4652bd18b5824", size = 11784670, upload-time = "2026-05-11T18:52:25.4Z" }, + { url = "https://files.pythonhosted.org/packages/49/44/1eb20389301b57b19cc099a1c2f662501f72f08a65f912d05822613c1532/pandas-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a55066a0505dae0ba2b50a46637db34b46f9094c65c5d4800794ef6335010938", size = 12353708, upload-time = "2026-05-11T18:52:28.139Z" }, + { url = "https://files.pythonhosted.org/packages/eb/62/c321f13b5ba1819fc8dca456c7fce578da2dcfecff1abbf0eaddf8406c0f/pandas-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6674ab18ad8c57802867264b00e15e7bb904700cdd9046e3b2fa1fce237439ea", size = 9907609, upload-time = "2026-05-11T18:52:30.982Z" }, + { url = "https://files.pythonhosted.org/packages/53/85/1b7f563ebc6357c27233a02a96b589bcce1fa9c6eb89fb4f0e56421d277e/pandas-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:5cc09a68b3120e0f54870dede8287a7bb1fa463907e4fcec1ea77cab6179bf7a", size = 9165596, upload-time = "2026-05-11T18:52:33.334Z" }, + { url = "https://files.pythonhosted.org/packages/24/f1/392f8c5bfc16f66a0d2d41561c01627c228fe7ed2a0d056ef11315042570/pandas-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fed2ff7fd9779120e388e285fc029bd5cf9490cdd2e4166a9ee22c0e49a9ab09", size = 10357846, upload-time = "2026-05-11T18:52:36.143Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3d/b16412745651e855f357e5e66930248688378853a6e2698a214e331fba1f/pandas-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b168fc218fd80a6cbdbdbc1a97ddc7889ed057d7eb45f50d866ceab5f39904c4", size = 9899550, upload-time = "2026-05-11T18:52:38.976Z" }, + { url = "https://files.pythonhosted.org/packages/31/a8/fa2535168fffcedf67f4f6de28d2dd903a747ca7c8ea6989451aaeb3a92f/pandas-3.0.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0383c72c75cdcca61a9e116e611143902dbfd08bff356829c2f6d1cf40a9ca8c", size = 10412965, upload-time = "2026-05-11T18:52:41.915Z" }, + { url = "https://files.pythonhosted.org/packages/65/b6/09b01cdbc15224e2850365192d17b7bdebb8bdbd8780ed221fcdf0d9a515/pandas-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6dc0b3fd2169c9157deed50b4d519553a3655c8c6a96027136d654592be973a9", size = 10894600, upload-time = "2026-05-11T18:52:45.02Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a4/2eb28f2fccb4ced4a2c79ab2a5dee9ade1ebf44922ebad6fea158c9f95d4/pandas-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e65d5407dc0b394f509699650e4a2ec01c0514f21850f453fa60f3be79a5dbf", size = 11422824, upload-time = "2026-05-11T18:52:48.058Z" }, + { url = "https://files.pythonhosted.org/packages/f8/45/830bb57f533a4604b355e07edcb8ea18cf88b5f94e5fca92f27052d7c597/pandas-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8894dc474d648fe7b6ff0ca9b0bd73950d19952bc1a6534540762c5d79d305c", size = 11950889, upload-time = "2026-05-11T18:52:50.905Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c5/fc1b368f303087d20e8c9bf3d6ceb186263cfac0ade735cd938538bea839/pandas-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:c7be265b62cef88e253a941e4698604973736dcfe242fdb5198f0f7bc473cdcc", size = 9755463, upload-time = "2026-05-11T18:52:53.386Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/fda8f9705b1b09c6ebe14bfc0fa0e4ec8584d54ea673628f157ff55131af/pandas-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:557409bc4178e70ee8d9ddb494798e51ebf6ea59330f6be22c51bab2a7db6c49", size = 9066158, upload-time = "2026-05-11T18:52:56.038Z" }, + { url = "https://files.pythonhosted.org/packages/c5/90/62d8302883c44308c477e222c3daf7c813a34c8e96985882fbd53d964352/pandas-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:67b3b64c11910cfa29f4e94a14d3bff9ee693b6fc76055e7cad549cee0aec5fa", size = 10331071, upload-time = "2026-05-11T18:52:58.838Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ae/6a6493c783a101f165e4356953ba3c74d6f77f0042fa7d753da9dfbb640c/pandas-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:39436b377d56d2a2e52d0395bdbee171f01068e99af5250509aceeb929f765c7", size = 9875690, upload-time = "2026-05-11T18:53:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/62/7c/5df8e9f56c69a2769fbe9382a5ef8f2658c007e376434e1e2cbb57ad895f/pandas-3.0.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4be06d68f9ddcfc645b87534911da79a8fbffc7573c80e0edcf42a5020624d8", size = 10381634, upload-time = "2026-05-11T18:53:04.393Z" }, + { url = "https://files.pythonhosted.org/packages/99/68/1237369725aa617bb358263d535803e3053fdbc593513ec5ed9c9896b5b6/pandas-3.0.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4eeb6830daf35a71cc09649bd823e2b542dac246cdee9614c6e4bd65028cd6a", size = 10891243, upload-time = "2026-05-11T18:53:07.643Z" }, + { url = "https://files.pythonhosted.org/packages/25/93/77d108e8af7222b4a503ebde0e30215b1c2e4f8e53a526431890f22d5586/pandas-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1928e07221f82db493cd4af1e23c1bfca524a19a4699887975bff68f49a72bfb", size = 11388659, upload-time = "2026-05-11T18:53:10.634Z" }, + { url = "https://files.pythonhosted.org/packages/d0/bd/eff5b4399f332ac386c853f6cd2bd3fa2ca0061b9f36ecd9c4d7c4265649/pandas-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51b1fe551acb77dac643c6fda86084d8d446c10fe64b06a9cc29c4cc8540e7f2", size = 11942880, upload-time = "2026-05-11T18:53:13.536Z" }, + { url = "https://files.pythonhosted.org/packages/2c/20/559ace4200982c3887d0b86bfd0d856a2143ef8ddab63cc07934951a964c/pandas-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:a82d532a3351d435432cd913edbccaf8b8e01d4dd0e5ced5a8d2e8ecd94c7e44", size = 9757091, upload-time = "2026-05-11T18:53:16.306Z" }, + { url = "https://files.pythonhosted.org/packages/3a/66/69055a09fe200f29f922a3eeec4804611900b95f52d932ece3393c3c0c19/pandas-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:275c14e0fce14a2ec20eee474aecd305478ea3c1e6f6a9d8fe219a165542717e", size = 9057282, upload-time = "2026-05-11T18:53:18.768Z" }, + { url = "https://files.pythonhosted.org/packages/57/0e/efe801b0e6811e8e650cd21b7f2608e30f08a7067e2bf6e8752b0d56ee3c/pandas-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:46997386d528eb40376ecd6b033cf4a8a1e5282580f68f43de875b78cba2199d", size = 10767016, upload-time = "2026-05-11T18:53:21.227Z" }, + { url = "https://files.pythonhosted.org/packages/ea/dc/eb55135a1d5f0f0519f28da1f609a206d2cad1f9c35c32d51e38dd7261ae/pandas-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:261e308dfb22448384b7580cf719d2f998fe2966c92893c3e77d14008af1f066", size = 10420210, upload-time = "2026-05-11T18:53:23.982Z" }, + { url = "https://files.pythonhosted.org/packages/c6/3e/b1d5d955ce33ffecb407465a60bc32769d74fcf68224b7ae67ae11d4dea4/pandas-3.0.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd1a5d1def6a46002e964510bdc67c368aa0951df5d1d9f8365336f5a1f490cd", size = 10336126, upload-time = "2026-05-11T18:53:26.731Z" }, + { url = "https://files.pythonhosted.org/packages/f5/76/a01261711ab60a22d71b862f0de20e4c504bf80457270ad8cb42110f6abc/pandas-3.0.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d72828c20c6d6e83e1e22a6a3b47b326b71664112fa9705dcbccfd7a39b62085", size = 10728051, upload-time = "2026-05-11T18:53:29.125Z" }, + { url = "https://files.pythonhosted.org/packages/e9/21/ea191195e587b18cf682e97f433f81b2d0fbe341380e80a3e0d6e4403c8e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d26cbe1fcfc12e8fd900e2454163e466b2d3af84f7c75481df7683ffc073d870", size = 11350796, upload-time = "2026-05-11T18:53:32.056Z" }, + { url = "https://files.pythonhosted.org/packages/64/69/f0eaaf54939f0e8c6768fd06be9af2cef9b36048b96dfb9e1b2c685a807e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e91cec1879ada0624fc3dc9953c5cbd60208e59c0db28f540c5d6d47502422f", size = 11799741, upload-time = "2026-05-11T18:53:34.985Z" }, + { url = "https://files.pythonhosted.org/packages/45/a4/865e0e510cae5fc2194de4db28be638952de942571ba9125934fd9c01d47/pandas-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:08d789b41f87e0905880e293cedf6197ce71fe67cc081358b1e148a491b9bd13", size = 10499958, upload-time = "2026-05-11T18:53:37.857Z" }, + { url = "https://files.pythonhosted.org/packages/86/54/effdcc3c0ff7a08037889200e148ebe94c16c4f653be078c7b3675955df1/pandas-3.0.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3650109c0f22879df8bd6179ab9ee3d7f1d1d4e7e0094a3f0032d9f51e2e64ac", size = 10336065, upload-time = "2026-05-11T18:53:41.099Z" }, + { url = "https://files.pythonhosted.org/packages/68/10/bf2d6738d72748b961a3751ab89522d58c54efc36a8e1a12161216cd45cf/pandas-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bab900348131a7db1f69a7309ef141fd5680f1487094193bcbbb61791573bf8f", size = 9926101, upload-time = "2026-05-11T18:53:43.515Z" }, + { url = "https://files.pythonhosted.org/packages/ae/e9/e35cf11c8a136e757b956f5f0efdcaa50aecde85ea055f1898dfc68262f3/pandas-3.0.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba7e08b9ac1d54569cd1e256e3668975ed624d6826f7b68df0342b012007bddb", size = 10457553, upload-time = "2026-05-11T18:53:46.394Z" }, + { url = "https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d71c63ae4ebdbf70209742096f1fc46a83a0613c99d4b23766cced9ff8cd62a", size = 10914065, upload-time = "2026-05-11T18:53:49.134Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c2/1ef644445fcd72e3627bceec77e3560636f87ddce4ed841afe76b83b5bf9/pandas-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e3a2ec42c98ffa2565a67e08e218d06d72576d758d90facb7c00805194d8f360", size = 11459188, upload-time = "2026-05-11T18:53:52.527Z" }, + { url = "https://files.pythonhosted.org/packages/7e/49/4d8d4f42cbc9c4adc7a1870f269c02cbd6cd40d059622c06fb298addcbad/pandas-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:335f62418ed562cfc3c49e9e196375c28b729dcef8543abf4f9438e381bf3c76", size = 11982966, upload-time = "2026-05-11T18:53:55.043Z" }, + { url = "https://files.pythonhosted.org/packages/38/55/792619469bab9882d8bbd5865d45a72f6478762d04a9af4bf0d08c503e95/pandas-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:3c20a521bbb85902f79f7270c80a59e1b5452d96d170c034f207181870f97ac5", size = 9876755, upload-time = "2026-05-11T18:53:58.067Z" }, + { url = "https://files.pythonhosted.org/packages/2a/af/33c469653b0ba03b50c3a98192d4c07f0c75c66b263ceb097fce0ee97d31/pandas-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:a2d2dff8a04f3917b55ab3910c32990f8ddf7eceba114947838cefa976a68977", size = 9198658, upload-time = "2026-05-11T18:54:00.733Z" }, + { url = "https://files.pythonhosted.org/packages/a2/fa/b8c257bd76b8bd060c3a9151c1fca05e9b9c5e3af5d0f549c0356f6d143d/pandas-3.0.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0d589105b3c14645af1738ff279b2995102d8f7a03b0a66dc8d95550eb513e04", size = 10787242, upload-time = "2026-05-11T18:54:03.564Z" }, + { url = "https://files.pythonhosted.org/packages/54/eb/f19206ffb0bf1919002969aa448b4702c6594845156a6f8050674855aac3/pandas-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:13fc1e853d9e04743d11ba75a985ccbc2a317fe07d8af61e445a6fd24dacd6a6", size = 10436369, upload-time = "2026-05-11T18:54:06.311Z" }, + { url = "https://files.pythonhosted.org/packages/fd/24/c7c39fb4fe22b71a0c2d78bf0c585c600092d85f94f086d2b3b2f6ca27e2/pandas-3.0.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:819959dab7bbd0049c15623fbac4e29a191b9528160a61fb1032242d8ced2d9c", size = 10358306, upload-time = "2026-05-11T18:54:09.085Z" }, + { url = "https://files.pythonhosted.org/packages/16/ec/dd2a9eb7fa1204df88c0864164e35b228ac581062ac612ba0a67fd812e4c/pandas-3.0.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:60ae316d3fd75d1858d450d0db0103ea2be3e7d4a95ec2f064f7e2ae63f7b028", size = 10758394, upload-time = "2026-05-11T18:54:11.956Z" }, + { url = "https://files.pythonhosted.org/packages/95/6e/00c61ea8e85b4f6d8d35e11852a1a4998fc7fafc91c6a602d1cc9c972d64/pandas-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd3a518890b400d32f9023722dc9a9a5c969f00b415419a3c06c043f09bb5d7d", size = 11375717, upload-time = "2026-05-11T18:54:14.539Z" }, + { url = "https://files.pythonhosted.org/packages/31/89/8fc1c268969fac43688d65fd92e67df24bd128d53cb4d2eee534cd307399/pandas-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9c39be2d709d01fa972a0cabc522389fceca4f3969332ba25a7d6c5802cf976a", size = 11828897, upload-time = "2026-05-11T18:54:17.146Z" }, + { url = "https://files.pythonhosted.org/packages/56/3b/e7d20dea247a3e6dc0bd8a6953854afbedc03951def4e7371e05e7263e25/pandas-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4db8c527972a821cf5286b40ccc57642a39bc62e62022b42f99f8a67fca8c3a1", size = 10900855, upload-time = "2026-05-11T18:54:19.72Z" }, + { url = "https://files.pythonhosted.org/packages/0f/54/68a0978d1ef8502b8492099beaa6e7a0c1b32e3b5d4f677f5810cb08711c/pandas-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b2c95f8bfc1ee412bf482605d7bfd30c12d1d26bd59fdd91efeef1d4718decb1", size = 9466464, upload-time = "2026-05-11T18:54:22.754Z" }, +] + +[[package]] +name = "pandas-stubs" +version = "2.3.3.260113" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "types-pytz", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/92/5d/be23854a73fda69f1dbdda7bc10fbd6f930bd1fa87aaec389f00c901c1e8/pandas_stubs-2.3.3.260113.tar.gz", hash = "sha256:076e3724bcaa73de78932b012ec64b3010463d377fa63116f4e6850643d93800", size = 116131, upload-time = "2026-01-13T22:30:16.704Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/c6/df1fe324248424f77b89371116dab5243db7f052c32cc9fe7442ad9c5f75/pandas_stubs-2.3.3.260113-py3-none-any.whl", hash = "sha256:ec070b5c576e1badf12544ae50385872f0631fc35d99d00dc598c2954ec564d3", size = 168246, upload-time = "2026-01-13T22:30:15.244Z" }, +] + +[[package]] +name = "pandas-stubs" +version = "3.0.3.260530" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/aa/c41a8a0ff86fd85dbb3ec0c1f3fa488ca64a8b5f82654ae1b07d84acefe5/pandas_stubs-3.0.3.260530.tar.gz", hash = "sha256:d1efe47b2e5a312c047d7feabec5cb7a55365747983420077e9fcbe9ab74f714", size = 113183, upload-time = "2026-05-30T17:47:40.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/e0/99ec5b02203c4e9ce878bc63d8caa06ac1f891e4d63bded9a5ced70fcb4f/pandas_stubs-3.0.3.260530-py3-none-any.whl", hash = "sha256:a6277eb1c8cebf48d9b2413fcd2e9a6b4ff479c934a223c29eacbc3058c4cb55", size = 173780, upload-time = "2026-05-30T17:47:39.13Z" }, +] + [[package]] name = "pathspec" version = "1.0.4" @@ -391,6 +904,194 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "pyarrow" +version = "24.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/13/13e1069b351bdc3881266e11147ffccf687505dbb0ea74036237f5d454a5/pyarrow-24.0.0.tar.gz", hash = "sha256:85fe721a14dd823aca09127acbb06c3ca723efbd436c004f16bca601b04dcc83", size = 1180261, upload-time = "2026-04-21T10:51:25.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/bf/a34fee1d624152124fa8355c42f34195ad5fe5233ce5bb87946432047d52/pyarrow-24.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:7c2b98645d576a0b9616892ead22b64a83a5f043c5e2ca15ebcefcb5b70c80cb", size = 35076681, upload-time = "2026-04-21T08:51:46.845Z" }, + { url = "https://files.pythonhosted.org/packages/1d/41/64180033d7027afce12dc96d0fe1f504c6fa112190582b458acea2399530/pyarrow-24.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:644a246325b8c69c595ad1dd4b463eba4b0cdb731370e4a86137d433208d6147", size = 36684260, upload-time = "2026-04-21T08:51:53.642Z" }, + { url = "https://files.pythonhosted.org/packages/57/02/9b9320e673dd8a99411fac78690f3df92f6dd6f59754c750110bca66d64e/pyarrow-24.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:3a577bd840ca83f646f0a625dbc571dba7044c43c2d1503afc378b570954345c", size = 45698566, upload-time = "2026-04-21T10:46:02.133Z" }, + { url = "https://files.pythonhosted.org/packages/67/33/f75e91b9a64c3f33c787e263c93b871ad91b8a4a68c1d5cebddd9840e835/pyarrow-24.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:e3268e43984d0b1a185c89b4cfff282a7ead12fc93f56cfd7088bdbcbe727041", size = 48835562, upload-time = "2026-04-21T10:46:10.278Z" }, + { url = "https://files.pythonhosted.org/packages/a5/63/097510448e47e4091faa41c43ba92f97cecaab8f4535b56a3d149578f634/pyarrow-24.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2392d954fcb920f42d230284b677605e4e2fbb11f2821e823e642abd67fbb491", size = 49394997, upload-time = "2026-04-21T10:46:18.08Z" }, + { url = "https://files.pythonhosted.org/packages/60/6b/c047d6222ab279024a062742d1807e2fbaf27bba88a98637299ff47b9236/pyarrow-24.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bec9373df11544592b0ba7ec2af0e35059e5f0e7647c6183a854dedd193298f1", size = 51911424, upload-time = "2026-04-21T10:46:25.347Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ba/464cc70761c2a525d97ebd84e21c31ebd47f3ef4bdcee117009f51c46f24/pyarrow-24.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:c42ab9439498270139cc63e18847a02afe5c8b3ed9c931266533cfe378bd3591", size = 27251730, upload-time = "2026-04-21T10:46:30.913Z" }, + { url = "https://files.pythonhosted.org/packages/62/c9/a47ab7ece0d86cbe6678418a0fbd1ac4bb493b9184a3891dfa0e7f287ae0/pyarrow-24.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b0e131f880cda8d04e076cee175a46fc0e8bc8b65c99c6c09dff6669335fde74", size = 35068898, upload-time = "2026-04-21T10:46:36.599Z" }, + { url = "https://files.pythonhosted.org/packages/d1/bc/8db86617a9a58008acf8913d6fed68ea2a46acb6de928db28d724c891a68/pyarrow-24.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:1b2fe7f9a5566401a0ef2571f197eb92358925c1f0c8dba305d6e43ea0871bb3", size = 36679915, upload-time = "2026-04-21T10:46:42.602Z" }, + { url = "https://files.pythonhosted.org/packages/eb/8e/fb178720400ef69db251eb4a9c3ccf4af269bc1feb5055529b8fc87170d1/pyarrow-24.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:0b3537c00fb8d384f15ac1e79b6eb6db04a16514c8c1d22e59a9b95c8ba42868", size = 45697931, upload-time = "2026-04-21T10:46:48.403Z" }, + { url = "https://files.pythonhosted.org/packages/f3/27/99c42abe8e21b44f4917f62631f3aa31404882a2c41d8a4cd5c110e13d52/pyarrow-24.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:14e31a3c9e35f1ab6356c6378f6f72830e6d2d5f1791df3774a7b097d18a6a1e", size = 48837449, upload-time = "2026-04-21T10:46:55.329Z" }, + { url = "https://files.pythonhosted.org/packages/36/b6/333749e2666e9032891125bf9c691146e92901bece62030ac1430e2e7c88/pyarrow-24.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b7d9a514e73bc42711e6a35aaccf3587c520024fe0a25d830a1a8a27c15f4f57", size = 49395949, upload-time = "2026-04-21T10:47:01.869Z" }, + { url = "https://files.pythonhosted.org/packages/17/25/c5201706a2dd374e8ba6ee3fd7a8c89fb7ffc16eed5217a91fd2bd7f7626/pyarrow-24.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b196eb3f931862af3fa84c2a253514d859c08e0d8fe020e07be12e75a5a9780c", size = 51912986, upload-time = "2026-04-21T10:47:09.872Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d2/4d1bbba65320b21a49678d6fbdc6ff7c649251359fdcfc03568c4136231d/pyarrow-24.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:35405aecb474e683fb36af650618fd5340ee5471fc65a21b36076a18bbc6c981", size = 27255371, upload-time = "2026-04-21T10:47:15.943Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a9/9686d9f07837f91f775e8932659192e02c74f9d8920524b480b85212cc68/pyarrow-24.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:6233c9ed9ab9d1db47de57d9753256d9dcffbf42db341576099f0fd9f6bf4810", size = 34981559, upload-time = "2026-04-21T10:47:22.17Z" }, + { url = "https://files.pythonhosted.org/packages/80/b6/0ddf0e9b6ead3474ab087ae598c76b031fc45532bf6a63f3a553440fb258/pyarrow-24.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:f7616236ec1bc2b15bfdec22a71ab38851c86f8f05ff64f379e1278cf20c634a", size = 36663654, upload-time = "2026-04-21T10:47:28.315Z" }, + { url = "https://files.pythonhosted.org/packages/7c/3b/926382efe8ce27ba729071d3566ade6dfb86bdf112f366000196b2f5780a/pyarrow-24.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:1617043b99bd33e5318ae18eb2919af09c71322ef1ca46566cdafc6e6712fb66", size = 45679394, upload-time = "2026-04-21T10:47:34.821Z" }, + { url = "https://files.pythonhosted.org/packages/b3/7a/829f7d9dfd37c207206081d6dad474d81dde29952401f07f2ba507814818/pyarrow-24.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6165461f55ef6314f026de6638d661188e3455d3ec49834556a0ebbdbace18bb", size = 48863122, upload-time = "2026-04-21T10:47:42.056Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e8/f88ce625fe8babaae64e8db2d417c7653adb3019b08aae85c5ed787dc816/pyarrow-24.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3b13dedfe76a0ad2d1d859b0811b53827a4e9d93a0bcb05cf59333ab4980cc7e", size = 49376032, upload-time = "2026-04-21T10:47:48.967Z" }, + { url = "https://files.pythonhosted.org/packages/36/7a/82c363caa145fff88fb475da50d3bf52bb024f61917be5424c3392eaf878/pyarrow-24.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:25ea65d868eb04015cd18e6df2fbe98f07e5bda2abefabcb88fce39a947716f6", size = 51929490, upload-time = "2026-04-21T10:47:55.981Z" }, + { url = "https://files.pythonhosted.org/packages/66/1c/e3e72c8014ad2743ca64a701652c733cc5cbcee15c0463a32a8c55518d9e/pyarrow-24.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:295f0a7f2e242dabd513737cf076007dc5b2d59237e3eca37b05c0c6446f3826", size = 27355660, upload-time = "2026-04-21T10:48:01.718Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d3/a1abf004482026ddc17f4503db227787fa3cfe41ec5091ff20e4fea55e57/pyarrow-24.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:02b001b3ed4723caa44f6cd1af2d5c86aa2cf9971dacc2ffa55b21237713dfba", size = 34976759, upload-time = "2026-04-21T10:48:07.258Z" }, + { url = "https://files.pythonhosted.org/packages/4f/4a/34f0a36d28a2dd32225301b79daad44e243dc1a2bb77d43b60749be255c4/pyarrow-24.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:04920d6a71aabd08a0417709efce97d45ea8e6fb733d9ca9ecffb13c67839f68", size = 36658471, upload-time = "2026-04-21T10:48:13.347Z" }, + { url = "https://files.pythonhosted.org/packages/1f/78/543b94712ae8bb1a6023bcc1acf1a740fbff8286747c289cd9468fced2a5/pyarrow-24.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a964266397740257f16f7bb2e4f08a0c81454004beab8ff59dd531b73610e9f2", size = 45675981, upload-time = "2026-04-21T10:48:20.201Z" }, + { url = "https://files.pythonhosted.org/packages/84/9f/8fb7c222b100d314137fa40ec050de56cd8c6d957d1cfff685ce72f15b17/pyarrow-24.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6f066b179d68c413374294bc1735f68475457c933258df594443bb9d88ddc2a0", size = 48859172, upload-time = "2026-04-21T10:48:27.541Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d3/1ea72538e6c8b3b475ed78d1049a2c518e655761ea50fe1171fc855fcab7/pyarrow-24.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1183baeb14c5f587b1ec52831e665718ce632caab84b7cd6b85fd44f96114495", size = 49385733, upload-time = "2026-04-21T10:48:34.7Z" }, + { url = "https://files.pythonhosted.org/packages/c3/be/c3d8b06a1ba35f2260f8e1f771abbee7d5e345c0937aab90675706b1690a/pyarrow-24.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:806f24b4085453c197a5078218d1ee08783ebbba271badd153d1ae22a3ee804f", size = 51934335, upload-time = "2026-04-21T10:48:42.099Z" }, + { url = "https://files.pythonhosted.org/packages/9c/62/89e07a1e7329d2cde3e3c6994ba0839a24977a2beda8be6005ea3d860b99/pyarrow-24.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:e4505fc6583f7b05ab854934896bcac8253b04ac1171a77dfb73efef92076d91", size = 27271748, upload-time = "2026-04-21T10:49:42.532Z" }, + { url = "https://files.pythonhosted.org/packages/17/1a/cff3a59f80b5b1658549d46611b67163f65e0664431c076ad728bf9d5af4/pyarrow-24.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:1a4e45017efbf115032e4475ee876d525e0e36c742214fbe405332480ecd6275", size = 35238554, upload-time = "2026-04-21T10:48:48.526Z" }, + { url = "https://files.pythonhosted.org/packages/a8/99/cce0f42a327bfef2c420fb6078a3eb834826e5d6697bf3009fe11d2ad051/pyarrow-24.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:7986f1fa71cee060ad00758bcc79d3a93bab8559bf978fab9e53472a2e25a17b", size = 36782301, upload-time = "2026-04-21T10:48:55.181Z" }, + { url = "https://files.pythonhosted.org/packages/2a/66/8e560d5ff6793ca29aca213c53eec0dd482dd46cb93b2819e5aab52e4252/pyarrow-24.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:d3e0b61e8efb24ed38898e5cdc5fffa9124be480008d401a1f8071500494ae42", size = 45721929, upload-time = "2026-04-21T10:49:03.676Z" }, + { url = "https://files.pythonhosted.org/packages/27/0c/a26e25505d030716e078d9f16eb74973cbf0b33b672884e9f9da1c83b871/pyarrow-24.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:55a3bc1e3df3b5567b7d27ef551b2283f0c68a5e86f1cd56abc569da4f31335b", size = 48825365, upload-time = "2026-04-21T10:49:11.714Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/771f9ecb0c65e73fe9dccdd1717901b9594f08c4515d000c7c62df573811/pyarrow-24.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:641f795b361874ac9da5294f8f443dfdbee355cf2bd9e3b8d97aaac2306b9b37", size = 49451819, upload-time = "2026-04-21T10:49:21.474Z" }, + { url = "https://files.pythonhosted.org/packages/48/da/61ae89a88732f5a785646f3ec6125dbb640fa98a540eb2b9889caa561403/pyarrow-24.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8adc8e6ce5fccf5dc707046ae4914fd537def529709cc0d285d37a7f9cd442ca", size = 51909252, upload-time = "2026-04-21T10:49:31.164Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1a/8dd5cafab7b66573fa91c03d06d213356ad4edd71813aa75e08ce2b3a844/pyarrow-24.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:9b18371ad2f44044b81a8d23bc2d8a9b6a6226dca775e8e16cfee640473d6c5d", size = 27388127, upload-time = "2026-04-21T10:49:37.334Z" }, + { url = "https://files.pythonhosted.org/packages/ad/80/d022a34ff05d2cbedd8ccf841fc1f532ecfa9eb5ed1711b56d0e0ea71fc9/pyarrow-24.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:1cc9057f0319e26333b357e17f3c2c022f1a83739b48a88b25bfd5fa2dc18838", size = 35007997, upload-time = "2026-04-21T10:49:48.796Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ff/f01485fda6f4e5d441afb8dd5e7681e4db18826c1e271852f5d3957d6a80/pyarrow-24.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:e6f1278ee4785b6db21229374a1c9e54ec7c549de5d1efc9630b6207de7e170b", size = 36678720, upload-time = "2026-04-21T10:49:55.858Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c2/2d2d5fea814237923f71b36495211f20b43a1576f9a4d6da7e751a64ec6f/pyarrow-24.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:adbbedc55506cbdabb830890444fb856bfb0060c46c6f8026c6c2f2cf86ae795", size = 45741852, upload-time = "2026-04-21T10:50:04.624Z" }, + { url = "https://files.pythonhosted.org/packages/8e/3a/28ba9c1c1ebdbb5f1b94dfebb46f207e52e6a554b7fe4132540fde29a3a0/pyarrow-24.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:ae8a1145af31d903fa9bb166824d7abe9b4681a000b0159c9fb99c11bc11ad26", size = 48889852, upload-time = "2026-04-21T10:50:12.293Z" }, + { url = "https://files.pythonhosted.org/packages/df/51/4a389acfd31dca009f8fb82d7f510bb4130f2b3a8e18cf00194d0687d8ac/pyarrow-24.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d7027eba1df3b2069e2e8d80f644fa0918b68c46432af3d088ddd390d063ecde", size = 49445207, upload-time = "2026-04-21T10:50:20.677Z" }, + { url = "https://files.pythonhosted.org/packages/19/4b/0bab2b23d2ae901b1b9a03c0efd4b2d070256f8ce3fc43f6e58c167b2081/pyarrow-24.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e56a1ffe9bf7b727432b89104cc0849c21582949dd7bdcb34f17b2001a351a76", size = 51954117, upload-time = "2026-04-21T10:50:29.14Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/f4e9145da0417b3d2c12035a8492b35ff4a3dbc653e614fcfb51d9dedb38/pyarrow-24.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:38be1808cdd068605b787e6ca9119b27eb275a0234e50212c3492331680c3b1e", size = 28001155, upload-time = "2026-04-21T10:51:22.337Z" }, + { url = "https://files.pythonhosted.org/packages/79/4f/46a49a63f43526da895b1a45bbb51d5baf8e4d77159f8528fc3e5490007f/pyarrow-24.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:418e48ce50a45a6a6c73c454677203a9c75c966cb1e92ca3370959185f197a05", size = 35250387, upload-time = "2026-04-21T10:50:35.552Z" }, + { url = "https://files.pythonhosted.org/packages/a0/da/d5e0cd5ef00796922404806d5f00325cdadc3441ce2c13fe7115f2df9a64/pyarrow-24.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:2f16197705a230a78270cdd4ea8a1d57e86b2fdcbc34a1f6aebc72e65c986f9a", size = 36797102, upload-time = "2026-04-21T10:50:42.417Z" }, + { url = "https://files.pythonhosted.org/packages/34/c7/5904145b0a593a05236c882933d439b5720f0a145381179063722fbfc123/pyarrow-24.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:fb24ac194bfc5e86839d7dcd52092ee31e5fe6733fe11f5e3b06ef0812b20072", size = 45745118, upload-time = "2026-04-21T10:50:49.324Z" }, + { url = "https://files.pythonhosted.org/packages/13/d3/cca42fe166d1c6e4d5b80e530b7949104d10e17508a90ae202dac205ce2a/pyarrow-24.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:9700ebd9a51f5895ce75ff4ac4b3c47a7d4b42bc618be8e713e5d56bacf5f931", size = 48844765, upload-time = "2026-04-21T10:50:55.579Z" }, + { url = "https://files.pythonhosted.org/packages/b0/49/942c3b79878ba928324d1e17c274ed84581db8c0a749b24bcf4cbdf15bd3/pyarrow-24.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d8ddd2768da81d3ee08cfea9b597f4abb4e8e1dc8ae7e204b608d23a0d3ab699", size = 49471890, upload-time = "2026-04-21T10:51:02.439Z" }, + { url = "https://files.pythonhosted.org/packages/76/97/ff71431000a75d84135a1ace5ca4ba11726a231a8007bbb320a4c54075d5/pyarrow-24.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:61a3d7eaa97a14768b542f3d284dc6400dd2470d9f080708b13cd46b6ae18136", size = 51932250, upload-time = "2026-04-21T10:51:10.576Z" }, + { url = "https://files.pythonhosted.org/packages/51/be/6f79d55816d5c22557cf27533543d5d70dfe692adfbee4b99f2760674f38/pyarrow-24.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:c91d00057f23b8d353039520dc3a6c09d8608164c692e9f59a175a42b2ae0c19", size = 28131282, upload-time = "2026-04-21T10:51:16.815Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/08/f1ba952f1c8ae5581c70fa9c6da89f247b83e3dd8c09c035d5d7931fc23d/pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4", size = 2113146, upload-time = "2026-05-06T13:37:36.537Z" }, + { url = "https://files.pythonhosted.org/packages/56/c6/65f646c7ff09bd257f660434adb45c4dfcbbcebcc030562fecf6f5bf887d/pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5", size = 1949769, upload-time = "2026-05-06T13:37:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/64/ba/bfb1d928fd5b49e1258935ff104ae356e9fd89384a55bf9f847e9193ad40/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba", size = 1974958, upload-time = "2026-05-06T13:37:28.611Z" }, + { url = "https://files.pythonhosted.org/packages/4e/74/76223bfb117b64af743c9b6670d1364516f5c0604f96b48f3272f6af6cc6/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b", size = 2042118, upload-time = "2026-05-06T13:36:55.216Z" }, + { url = "https://files.pythonhosted.org/packages/cb/7b/848732968bc8f48f3187542f08358b9d842db564147b256669426ebb1652/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c", size = 2222876, upload-time = "2026-05-06T13:38:25.455Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2f/e90b63ee2e14bd8d3db8f705a6d75d64e6ee1b7c2c8833747ce706e1e0ce/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50", size = 2286703, upload-time = "2026-05-06T13:37:53.304Z" }, + { url = "https://files.pythonhosted.org/packages/ba/1e/acc4d70f88a0a277e4a1fa77ebb985ceabaf900430f875bf9338e11c9420/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd", size = 2092042, upload-time = "2026-05-06T13:38:46.981Z" }, + { url = "https://files.pythonhosted.org/packages/a9/da/0a422b57bf8504102bf3c4ccea9c41bab5a5cee6a54650acf8faf67f5a24/pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01", size = 2117231, upload-time = "2026-05-06T13:39:23.146Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2a/2ac13c3af305843e23c5078c53d135656b3f05a2fd78cb7bbbb12e97b473/pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d", size = 2168388, upload-time = "2026-05-06T13:40:08.06Z" }, + { url = "https://files.pythonhosted.org/packages/72/04/2beacf7e1607e93eefe4aed1b4709f079b905fb77530179d4f7c71745f22/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4", size = 2184769, upload-time = "2026-05-06T13:38:13.901Z" }, + { url = "https://files.pythonhosted.org/packages/9e/29/d2b9fd9f539133548eaf622c06a4ce176cb46ac59f32d0359c4abc0de047/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f", size = 2319312, upload-time = "2026-05-06T13:39:08.24Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/0f7a5b85fec6075bea96e3ef9187de38fccced0de92c1e7feda8d5cc7bb9/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39", size = 2361817, upload-time = "2026-05-06T13:38:43.2Z" }, + { url = "https://files.pythonhosted.org/packages/25/a4/73363fec545fd3ec025490bdda2743c56d0dd5b6266b1a53bbe9e4265375/pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d", size = 1987085, upload-time = "2026-05-06T13:39:25.497Z" }, + { url = "https://files.pythonhosted.org/packages/01/aa/62f082da2c91fac1c234bc9ee0066257ce83f0604abd72e4c9d5991f2d84/pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf", size = 2074311, upload-time = "2026-05-06T13:39:59.922Z" }, + { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" }, + { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" }, + { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" }, + { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" }, + { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" }, + { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" }, + { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" }, + { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" }, + { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" }, + { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" }, + { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" }, + { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" }, + { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" }, + { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, + { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" }, + { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" }, + { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" }, + { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" }, +] + [[package]] name = "pygments" version = "2.20.0" @@ -431,6 +1132,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, ] +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + [[package]] name = "python-discovery" version = "1.2.2" @@ -444,6 +1157,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d8/db/795879cc3ddfe338599bddea6388cc5100b088db0a4caf6e6c1af1c27e04/python_discovery-1.2.2-py3-none-any.whl", hash = "sha256:e1ae95d9af875e78f15e19aed0c6137ab1bb49c200f21f5061786490c9585c7a", size = 31894, upload-time = "2026-04-07T17:28:48.09Z" }, ] +[[package]] +name = "pytz" +version = "2026.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/46/dd499ec9038423421951e4fad73051febaa13d2df82b4064f87af8b8c0c3/pytz-2026.2.tar.gz", hash = "sha256:0e60b47b29f21574376f218fe21abc009894a2321ea16c6754f3cad6eb7cdd6a", size = 320861, upload-time = "2026-05-04T01:35:29.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/dd/96da98f892250475bdf2328112d7468abdd4acc7b902b6af23f4ed958ea0/pytz-2026.2-py2.py3-none-any.whl", hash = "sha256:04156e608bee23d3792fd45c94ae47fae1036688e75032eea2e3bf0323d1f126", size = 510141, upload-time = "2026-05-04T01:35:27.408Z" }, +] + [[package]] name = "pyyaml" version = "6.0.3" @@ -508,6 +1230,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, ] +[[package]] +name = "respx" +version = "0.23.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/98/4e55c9c486404ec12373708d015ebce157966965a5ebe7f28ff2c784d41b/respx-0.23.1.tar.gz", hash = "sha256:242dcc6ce6b5b9bf621f5870c82a63997e8e82bc7c947f9ffe272b8f3dd5a780", size = 29243, upload-time = "2026-04-08T14:37:16.008Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/4a/221da6ca167db45693d8d26c7dc79ccfc978a440251bf6721c9aaf251ac0/respx-0.23.1-py2.py3-none-any.whl", hash = "sha256:b18004b029935384bccfa6d7d9d74b4ec9af73a081cc28600fffc0447f4b8c1a", size = 25557, upload-time = "2026-04-08T14:37:14.613Z" }, +] + [[package]] name = "rich" version = "14.3.2" @@ -546,6 +1280,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" }, ] +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, +] + [[package]] name = "stevedore" version = "5.6.0" @@ -555,6 +1307,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/40/8561ce06dc46fd17242c7724ab25b257a2ac1b35f4ebf551b40ce6105cfa/stevedore-5.6.0-py3-none-any.whl", hash = "sha256:4a36dccefd7aeea0c70135526cecb7766c4c84c473b1af68db23d541b6dc1820", size = 54428, upload-time = "2025-11-20T10:06:05.946Z" }, ] +[[package]] +name = "tenacity" +version = "9.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, +] + [[package]] name = "tomli" version = "2.4.0" @@ -667,6 +1428,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/53/4a33dc81da39db7b31e5622333df361e8fe055b7ec636bd5fea762c9182d/tox_uv_bare-1.35.2-py3-none-any.whl", hash = "sha256:c0d590a41d1054a1ad0874e9e5943ff52402786e3d4599d8f8d37a65b566ef53", size = 22307, upload-time = "2026-05-05T01:34:17.681Z" }, ] +[[package]] +name = "types-pytz" +version = "2026.2.0.20260518" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/d9/9fa4019d2235bd374293e1fd4153879b28b6ae1d2bae98addd352c9713f2/types_pytz-2026.2.0.20260518.tar.gz", hash = "sha256:e5d254329e9c4e91f0781b22c43a4bb2d10bb044d97b24c4b05d45567b0eae16", size = 10871, upload-time = "2026-05-18T06:02:45.789Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/89/41e80670779a223d8bc8bc83019a619988cfa5c432cedac5cec23884fbc4/types_pytz-2026.2.0.20260518-py3-none-any.whl", hash = "sha256:3a12eaa38f476bd650902a9c9bb442f03f3c7dee2be5c5848bce61bd708d205a", size = 10125, upload-time = "2026-05-18T06:02:44.968Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0" @@ -676,6 +1446,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "tzdata" +version = "2026.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, +] + [[package]] name = "uv" version = "0.11.15" From 214e308ca00f92041ed5711bdfa7195bce6350e7 Mon Sep 17 00:00:00 2001 From: Tom Kaltofen Date: Wed, 8 Jul 2026 12:09:08 +0000 Subject: [PATCH 2/3] chore(deps): bump mloda to 0.9.0 Update the mloda pin from >=0.8.0 to >=0.9.0 (latest release) and refresh the lockfile. The 0.9.0 breaking changes (default input-feature option forwarding, PROPERTY_MAPPING default enforcement, credential path changes) do not touch the connector/reader APIs this plugin uses. Full tox gate (pytest, ruff, mypy --strict, bandit) passes. --- pyproject.toml | 2 +- uv.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 603972f..76b443f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ description = "mloda plugin providing connectors and harmonization for German go license = "Apache-2.0" authors = [{ name = "Tom Kaltofen", email = "tomkaltofen@mloda.ai" }] dependencies = [ - "mloda>=0.8.0", + "mloda>=0.9.0", "mloda-testing>=0.3.1", "httpx>=0.27", "pandas>=2.0", diff --git a/uv.lock b/uv.lock index 6c386ff..5ed5ff7 100644 --- a/uv.lock +++ b/uv.lock @@ -337,10 +337,10 @@ wheels = [ [[package]] name = "mloda" -version = "0.8.0" +version = "0.9.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/d8/b4f8beceec599fe75b4956fc8614ead1d40bc0642e6562c18c2c683f2685/mloda-0.8.0-py3-none-any.whl", hash = "sha256:521e0425169a99370b3d1cdf9fd480ebecb3f9a0ec67c62fe6a321ca8e3bcabe", size = 428976, upload-time = "2026-06-05T17:24:09.732Z" }, + { url = "https://files.pythonhosted.org/packages/f2/82/a7893ddd9ab26dd839671c594e9c16437364a8b8ed5dffb02e9b2de52d7f/mloda-0.9.0-py3-none-any.whl", hash = "sha256:d193b03622e6c8670ccde500c9e6d74eba9ef4ed70ca3bd6adbcb9330a370585", size = 430826, upload-time = "2026-07-07T08:48:32.032Z" }, ] [[package]] @@ -378,7 +378,7 @@ requires-dist = [ { name = "bandit", marker = "extra == 'dev'" }, { name = "httpx", specifier = ">=0.27" }, { name = "hypothesis", marker = "extra == 'dev'" }, - { name = "mloda", specifier = ">=0.8.0" }, + { name = "mloda", specifier = ">=0.9.0" }, { name = "mloda-testing", specifier = ">=0.3.1" }, { name = "mypy", marker = "extra == 'dev'" }, { name = "pandas", specifier = ">=2.0" }, From 8e2cff5cf70a989620e03bb008241e6673f3b7c6 Mon Sep 17 00:00:00 2001 From: Tom Kaltofen Date: Tue, 21 Jul 2026 08:31:09 +0000 Subject: [PATCH 3/3] refactor: govdata --- README.md | 49 +- demos/govdata_demo.py | 149 +++++ docs/adding-a-reader.md | 12 + .../compute_frameworks/__init__.py | 1 - mloda_plugin_govdata/extenders/__init__.py | 1 - .../feature_groups/govdata/__init__.py | 29 +- .../govdata/bundeswahlleiterin.py | 20 + .../feature_groups/govdata/core/__init__.py | 1 + .../govdata/{ => core}/cache.py | 0 .../govdata/{ => core}/client.py | 0 .../govdata/{ => core}/discovery.py | 54 +- .../govdata/{ => core}/locator.py | 0 .../govdata/{ => core}/parse.py | 0 .../feature_groups/govdata/feature.py | 17 + .../feature_groups/govdata/population.py | 22 + .../feature_groups/govdata/reader.py | 127 ++-- .../govdata/tests/test_cache.py | 2 +- .../govdata/tests/test_client.py | 8 +- .../govdata/tests/test_discovery.py | 102 ++- .../govdata/tests/test_parse.py | 2 +- .../govdata/tests/test_parse_hypothesis.py | 2 +- .../govdata/tests/test_reader.py | 135 +++- .../feature_groups/govdata/tests/test_uba.py | 2 +- .../feature_groups/govdata/uba.py | 34 +- pyproject.toml | 3 + tests/test_demo_definition.py | 19 + tox.ini | 4 +- uv.lock | 586 +++++++++++++++++- 28 files changed, 1274 insertions(+), 107 deletions(-) create mode 100644 demos/govdata_demo.py create mode 100644 docs/adding-a-reader.md delete mode 100644 mloda_plugin_govdata/compute_frameworks/__init__.py delete mode 100644 mloda_plugin_govdata/extenders/__init__.py create mode 100644 mloda_plugin_govdata/feature_groups/govdata/bundeswahlleiterin.py create mode 100644 mloda_plugin_govdata/feature_groups/govdata/core/__init__.py rename mloda_plugin_govdata/feature_groups/govdata/{ => core}/cache.py (100%) rename mloda_plugin_govdata/feature_groups/govdata/{ => core}/client.py (100%) rename mloda_plugin_govdata/feature_groups/govdata/{ => core}/discovery.py (70%) rename mloda_plugin_govdata/feature_groups/govdata/{ => core}/locator.py (100%) rename mloda_plugin_govdata/feature_groups/govdata/{ => core}/parse.py (100%) create mode 100644 mloda_plugin_govdata/feature_groups/govdata/feature.py create mode 100644 mloda_plugin_govdata/feature_groups/govdata/population.py create mode 100644 tests/test_demo_definition.py diff --git a/README.md b/README.md index 3aad80a..50e44c6 100644 --- a/README.md +++ b/README.md @@ -10,26 +10,50 @@ Connectors for German open government data, built on [mloda](https://github.com/ Three example datasets cover the M1 themes: population (GovData CSV), elections (Bundeswahlleiterin `kerg.csv`), and environment (UBA Air Data JSON). +## Status + +Young but working. All three example readers run end to end, with paginated dataset search, cached downloads with retries, and unit plus property-based tests behind them. Every reader is a thin subclass of `BaseGovDataReader` that overrides only the parse step; new datasets follow the same path (see [docs/adding-a-reader.md](docs/adding-a-reader.md)). Development happens in a 6-month Prototype Fund stage (June to November 2026), so the API may still shift between releases. + ## Usage Read the Stuttgart population dataset (via GovData) as a typed PyArrow table: ```python from mloda.user import Feature, mloda -from mloda_plugin_govdata.feature_groups.govdata import GovDataReader +from mloda_plugin_govdata.feature_groups.govdata import StuttgartPopulationReader slug = "einwohner-nach-altersgruppen-und-stadtbezirken" result = mloda.run_all( [ - Feature("Einwohner", options={GovDataReader.__name__: slug}), - Feature("Stadtbezirk", options={GovDataReader.__name__: slug}), + Feature("Einwohner", options={StuttgartPopulationReader.__name__: slug}), + Feature("Stadtbezirk", options={StuttgartPopulationReader.__name__: slug}), ], compute_frameworks=["PyArrowTable"], ) table = result[0] # pyarrow.Table with the requested columns ``` -The option value is a GovData dataset slug or a direct distribution URL. The license is read from the CKAN distribution metadata. Set `GovDataReader.cache_dir` to control where downloads are cached. +The option value is a GovData dataset slug or a direct distribution URL. The license is read from the CKAN distribution metadata. Set `BaseGovDataReader.cache_dir` to control where downloads are cached. For any other GovData CSV dataset, `GovDataReader` works out of the box and reads every column as a string; subclass it and set `schema` for typed columns. + +Don't know the slug yet? Search GovData with the paginated CKAN `package_search` API: + +```python +from mloda_plugin_govdata.feature_groups.govdata import build_client, search_datasets + +with build_client() as client: + for dataset in search_datasets(client, "einwohner stuttgart", max_results=10): + print(dataset.name, "|", dataset.title) +``` + +`search_datasets` walks the result pages lazily (`page_size` per request) and stops at `max_results` or the end of the result set. + +Got a slug but not the column names? `peek` lists what you can request as features: + +```python +StuttgartPopulationReader.peek(slug) # {"Stichtag": "date32[day]", "Stadtbezirk": "string", ...} +``` + +It works on every reader (`BundeswahlleiterinReader.peek(kerg)`, `UbaAirReader.peek(url)`) and downloads through the cache, so the actual feature request reuses the file. A typo in a feature name fails with the available columns and a close-match suggestion instead of a raw KeyError. The elections reader handles a direct CSV URL whose file has a multi-row merged header (Bundeswahlleiterin `kerg.csv`): @@ -60,11 +84,24 @@ result = mloda.run_all( Columns are `station_id`, `date_start`, `component_id`, `scope_id`, `value`, `date_end`, and `index` (the air-quality index). Component and scope ids come from the UBA `components` and `scopes` endpoints. +## Demo + +An interactive [marimo](https://marimo.io) notebook walks through dataset discovery and all three example datasets. The notebook lives in the repository (not in the published package), so run it from a source checkout: + +```bash +git clone https://github.com/TomKaltofen/mloda-plugin-govdata.git +cd mloda-plugin-govdata +uv sync --all-extras +uv run marimo edit demos/govdata_demo.py +``` + +The notebook hits the live GovData, Bundeswahlleiterin, and UBA endpoints; downloads are cached locally after the first run. + ## Related Repositories -- **[mloda](https://github.com/mloda-ai/mloda)**: The core library for open data access. Declaratively define what data you need, not how to get it. mloda handles feature resolution, dependency management, and compute framework abstraction automatically. +- **[mloda](https://github.com/mloda-ai/mloda)**: the core library this plugin builds on. You declare which features you need; mloda resolves how to compute them. -- **[mloda-registry](https://github.com/mloda-ai/mloda-registry)**: The central hub for discovering and sharing mloda plugins. Browse community-contributed FeatureGroups, find integration guides, and publish your own plugins for others to use. +- **[mloda-registry](https://github.com/mloda-ai/mloda-registry)**: plugin registry and development guides for the mloda ecosystem. ## Funding diff --git a/demos/govdata_demo.py b/demos/govdata_demo.py new file mode 100644 index 0000000..2bb00d6 --- /dev/null +++ b/demos/govdata_demo.py @@ -0,0 +1,149 @@ +"""Marimo demo: discover GovData datasets and read the three M1 example datasets. + +Run with: marimo edit demos/govdata_demo.py (needs network access; install the +"demo" extra for marimo itself). +""" + +import marimo + +__generated_with = "0.14.16" +app = marimo.App(width="medium") + + +@app.cell +def _(): + import marimo as mo + + return (mo,) + + +@app.cell +def _(mo): + mo.md( + """ + # mloda-plugin-govdata demo + + German open government data as mloda features: search GovData via the + paginated CKAN API, then read the three M1 example datasets (population, + elections, environment) as typed Arrow tables. Every cell below talks to + the live endpoints; downloads are cached locally after the first run. + + Part of the Prototype Fund project mloda-plugin-govdata (FKZ 16IS26S11). + """ + ) + return + + +@app.cell +def _(): + import pandas as pd + + from mloda.user import Feature, mloda + from mloda_plugin_govdata.feature_groups.govdata import ( + BundeswahlleiterinReader, + StuttgartPopulationReader, + UbaAirReader, + build_client, + search_datasets, + uba_measures_url, + ) + + return ( + BundeswahlleiterinReader, + Feature, + StuttgartPopulationReader, + UbaAirReader, + build_client, + mloda, + pd, + search_datasets, + uba_measures_url, + ) + + +@app.cell +def _(mo): + mo.md("""## 1. Discover datasets (paginated CKAN `package_search`)""") + return + + +@app.cell +def _(mo): + query = mo.ui.text(value="einwohner stuttgart altersgruppen", label="GovData search", full_width=True) + query + return (query,) + + +@app.cell +def _(build_client, pd, query, search_datasets): + with build_client() as _client: + _hits = list(search_datasets(_client, query.value, max_results=10)) + hits = pd.DataFrame({"name": [d.name for d in _hits], "title": [d.title for d in _hits]}) + hits + return + + +@app.cell +def _(mo): + mo.md("""## 2. Population: Stuttgart residents by age group (GovData CSV)""") + return + + +@app.cell +def _(Feature, StuttgartPopulationReader, mloda): + _slug = "einwohner-nach-altersgruppen-und-stadtbezirken" + _result = mloda.run_all( + [ + Feature("Stichtag", options={StuttgartPopulationReader.__name__: _slug}), + Feature("Stadtbezirk", options={StuttgartPopulationReader.__name__: _slug}), + Feature("Alter in 10 Gruppen", options={StuttgartPopulationReader.__name__: _slug}), + Feature("Einwohner", options={StuttgartPopulationReader.__name__: _slug}), + ], + compute_frameworks=["PyArrowTable"], + ) + population = _result[0].to_pandas() + population + return + + +@app.cell +def _(mo): + mo.md("""## 3. Elections: Bundestagswahl 2025 results (`kerg.csv`, merged header)""") + return + + +@app.cell +def _(BundeswahlleiterinReader, Feature, mloda): + _kerg = "https://www.bundeswahlleiterin.de/bundestagswahlen/2025/ergebnisse/opendata/btw25/csv/kerg.csv" + _result = mloda.run_all( + [Feature("Gebiet", options={BundeswahlleiterinReader.__name__: _kerg})], + compute_frameworks=["PyArrowTable"], + ) + elections = _result[0].to_pandas() + elections.head(20) + return + + +@app.cell +def _(mo): + mo.md("""## 4. Environment: hourly ozone at one station (UBA Air Data JSON)""") + return + + +@app.cell +def _(Feature, UbaAirReader, mloda, uba_measures_url): + _url = uba_measures_url(station=143, component=3, scope=2, date_from="2025-01-01", date_to="2025-01-01") + _result = mloda.run_all( + [ + Feature("date_start", options={UbaAirReader.__name__: _url}), + Feature("value", options={UbaAirReader.__name__: _url}), + ], + compute_frameworks=["PyArrowTable"], + ) + environment = _result[0].to_pandas() + environment + return + + +if __name__ == "__main__": + app.run() diff --git a/docs/adding-a-reader.md b/docs/adding-a-reader.md new file mode 100644 index 0000000..33a7028 --- /dev/null +++ b/docs/adding-a-reader.md @@ -0,0 +1,12 @@ +# Adding a Reader + +`BaseGovDataReader` (in `mloda_plugin_govdata/feature_groups/govdata/reader.py`) does the plumbing: locator coercion, CKAN discovery, cached download with retries, and column selection. Subclasses implement `_parse` (or, for plain CSVs, just set `schema` on a `GovDataReader` subclass), plus `suffix` when the payload is not CSV. Each data source lives in its own module (`population.py`, `bundeswahlleiterin.py`, `uba.py`); shared source-agnostic code (client, cache, discovery, locator, CSV parsing) lives in `core/`. + +To connect a new dataset: + +1. Check whether `GovDataReader` already handles it. A GovData slug or a direct CSV URL with a regular single-row header needs no code; every column is read as a string. For typed columns, subclass `GovDataReader` in a new module and set `schema` (see `population.py`). +2. For a different payload shape, subclass `BaseGovDataReader` and implement `_parse` (and `suffix` for non-CSV payloads); `bundeswahlleiterin.py` and `uba.py` show the pattern. +3. Keep source-specific parse logic in the source's module as a `parse_*_bytes` function plus a path wrapper, like `uba.py`. Generic parsing belongs in `core/parse.py`. That keeps it testable from fixture files without network access. +4. Add tests under `mloda_plugin_govdata/feature_groups/govdata/tests/` with a small real sample in `tests/fixtures/`. +5. Export the reader from `feature_groups/govdata/__init__.py` and add a usage snippet to the README. +6. Run `tox` (pytest, ruff, mypy strict, bandit); it must pass before a PR. diff --git a/mloda_plugin_govdata/compute_frameworks/__init__.py b/mloda_plugin_govdata/compute_frameworks/__init__.py deleted file mode 100644 index 7173f84..0000000 --- a/mloda_plugin_govdata/compute_frameworks/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Compute frameworks namespace package.""" diff --git a/mloda_plugin_govdata/extenders/__init__.py b/mloda_plugin_govdata/extenders/__init__.py deleted file mode 100644 index 1e3e767..0000000 --- a/mloda_plugin_govdata/extenders/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Extenders namespace package.""" diff --git a/mloda_plugin_govdata/feature_groups/govdata/__init__.py b/mloda_plugin_govdata/feature_groups/govdata/__init__.py index 2aeb80a..67c98f4 100644 --- a/mloda_plugin_govdata/feature_groups/govdata/__init__.py +++ b/mloda_plugin_govdata/feature_groups/govdata/__init__.py @@ -1,13 +1,27 @@ -"""GovData connector: read German open-government CSV distributions into mloda.""" +"""GovData connector: read German open-government data into mloda.""" -from .discovery import Dataset, ResolvedDistribution, Resource, normalize_license, resolve_distribution -from .locator import GovDataLocator -from .parse import ColumnType, parse_german_csv, parse_german_csv_bytes, parse_multi_header_csv -from .reader import BundeswahlleiterinReader, GovDataFeature, GovDataReader, UbaAirReader -from .uba import UBA_AIR_BASE, parse_uba_measures, parse_uba_measures_bytes, uba_measures_url +from .bundeswahlleiterin import BundeswahlleiterinReader +from .core.client import build_client +from .core.discovery import ( + Dataset, + ResolvedDistribution, + Resource, + normalize_license, + resolve_distribution, + search_datasets, +) +from .core.locator import GovDataLocator +from .core.parse import ColumnType, parse_german_csv, parse_german_csv_bytes, parse_multi_header_csv +from .feature import GovDataFeature +from .population import POPULATION_SCHEMA, POPULATION_SLUG, StuttgartPopulationReader +from .reader import BaseGovDataReader, GovDataReader +from .uba import UBA_AIR_BASE, UbaAirReader, parse_uba_measures, parse_uba_measures_bytes, uba_measures_url __all__ = [ + "POPULATION_SCHEMA", + "POPULATION_SLUG", "UBA_AIR_BASE", + "BaseGovDataReader", "BundeswahlleiterinReader", "ColumnType", "Dataset", @@ -16,7 +30,9 @@ "GovDataReader", "Resource", "ResolvedDistribution", + "StuttgartPopulationReader", "UbaAirReader", + "build_client", "normalize_license", "parse_german_csv", "parse_german_csv_bytes", @@ -24,5 +40,6 @@ "parse_uba_measures", "parse_uba_measures_bytes", "resolve_distribution", + "search_datasets", "uba_measures_url", ] diff --git a/mloda_plugin_govdata/feature_groups/govdata/bundeswahlleiterin.py b/mloda_plugin_govdata/feature_groups/govdata/bundeswahlleiterin.py new file mode 100644 index 0000000..be56f09 --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/bundeswahlleiterin.py @@ -0,0 +1,20 @@ +"""Bundeswahlleiterin federal election results (M1 elections theme, kerg.csv).""" + +from __future__ import annotations + +from pathlib import Path + +import pyarrow as pa + +from .core.discovery import ResolvedDistribution +from .core.locator import GovDataLocator +from .core.parse import ColumnType, parse_multi_header_csv +from .reader import BaseGovDataReader + + +class BundeswahlleiterinReader(BaseGovDataReader): + """Reads the Bundeswahlleiterin kerg.csv result file (5-line preamble, 3-row merged header).""" + + @classmethod + def _parse(cls, path: Path, locator: GovDataLocator, distribution: ResolvedDistribution) -> pa.Table: + return parse_multi_header_csv(path, skiprows=5, header_rows=3, label_columns=4, value_type=ColumnType.INTEGER) diff --git a/mloda_plugin_govdata/feature_groups/govdata/core/__init__.py b/mloda_plugin_govdata/feature_groups/govdata/core/__init__.py new file mode 100644 index 0000000..49656ba --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/core/__init__.py @@ -0,0 +1 @@ +"""Source-agnostic plumbing: HTTP client, download cache, CKAN discovery, locator, CSV parsing.""" diff --git a/mloda_plugin_govdata/feature_groups/govdata/cache.py b/mloda_plugin_govdata/feature_groups/govdata/core/cache.py similarity index 100% rename from mloda_plugin_govdata/feature_groups/govdata/cache.py rename to mloda_plugin_govdata/feature_groups/govdata/core/cache.py diff --git a/mloda_plugin_govdata/feature_groups/govdata/client.py b/mloda_plugin_govdata/feature_groups/govdata/core/client.py similarity index 100% rename from mloda_plugin_govdata/feature_groups/govdata/client.py rename to mloda_plugin_govdata/feature_groups/govdata/core/client.py diff --git a/mloda_plugin_govdata/feature_groups/govdata/discovery.py b/mloda_plugin_govdata/feature_groups/govdata/core/discovery.py similarity index 70% rename from mloda_plugin_govdata/feature_groups/govdata/discovery.py rename to mloda_plugin_govdata/feature_groups/govdata/core/discovery.py index 884e0f5..4fb146c 100644 --- a/mloda_plugin_govdata/feature_groups/govdata/discovery.py +++ b/mloda_plugin_govdata/feature_groups/govdata/core/discovery.py @@ -1,7 +1,8 @@ -"""CKAN discovery: resolve a GovData dataset to a distribution URL and license.""" +"""CKAN discovery: find GovData datasets and resolve them to a distribution URL and license.""" from __future__ import annotations +from collections.abc import Iterator from dataclasses import dataclass from typing import Any @@ -9,7 +10,7 @@ from pydantic import BaseModel, ConfigDict from .client import request_with_retry -from .locator import GovDataLocator +from .locator import DEFAULT_CKAN_BASE, GovDataLocator # dcat-ap.de license URIs observed across GovData distributions -> short labels. LICENSE_LABELS: dict[str, str] = { @@ -90,6 +91,55 @@ def fetch_dataset(client: httpx.Client, locator: GovDataLocator) -> Dataset: return Dataset.from_ckan(payload["result"]) +def search_datasets( + client: httpx.Client, + query: str = "", + *, + ckan_base: str = DEFAULT_CKAN_BASE, + page_size: int = 100, + max_results: int | None = None, +) -> Iterator[Dataset]: + """Search GovData via CKAN ``package_search``, paginating with ``rows``/``start``. + + Yields datasets lazily page by page; ``max_results`` caps the total yield. + Stops early when the server returns an empty page, so a stale ``count`` + cannot loop forever. + """ + if page_size < 1: + raise ValueError(f"page_size must be >= 1, got {page_size}") + if max_results is not None and max_results < 1: + return + + yielded = 0 + start = 0 + while True: + rows = page_size if max_results is None else min(page_size, max_results - yielded) + response = request_with_retry( + client, + "GET", + f"{ckan_base}/package_search", + params={"q": query, "rows": rows, "start": start}, + ) + response.raise_for_status() + payload: dict[str, Any] = response.json() + if not payload.get("success"): + raise ValueError(f"CKAN package_search was not successful for query {query!r}") + result = payload["result"] + results: list[dict[str, Any]] = result.get("results", []) + if not results: + return + for entry in results: + yield Dataset.from_ckan(entry) + yielded += 1 + if max_results is not None and yielded >= max_results: + return + start += len(results) + count = result.get("count") + # Without a usable count, the empty-page guard above ends the walk. + if count is not None and start >= int(count): + return + + def _looks_like_csv(resource: Resource) -> bool: """True when the resource advertises CSV via format, mimetype, or URL suffix.""" fmt = (resource.format or "").strip().lower() diff --git a/mloda_plugin_govdata/feature_groups/govdata/locator.py b/mloda_plugin_govdata/feature_groups/govdata/core/locator.py similarity index 100% rename from mloda_plugin_govdata/feature_groups/govdata/locator.py rename to mloda_plugin_govdata/feature_groups/govdata/core/locator.py diff --git a/mloda_plugin_govdata/feature_groups/govdata/parse.py b/mloda_plugin_govdata/feature_groups/govdata/core/parse.py similarity index 100% rename from mloda_plugin_govdata/feature_groups/govdata/parse.py rename to mloda_plugin_govdata/feature_groups/govdata/core/parse.py diff --git a/mloda_plugin_govdata/feature_groups/govdata/feature.py b/mloda_plugin_govdata/feature_groups/govdata/feature.py new file mode 100644 index 0000000..ffaa4b8 --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/feature.py @@ -0,0 +1,17 @@ +"""Root FeatureGroup exposing the GovData readers.""" + +from __future__ import annotations + +from mloda.provider import BaseInputData +from mloda_plugins.feature_group.input_data.read_file_feature import ReadFileFeature + +from .reader import BaseGovDataReader + + +class GovDataFeature(ReadFileFeature): + """Root FeatureGroup for GovData columns; inherits the any-framework rule to avoid resolver collisions.""" + + @classmethod + def input_data(cls) -> BaseInputData | None: + # The base reader, so every sibling reader subclass stays matchable via its class-name options key. + return BaseGovDataReader() diff --git a/mloda_plugin_govdata/feature_groups/govdata/population.py b/mloda_plugin_govdata/feature_groups/govdata/population.py new file mode 100644 index 0000000..61606a3 --- /dev/null +++ b/mloda_plugin_govdata/feature_groups/govdata/population.py @@ -0,0 +1,22 @@ +"""Stuttgart population dataset (M1 population theme, via GovData).""" + +from __future__ import annotations + +from typing import ClassVar + +from .core.parse import ColumnType +from .reader import GovDataReader + +POPULATION_SLUG = "einwohner-nach-altersgruppen-und-stadtbezirken" +POPULATION_SCHEMA: dict[str, ColumnType] = { + "Stichtag": ColumnType.DATE, + "Stadtbezirk": ColumnType.STRING, + "Alter in 10 Gruppen": ColumnType.STRING, + "Einwohner": ColumnType.INTEGER, +} + + +class StuttgartPopulationReader(GovDataReader): + """Residents by age group and city district, with typed columns.""" + + schema: ClassVar[dict[str, ColumnType] | None] = POPULATION_SCHEMA diff --git a/mloda_plugin_govdata/feature_groups/govdata/reader.py b/mloda_plugin_govdata/feature_groups/govdata/reader.py index 82a7bfb..b50706c 100644 --- a/mloda_plugin_govdata/feature_groups/govdata/reader.py +++ b/mloda_plugin_govdata/feature_groups/govdata/reader.py @@ -1,46 +1,55 @@ -"""mloda reader and FeatureGroup for GovData distributions. +"""Base mloda reader for GovData distributions. -``GovDataReader`` follows the ``CsvReader`` pattern (a ``ReadFile`` subclass): -it resolves a locator to a distribution, downloads it through the cache, and -parses it into a typed Arrow table. ``GovDataFeature`` exposes it as a root -FeatureGroup on the PyArrow compute framework. +``BaseGovDataReader`` follows the ``CsvReader`` pattern (a ``ReadFile`` subclass): +it resolves a locator to a distribution, downloads it through the cache, and lets +the subclass parse the payload into a typed Arrow table. One module per data +source implements ``_parse``; ``GovDataReader`` is the generic single-header +German-CSV reader. """ from __future__ import annotations +import difflib import tempfile from pathlib import Path from typing import Any, ClassVar import pyarrow as pa -from mloda.provider import BaseInputData, FeatureSet +from mloda.provider import FeatureSet # Imported for its registration side effect so "PyArrowTable" resolves; the reader returns a pyarrow Table. from mloda_plugins.compute_framework.base_implementations.pyarrow.table import PyArrowTable # noqa: F401 from mloda_plugins.feature_group.input_data.read_file import ReadFile -from mloda_plugins.feature_group.input_data.read_file_feature import ReadFileFeature -from .cache import DownloadCache -from .client import build_client -from .discovery import ResolvedDistribution, resolve_distribution -from .locator import GovDataLocator -from .parse import ColumnType, parse_german_csv, parse_multi_header_csv -from .uba import parse_uba_measures - -# The M1 population dataset (Stuttgart, via GovData) and its known typed schema. -POPULATION_SLUG = "einwohner-nach-altersgruppen-und-stadtbezirken" -POPULATION_URL_MARKER = "einwohner-in-stuttgart-nach-altersgruppen" -POPULATION_SCHEMA: dict[str, ColumnType] = { - "Stichtag": ColumnType.DATE, - "Stadtbezirk": ColumnType.STRING, - "Alter in 10 Gruppen": ColumnType.STRING, - "Einwohner": ColumnType.INTEGER, -} - - -class GovDataReader(ReadFile): - """Reads a GovData CSV distribution into a typed Arrow table.""" +from .core.cache import DownloadCache +from .core.client import build_client +from .core.discovery import ResolvedDistribution, resolve_distribution +from .core.locator import GovDataLocator +from .core.parse import ColumnType, parse_german_csv + + +def _unknown_features_message(missing: list[str], available: list[str], locator: GovDataLocator) -> str: + """Name the missing features, list what the distribution offers, and suggest close matches.""" + target = locator.dataset_id or locator.distribution_url + parts = [ + f"Unknown feature(s) {', '.join(repr(name) for name in missing)} for {target!r}.", + f"Available: {', '.join(sorted(available))}.", + ] + for name in missing: + close = difflib.get_close_matches(name, available, n=1) + if close: + parts.append(f"Did you mean {close[0]!r} instead of {name!r}?") + return " ".join(parts) + + +class BaseGovDataReader(ReadFile): + """Plumbing shared by every GovData reader. + + Handles locator coercion, CKAN discovery, cached download with retries, and + column selection. Subclasses implement ``_parse`` (and override ``suffix`` + when the payload is not CSV). + """ # Persistent, content-addressed cache location (override per environment). cache_dir: ClassVar[str] = str(Path(tempfile.gettempdir()) / "mloda-govdata-cache") @@ -58,12 +67,30 @@ def load_data(cls, data_access: Any, features: FeatureSet) -> Any: # Touch features first: the framework probes load_data(None, None) and # only tolerates AttributeError to detect scoped-access support. requested = sorted(features.get_all_names()) # deterministic column order - locator = GovDataLocator.coerce(data_access) - if locator is None: - raise ValueError(f"GovDataReader cannot handle data access {data_access!r}") + locator = cls._coerce_locator(data_access) table = cls._read_table(locator) + available = set(table.column_names) + missing = [name for name in requested if name not in available] + if missing: + raise ValueError(_unknown_features_message(missing, table.column_names, locator)) return table.select(requested) + @classmethod + def peek(cls, data_access: Any) -> dict[str, str]: + """Columns selectable as features for this locator, as name to Arrow type. + + Downloads through the cache, so a following feature request reuses the file. + """ + table = cls._read_table(cls._coerce_locator(data_access)) + return {field.name: str(field.type) for field in table.schema} + + @classmethod + def _coerce_locator(cls, data_access: Any) -> GovDataLocator: + locator = GovDataLocator.coerce(data_access) + if locator is None: + raise ValueError(f"{cls.__name__} cannot handle data access {data_access!r}") + return locator + @classmethod def _read_table(cls, locator: GovDataLocator) -> pa.Table: with build_client() as client: @@ -74,44 +101,18 @@ def _read_table(cls, locator: GovDataLocator) -> pa.Table: @classmethod def _parse(cls, path: Path, locator: GovDataLocator, distribution: ResolvedDistribution) -> pa.Table: - return parse_german_csv(path, cls._schema_for(locator, distribution)) + raise NotImplementedError(f"{cls.__name__} must implement _parse") - @staticmethod - def _schema_for(locator: GovDataLocator, distribution: ResolvedDistribution) -> dict[str, ColumnType] | None: - if locator.dataset_id == POPULATION_SLUG or POPULATION_URL_MARKER in distribution.url: - return POPULATION_SCHEMA - return None # unknown dataset: read every column as a string +class GovDataReader(BaseGovDataReader): + """Reads a single-header German-CSV distribution into a typed Arrow table. -class BundeswahlleiterinReader(GovDataReader): - """Reads the Bundeswahlleiterin kerg.csv result file (5-line preamble, 3-row merged header).""" - - @classmethod - def _parse(cls, path: Path, locator: GovDataLocator, distribution: ResolvedDistribution) -> pa.Table: - return parse_multi_header_csv(path, skiprows=5, header_rows=3, label_columns=4, value_type=ColumnType.INTEGER) - - -class UbaAirReader(GovDataReader): - """Reads the UBA Air Data v4 ``measures`` JSON endpoint into a typed Arrow table. - - The option value is a full ``measures`` URL (build one with - :func:`~mloda_plugin_govdata.feature_groups.govdata.uba.uba_measures_url`); the response - is flattened to one row per station and measurement timestamp. Reuses the client, cache, - retry, and direct-URL resolution; only the parse seam differs from the CSV readers. + ``schema`` maps column name to type; ``None`` reads every column as a string. + Subclass and set ``schema`` for a dataset with known typed columns. """ - @classmethod - def suffix(cls) -> tuple[str, ...]: - return (".json",) + schema: ClassVar[dict[str, ColumnType] | None] = None @classmethod def _parse(cls, path: Path, locator: GovDataLocator, distribution: ResolvedDistribution) -> pa.Table: - return parse_uba_measures(path) - - -class GovDataFeature(ReadFileFeature): - """Root FeatureGroup for GovData columns; inherits the any-framework rule to avoid resolver collisions.""" - - @classmethod - def input_data(cls) -> BaseInputData | None: - return GovDataReader() + return parse_german_csv(path, cls.schema) diff --git a/mloda_plugin_govdata/feature_groups/govdata/tests/test_cache.py b/mloda_plugin_govdata/feature_groups/govdata/tests/test_cache.py index 7486895..11848f4 100644 --- a/mloda_plugin_govdata/feature_groups/govdata/tests/test_cache.py +++ b/mloda_plugin_govdata/feature_groups/govdata/tests/test_cache.py @@ -6,7 +6,7 @@ import httpx import respx -from mloda_plugin_govdata.feature_groups.govdata.cache import DownloadCache +from mloda_plugin_govdata.feature_groups.govdata.core.cache import DownloadCache URL = "https://example.org/data.csv" BODY = b"Stichtag;Wert\r\n30.06.2020;1\r\n" diff --git a/mloda_plugin_govdata/feature_groups/govdata/tests/test_client.py b/mloda_plugin_govdata/feature_groups/govdata/tests/test_client.py index 3d5a2fd..803b936 100644 --- a/mloda_plugin_govdata/feature_groups/govdata/tests/test_client.py +++ b/mloda_plugin_govdata/feature_groups/govdata/tests/test_client.py @@ -4,7 +4,11 @@ import pytest import respx -from mloda_plugin_govdata.feature_groups.govdata.client import RetryableStatusError, build_client, request_with_retry +from mloda_plugin_govdata.feature_groups.govdata.core.client import ( + RetryableStatusError, + build_client, + request_with_retry, +) URL = "https://example.org/x" @@ -13,7 +17,7 @@ def _instant_backoff(monkeypatch: pytest.MonkeyPatch) -> None: # Collapse tenacity's backoff to zero so retry tests run instantly. monkeypatch.setattr( - "mloda_plugin_govdata.feature_groups.govdata.client._BASE_WAIT", + "mloda_plugin_govdata.feature_groups.govdata.core.client._BASE_WAIT", lambda _state: 0.0, ) diff --git a/mloda_plugin_govdata/feature_groups/govdata/tests/test_discovery.py b/mloda_plugin_govdata/feature_groups/govdata/tests/test_discovery.py index db7550d..1d33c35 100644 --- a/mloda_plugin_govdata/feature_groups/govdata/tests/test_discovery.py +++ b/mloda_plugin_govdata/feature_groups/govdata/tests/test_discovery.py @@ -4,19 +4,22 @@ from pathlib import Path import httpx +import pytest import respx -from mloda_plugin_govdata.feature_groups.govdata.client import build_client -from mloda_plugin_govdata.feature_groups.govdata.discovery import ( +from mloda_plugin_govdata.feature_groups.govdata.core.client import build_client +from mloda_plugin_govdata.feature_groups.govdata.core.discovery import ( Dataset, Resource, _select_resource, normalize_license, resolve_distribution, + search_datasets, ) -from mloda_plugin_govdata.feature_groups.govdata.locator import GovDataLocator +from mloda_plugin_govdata.feature_groups.govdata.core.locator import GovDataLocator PACKAGE_SHOW = "https://ckan.govdata.de/api/3/action/package_show" +PACKAGE_SEARCH = "https://ckan.govdata.de/api/3/action/package_search" SLUG = "einwohner-nach-altersgruppen-und-stadtbezirken" @@ -61,3 +64,96 @@ def test_select_resource_prefers_csv_over_first_non_csv() -> None: Resource(url="https://example.org/data.csv", format="CSV"), ] assert _select_resource(resources, 0).url.endswith(".csv") + + +def _search_page(count: int, names: list[str]) -> httpx.Response: + results = [{"name": name, "title": name.replace("-", " "), "resources": []} for name in names] + return httpx.Response(200, json={"success": True, "result": {"count": count, "results": results}}) + + +@respx.mock +def test_search_datasets_paginates_until_count() -> None: + route = respx.get(PACKAGE_SEARCH).mock( + side_effect=[_search_page(3, ["ds-a", "ds-b"]), _search_page(3, ["ds-c"])], + ) + with build_client() as client: + names = [d.name for d in search_datasets(client, "einwohner", page_size=2)] + assert names == ["ds-a", "ds-b", "ds-c"] + assert route.call_count == 2 + first, second = route.calls + assert dict(first.request.url.params) == {"q": "einwohner", "rows": "2", "start": "0"} + assert dict(second.request.url.params) == {"q": "einwohner", "rows": "2", "start": "2"} + + +@respx.mock +def test_search_datasets_empty_result() -> None: + route = respx.get(PACKAGE_SEARCH).mock(return_value=_search_page(0, [])) + with build_client() as client: + assert list(search_datasets(client, "no-such-dataset")) == [] + assert route.call_count == 1 + + +@respx.mock +def test_search_datasets_unsuccessful_payload_raises() -> None: + respx.get(PACKAGE_SEARCH).mock(return_value=httpx.Response(200, json={"success": False})) + with build_client() as client: + with pytest.raises(ValueError, match="package_search"): + list(search_datasets(client, "einwohner")) + + +@respx.mock +def test_search_datasets_caps_at_max_results_and_requests_no_extra_rows() -> None: + route = respx.get(PACKAGE_SEARCH).mock(return_value=_search_page(5, ["ds-a", "ds-b"])) + with build_client() as client: + names = [d.name for d in search_datasets(client, "einwohner", page_size=100, max_results=2)] + assert names == ["ds-a", "ds-b"] + assert route.call_count == 1 + assert dict(route.calls[0].request.url.params) == {"q": "einwohner", "rows": "2", "start": "0"} + + +@respx.mock +def test_search_datasets_stops_on_stalled_server_page() -> None: + # A server whose count promises more than it delivers must not loop forever. + route = respx.get(PACKAGE_SEARCH).mock( + side_effect=[_search_page(10, ["ds-a"]), _search_page(10, [])], + ) + with build_client() as client: + names = [d.name for d in search_datasets(client, "einwohner", page_size=1)] + assert names == ["ds-a"] + assert route.call_count == 2 + + +@respx.mock +def test_search_datasets_shrinks_rows_near_max_results_and_honors_ckan_base() -> None: + custom_search = "https://ckan.example.org/api/3/action/package_search" + route = respx.get(custom_search).mock( + side_effect=[_search_page(5, ["ds-a", "ds-b"]), _search_page(5, ["ds-c"])], + ) + with build_client() as client: + names = [ + d.name + for d in search_datasets( + client, + "einwohner", + ckan_base="https://ckan.example.org/api/3/action", + page_size=2, + max_results=3, + ) + ] + assert names == ["ds-a", "ds-b", "ds-c"] + assert route.call_count == 2 + # The second request only asks for the one row still allowed by max_results. + assert dict(route.calls[1].request.url.params) == {"q": "einwohner", "rows": "1", "start": "2"} + + +def test_search_datasets_rejects_bad_page_size() -> None: + with build_client() as client: + with pytest.raises(ValueError, match="page_size"): + list(search_datasets(client, "einwohner", page_size=0)) + + +@pytest.mark.live +def test_live_search_finds_population_dataset() -> None: + with build_client() as client: + names = [d.name for d in search_datasets(client, "einwohner altersgruppen stadtbezirken", max_results=100)] + assert SLUG in names diff --git a/mloda_plugin_govdata/feature_groups/govdata/tests/test_parse.py b/mloda_plugin_govdata/feature_groups/govdata/tests/test_parse.py index cbdaaa2..9b1d64d 100644 --- a/mloda_plugin_govdata/feature_groups/govdata/tests/test_parse.py +++ b/mloda_plugin_govdata/feature_groups/govdata/tests/test_parse.py @@ -6,7 +6,7 @@ import pyarrow as pa import pytest -from mloda_plugin_govdata.feature_groups.govdata.parse import ( +from mloda_plugin_govdata.feature_groups.govdata.core.parse import ( ColumnType, detect_encoding, parse_german_csv, diff --git a/mloda_plugin_govdata/feature_groups/govdata/tests/test_parse_hypothesis.py b/mloda_plugin_govdata/feature_groups/govdata/tests/test_parse_hypothesis.py index 2e5f8f8..60fbdc3 100644 --- a/mloda_plugin_govdata/feature_groups/govdata/tests/test_parse_hypothesis.py +++ b/mloda_plugin_govdata/feature_groups/govdata/tests/test_parse_hypothesis.py @@ -3,7 +3,7 @@ from hypothesis import given from hypothesis import strategies as st -from mloda_plugin_govdata.feature_groups.govdata.parse import ColumnType, parse_german_csv_bytes +from mloda_plugin_govdata.feature_groups.govdata.core.parse import ColumnType, parse_german_csv_bytes def _german_thousands(value: int) -> str: diff --git a/mloda_plugin_govdata/feature_groups/govdata/tests/test_reader.py b/mloda_plugin_govdata/feature_groups/govdata/tests/test_reader.py index 0e634ba..12f8750 100644 --- a/mloda_plugin_govdata/feature_groups/govdata/tests/test_reader.py +++ b/mloda_plugin_govdata/feature_groups/govdata/tests/test_reader.py @@ -2,19 +2,21 @@ import json from pathlib import Path +from typing import cast import httpx import pyarrow as pa import pytest import respx +from mloda.provider import FeatureSet from mloda.user import Feature, mloda -from mloda_plugin_govdata.feature_groups.govdata.reader import ( - BundeswahlleiterinReader, - GovDataFeature, - GovDataReader, -) +from mloda_plugin_govdata.feature_groups.govdata.bundeswahlleiterin import BundeswahlleiterinReader +from mloda_plugin_govdata.feature_groups.govdata.feature import GovDataFeature +from mloda_plugin_govdata.feature_groups.govdata.population import StuttgartPopulationReader +from mloda_plugin_govdata.feature_groups.govdata.reader import BaseGovDataReader, GovDataReader +from mloda_plugin_govdata.feature_groups.govdata.uba import UbaAirReader, uba_measures_url SLUG = "einwohner-nach-altersgruppen-und-stadtbezirken" PACKAGE_SHOW = "https://ckan.govdata.de/api/3/action/package_show" @@ -22,13 +24,31 @@ KERG_MEASURE = "Wahlberechtigte Erststimmen Endgültig" -def test_feature_group_uses_govdata_reader() -> None: - assert isinstance(GovDataFeature.input_data(), GovDataReader) +def _mock_population_endpoints(fixtures_dir: Path) -> None: + package_show = (fixtures_dir / "package_show.json").read_text(encoding="utf-8") + csv_bytes = (fixtures_dir / "population_sample.csv").read_bytes() + distribution_url = json.loads(package_show)["result"]["resources"][0]["url"] + respx.get(PACKAGE_SHOW).mock(return_value=httpx.Response(200, text=package_show)) + respx.get(distribution_url).mock(return_value=httpx.Response(200, content=csv_bytes, headers={"ETag": '"v1"'})) + + +class _FakeFeatureSet: + """Just enough FeatureSet surface for load_data.""" + + def __init__(self, names: set[str]) -> None: + self._names = names + + def get_all_names(self) -> set[str]: + return self._names + + +def test_feature_group_uses_base_govdata_reader() -> None: + assert isinstance(GovDataFeature.input_data(), BaseGovDataReader) @respx.mock def test_load_data_level2(fixtures_dir: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(GovDataReader, "cache_dir", str(tmp_path)) + monkeypatch.setattr(StuttgartPopulationReader, "cache_dir", str(tmp_path)) package_show = (fixtures_dir / "package_show.json").read_text(encoding="utf-8") csv_bytes = (fixtures_dir / "population_sample.csv").read_bytes() distribution_url = json.loads(package_show)["result"]["resources"][0]["url"] @@ -38,8 +58,8 @@ def test_load_data_level2(fixtures_dir: Path, tmp_path: Path, monkeypatch: pytes result = mloda.run_all( [ - Feature("Einwohner", options={GovDataReader.__name__: SLUG}), - Feature("Stadtbezirk", options={GovDataReader.__name__: SLUG}), + Feature("Einwohner", options={StuttgartPopulationReader.__name__: SLUG}), + Feature("Stadtbezirk", options={StuttgartPopulationReader.__name__: SLUG}), ], compute_frameworks=["PyArrowTable"], ) @@ -69,7 +89,7 @@ def test_resolves_without_explicit_compute_framework( @pytest.mark.live def test_live_end_to_end() -> None: result = mloda.run_all( - [Feature("Einwohner", options={GovDataReader.__name__: SLUG})], + [Feature("Einwohner", options={StuttgartPopulationReader.__name__: SLUG})], compute_frameworks=["PyArrowTable"], ) table = result[0] @@ -105,3 +125,96 @@ def test_elections_live_end_to_end() -> None: table = result[0] assert table.num_rows > 300 assert table.column("Gebiet").to_pylist()[-1] == "Bundesgebiet" + + +@respx.mock +def test_peek_population_level2(fixtures_dir: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(StuttgartPopulationReader, "cache_dir", str(tmp_path)) + _mock_population_endpoints(fixtures_dir) + assert StuttgartPopulationReader.peek(SLUG) == { + "Stichtag": "date32[day]", + "Stadtbezirk": "string", + "Alter in 10 Gruppen": "string", + "Einwohner": "int64", + } + + +@respx.mock +def test_generic_reader_reads_unknown_dataset_as_strings( + fixtures_dir: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + # The generic reader carries no dataset schema; every column comes back as a string. + monkeypatch.setattr(GovDataReader, "cache_dir", str(tmp_path)) + _mock_population_endpoints(fixtures_dir) + assert GovDataReader.peek(SLUG) == { + "Stichtag": "string", + "Stadtbezirk": "string", + "Alter in 10 Gruppen": "string", + "Einwohner": "string", + } + + +@respx.mock +def test_peek_elections_level2(fixtures_dir: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(BundeswahlleiterinReader, "cache_dir", str(tmp_path)) + kerg_bytes = (fixtures_dir / "kerg_sample.csv").read_bytes() + respx.get(KERG_URL).mock(return_value=httpx.Response(200, content=kerg_bytes, headers={"ETag": '"k1"'})) + columns = BundeswahlleiterinReader.peek(KERG_URL) + assert columns["Gebiet"] == "string" + assert columns[KERG_MEASURE] == "int64" + + +@respx.mock +def test_peek_uba_level2(fixtures_dir: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(UbaAirReader, "cache_dir", str(tmp_path)) + url = uba_measures_url(station=143, component=3, scope=2, date_from="2025-01-01", date_to="2025-01-01") + payload = (fixtures_dir / "uba_measures.json").read_bytes() + respx.get(url).mock(return_value=httpx.Response(200, content=payload, headers={"ETag": '"u1"'})) + columns = UbaAirReader.peek(url) + assert columns["station_id"] == "int64" + assert columns["value"] == "double" + + +def test_peek_rejects_unusable_data_access() -> None: + with pytest.raises(ValueError, match="cannot handle data access"): + GovDataReader.peek(123) + + +@pytest.mark.parametrize( + "reader", + [BaseGovDataReader, GovDataReader, StuttgartPopulationReader, BundeswahlleiterinReader, UbaAirReader], +) +def test_load_data_probe_still_raises_attribute_error(reader: type[BaseGovDataReader]) -> None: + # The framework probes load_data(None, None) and only tolerates AttributeError. + with pytest.raises(AttributeError): + reader.load_data(None, cast(FeatureSet, None)) + + +@respx.mock +def test_unknown_feature_names_available_columns( + fixtures_dir: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + monkeypatch.setattr(GovDataReader, "cache_dir", str(tmp_path)) + _mock_population_endpoints(fixtures_dir) + features = cast(FeatureSet, _FakeFeatureSet({"Einwohner_", "Stadtbezirk"})) + with pytest.raises(ValueError) as excinfo: + GovDataReader.load_data(SLUG, features) + message = str(excinfo.value) + assert "Unknown feature(s) 'Einwohner_'" in message + assert "Available: Alter in 10 Gruppen, Einwohner, Stadtbezirk, Stichtag." in message + assert "Did you mean 'Einwohner' instead of 'Einwohner_'?" in message + + +@respx.mock +def test_unknown_feature_suggestion_only_for_close_matches( + fixtures_dir: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + monkeypatch.setattr(GovDataReader, "cache_dir", str(tmp_path)) + _mock_population_endpoints(fixtures_dir) + features = cast(FeatureSet, _FakeFeatureSet({"Einwohner_", "zzzzz"})) + with pytest.raises(ValueError) as excinfo: + GovDataReader.load_data(SLUG, features) + message = str(excinfo.value) + assert "Unknown feature(s) 'Einwohner_', 'zzzzz'" in message + assert message.count("Did you mean") == 1 # no suggestion for 'zzzzz' + assert "Did you mean 'Einwohner' instead of 'Einwohner_'?" in message diff --git a/mloda_plugin_govdata/feature_groups/govdata/tests/test_uba.py b/mloda_plugin_govdata/feature_groups/govdata/tests/test_uba.py index 179091f..9c29f50 100644 --- a/mloda_plugin_govdata/feature_groups/govdata/tests/test_uba.py +++ b/mloda_plugin_govdata/feature_groups/govdata/tests/test_uba.py @@ -13,9 +13,9 @@ from mloda.user import Feature, mloda -from mloda_plugin_govdata.feature_groups.govdata.reader import UbaAirReader from mloda_plugin_govdata.feature_groups.govdata.uba import ( UBA_AIR_BASE, + UbaAirReader, parse_uba_measures_bytes, uba_measures_url, ) diff --git a/mloda_plugin_govdata/feature_groups/govdata/uba.py b/mloda_plugin_govdata/feature_groups/govdata/uba.py index 9f6e024..ec22e4a 100644 --- a/mloda_plugin_govdata/feature_groups/govdata/uba.py +++ b/mloda_plugin_govdata/feature_groups/govdata/uba.py @@ -1,9 +1,9 @@ -"""Umweltbundesamt (UBA) Air Data v4 ``measures`` endpoint: URL builder and JSON flatten. +"""Umweltbundesamt (UBA) Air Data v4 ``measures`` endpoint: reader, URL builder, JSON flatten. -The environment dataset is publisher-direct REST JSON, not a CSV distribution, so it -has a distinct shape from the GovData / Bundeswahlleiterin CSV readers. The response is -``{request, indices, data}`` where ``data`` is keyed by station then by measurement -start datetime, and each leaf is ``[component id, scope id, value, date end, index]``. +The environment dataset (M1 environment theme) is publisher-direct REST JSON, not a CSV +distribution, so it has a distinct shape from the GovData / Bundeswahlleiterin CSV readers. +The response is ``{request, indices, data}`` where ``data`` is keyed by station then by +measurement start datetime, and each leaf is ``[component id, scope id, value, date end, index]``. The leaf order is fixed by the v4 contract, so the flatten maps it to a canonical schema by position and uses the self-describing ``indices.data`` block only to detect a changed layout. """ @@ -12,12 +12,16 @@ import json import os +from pathlib import Path from typing import Any from urllib.parse import urlencode import pyarrow as pa -from .parse import ColumnType +from .core.discovery import ResolvedDistribution +from .core.locator import GovDataLocator +from .core.parse import ColumnType +from .reader import BaseGovDataReader UBA_AIR_BASE = "https://luftdaten.umweltbundesamt.de/api/air-data/v4" @@ -191,3 +195,21 @@ def parse_uba_measures(path: str | os.PathLike[str]) -> pa.Table: with open(path, "rb") as handle: data = handle.read() return parse_uba_measures_bytes(data) + + +class UbaAirReader(BaseGovDataReader): + """Reads the UBA Air Data v4 ``measures`` JSON endpoint into a typed Arrow table. + + The option value is a full ``measures`` URL (build one with :func:`uba_measures_url`); + the response is flattened to one row per station and measurement timestamp. Reuses the + client, cache, retry, and direct-URL resolution; only the parse seam differs from the + CSV readers. + """ + + @classmethod + def suffix(cls) -> tuple[str, ...]: + return (".json",) + + @classmethod + def _parse(cls, path: Path, locator: GovDataLocator, distribution: ResolvedDistribution) -> pa.Table: + return parse_uba_measures(path) diff --git a/pyproject.toml b/pyproject.toml index 76b443f..36f4978 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ dependencies = [ requires-python = ">=3.10" [project.optional-dependencies] +demo = ["marimo>=0.14"] dev = [ "tox", "tox-uv", @@ -53,6 +54,8 @@ strict = true ignore_missing_imports = true namespace_packages = true explicit_package_bases = true +# Marimo notebooks are generated cell functions without annotations; not library code. +exclude = ["^demos/"] [tool.bandit] exclude_dirs = [".tox", ".venv", "tests"] diff --git a/tests/test_demo_definition.py b/tests/test_demo_definition.py new file mode 100644 index 0000000..1827e40 --- /dev/null +++ b/tests/test_demo_definition.py @@ -0,0 +1,19 @@ +"""The marimo demo must define its app without running any cell (no network).""" + +import importlib.util +from pathlib import Path + +# A hard import on purpose: tox installs the demo extra, so a broken marimo +# dependency set must fail this test instead of skipping it. +import marimo + +DEMO_PATH = Path(__file__).resolve().parents[1] / "demos" / "govdata_demo.py" + + +def test_demo_defines_marimo_app() -> None: + spec = importlib.util.spec_from_file_location("govdata_demo", DEMO_PATH) + assert spec is not None + assert spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + assert isinstance(module.app, marimo.App) diff --git a/tox.ini b/tox.ini index 8754f8c..49e4e70 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,9 @@ envlist = python310 [testenv] usedevelop = true -extras = dev +extras = + dev + demo allowlist_externals = sh commands = pytest diff --git a/uv.lock b/uv.lock index 5ed5ff7..0a415fc 100644 --- a/uv.lock +++ b/uv.lock @@ -122,6 +122,100 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, ] +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "(python_full_version < '3.15' and implementation_name != 'PyPy' and sys_platform != 'emscripten') or (python_full_version < '3.11' and implementation_name != 'PyPy' and sys_platform == 'emscripten')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, + { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, + { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, + { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, + { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, + { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, + { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, + { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "click" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -140,6 +234,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, ] +[[package]] +name = "docutils" +version = "0.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/39/a4/5180d9afc57e8fca05601dd652bdff19604c218814037fe90ffc7625a50a/docutils-0.23.tar.gz", hash = "sha256:746f5060322511280a1e50eb76846ed6bf2342984b2ac04dc42caa1a8d78799e", size = 2303823, upload-time = "2026-05-27T17:41:06.934Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/91/30151a39f7570f448ed84529390628a651d7f27c87d73c9b887f8189695e/docutils-0.23-py3-none-any.whl", hash = "sha256:25d013af9bf23bc1c7b2b093dff4208166c53a94786c9e447808335ef1185fea", size = 634701, upload-time = "2026-05-27T17:40:58.442Z" }, +] + [[package]] name = "exceptiongroup" version = "1.3.1" @@ -229,6 +332,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, +] + [[package]] name = "librt" version = "0.11.0" @@ -314,6 +438,152 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ce/62/b40b382fa0c66fee1478073eb8db352a4a6beda4a1adccf1df911d8c289c/librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253", size = 102572, upload-time = "2026-05-10T18:17:06.809Z" }, ] +[[package]] +name = "loro" +version = "1.13.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/0a/a5171cc085b08a066bd1f7b686db9f6493fc45b69b4047ccea682255fdea/loro-1.13.1.tar.gz", hash = "sha256:0c3727bc52f5098576532edd399efc08ceb234c5d9235a19cbd5ea37c4fba485", size = 70462, upload-time = "2026-06-14T09:44:08.296Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/0e/78d538a3a4ed549bf67ddbcbc94c7f283f9bbb023b91615cb6e94aebe3e0/loro-1.13.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:881a45d7e557064b9749a86d390c256712c5313a08b89cfb832da6085764cb8c", size = 3389576, upload-time = "2026-06-14T09:41:13.695Z" }, + { url = "https://files.pythonhosted.org/packages/16/fc/97adb97b98871dd5837770effe54f8aa384eada9f8a92169de556d3c86fc/loro-1.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d8197e6ad7577950b7989d5e2e631195319e5da9eb1d37b88fb153813c3462ec", size = 3214088, upload-time = "2026-06-14T09:40:56.383Z" }, + { url = "https://files.pythonhosted.org/packages/8d/e2/3a6cfaaeef365f2dce48522397270ab8ab3738aa5ee0d57600bb55bbadb4/loro-1.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:634d3d271e586c8ee5f461f4fcca033509ebe18f12bbd6fc1cf5015b6d451961", size = 3456589, upload-time = "2026-06-14T09:37:29.065Z" }, + { url = "https://files.pythonhosted.org/packages/27/27/4a8dd890a2b286d44cc551ee7f7e99af840e665e2fd7302d9a854109b8c5/loro-1.13.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:585289f1ce720e64ed4c11b5dcdcd820696b140c7ec01169049979c079835a75", size = 3479469, upload-time = "2026-06-14T09:38:06.753Z" }, + { url = "https://files.pythonhosted.org/packages/18/65/4e73c5c25c9b524d8e8198b4fcfc528d854390ca57cfea027aaaf9082b71/loro-1.13.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:016f8ce02e15be615b91e503143ee57380cc88c882bd25a06f7ee36e14a83cfa", size = 3867965, upload-time = "2026-06-14T09:38:43.888Z" }, + { url = "https://files.pythonhosted.org/packages/6f/0c/ea0aee61f2a08eac87c6cf71ffdb85681f2d0842310df11c94b6aca41189/loro-1.13.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e307f6b18d698f1230aafca0e10791c268d9ed2d663d385fc4cac69145cba12", size = 3557253, upload-time = "2026-06-14T09:39:21.897Z" }, + { url = "https://files.pythonhosted.org/packages/89/99/fc1cf20c7f5db88417a46aabbe17e9f4d4aa046fd4036c3f5395c8907f6b/loro-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa325d3b519ec005d57abc0e1cf6c093a8bfbdc2b6fcce2202339a9c1af8fdc7", size = 3498675, upload-time = "2026-06-14T09:40:28.595Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9a/bf3f976141c7a981b92e38cdacdc7e287fdf460f412f2d6c8c6fb2714ced/loro-1.13.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:be581dd2a0b9ec61aa7f8b2805883d94e7db5408e15991c612507d3bb2c6e969", size = 3814885, upload-time = "2026-06-14T09:39:59.165Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/e3a9ccabc8d9b59a621f570b9aa267c9306286aa601e1661bd506d57ac12/loro-1.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b99d7f919bdfc2ae29dd69dfccef890dd9ac225b9802ce20b85260a90efa45fc", size = 3632197, upload-time = "2026-06-14T09:41:32.04Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c9/4a9e7799e327a007cd869e70586ac65d5e4ee0b0998194549335519906a0/loro-1.13.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a0cbd507903dd0f1017d602ef59f15a185493aaa5fe597149026c4f6d19a86ad", size = 3748608, upload-time = "2026-06-14T09:42:10.336Z" }, + { url = "https://files.pythonhosted.org/packages/69/f0/b16183e3bf647135077e59778ab74ff30292b1ba4ea0563ace3a26ecf848/loro-1.13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6836ed89e71a70a2a48fa989247ef463cfc53bd192315fe5df56f1d702a77c5f", size = 3836328, upload-time = "2026-06-14T09:42:49.429Z" }, + { url = "https://files.pythonhosted.org/packages/cc/e2/98bd5558b47e46952b85cc374a6433c9de068af40936777759f0b4a2c593/loro-1.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e3b3b52fb6a326fe33a6c8dda2725ab8678e35995042966e4cbc0dda884d89f", size = 3758591, upload-time = "2026-06-14T09:43:27.852Z" }, + { url = "https://files.pythonhosted.org/packages/74/8a/bec03707e0e724ef0b3b1c4b318e1f732b79ca0bff0ccb6fddb7f51db071/loro-1.13.1-cp310-cp310-win32.whl", hash = "sha256:defc33602140c410ac63bc8301ee2ae91141119b9622aa9a8481f5ab019179e8", size = 2858413, upload-time = "2026-06-14T09:44:29.256Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0f/e37c22d1c24f97423c213c81c4b0c1979da506a47fcb7c4252ddfae259e1/loro-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:24bd4f961ff269c332754467410fe54e30084f11e09b390842d88a8d7aef3d84", size = 3087131, upload-time = "2026-06-14T09:44:09.371Z" }, + { url = "https://files.pythonhosted.org/packages/6f/73/9f28cc5d626cae0b1129a62a6edd0922ce979aae67ccbd162a3052aa70c6/loro-1.13.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ca89672a72fd211caddec137cb5539ca7b9906e9afebabd25948590c042a52d7", size = 3388491, upload-time = "2026-06-14T09:41:15.078Z" }, + { url = "https://files.pythonhosted.org/packages/68/ee/8ada6f1ae19b9e1d69c7d9760fe4544a6a8baa6015951f260d368311beb3/loro-1.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:84026a187d2fab3f7a1386eb239042a8c334ef2493621fb7684fe758229d3f78", size = 3212708, upload-time = "2026-06-14T09:40:57.617Z" }, + { url = "https://files.pythonhosted.org/packages/5a/d8/f0c7430e9a767cc956f6f4414e8cc69b1382e15c13dd5fe8a6d099ac4270/loro-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1e5e572e69f3aeedd3dce43bb4cda602bd20c91ea001f6d034b89ce511affc5", size = 3454249, upload-time = "2026-06-14T09:37:30.905Z" }, + { url = "https://files.pythonhosted.org/packages/2e/c2/c8b10557879cabf6f9fb3858dee5fe0e4d72c0bbf390d5a3f4a1aa8c86fb/loro-1.13.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e87166c9bff35908e2a2b378def56c8a5ae6286ca7d3c14dd7d6a94a2018713e", size = 3478994, upload-time = "2026-06-14T09:38:08.32Z" }, + { url = "https://files.pythonhosted.org/packages/95/82/37a9163194b097c89f7c87062e8dc3067af288ffe36acf17d83e50577a03/loro-1.13.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:984f2c7c17f7ff5c11cad04d5aa22f76247800b4b10465845a64b0e7d5491a4a", size = 3868397, upload-time = "2026-06-14T09:38:45.22Z" }, + { url = "https://files.pythonhosted.org/packages/69/57/e2453b4fcb83c0ce238fe43e6073d32c257f1298c2209a7ca532701e24d6/loro-1.13.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3a91938bd8812f4aa445d8dc7f4a2296aae6993ae99b2573b8346d95a3112f9", size = 3556776, upload-time = "2026-06-14T09:39:23.613Z" }, + { url = "https://files.pythonhosted.org/packages/82/7a/8d0b98fd2cb7459ce662fc09d09f2c7759dace5281041f1a40ce88da7118/loro-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb40d659067c6d393e28c7fa1d47a74f02c21b002f631b296bc5956ed10e4e03", size = 3498669, upload-time = "2026-06-14T09:40:30.223Z" }, + { url = "https://files.pythonhosted.org/packages/56/e6/44cbc490efeb482ffdab05a9911de479cde2c5b15cf78cc380c461569770/loro-1.13.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba14aac864efa39060f429e13fe826dd7a3eea51770c7ba18f7ab417a966733e", size = 3814637, upload-time = "2026-06-14T09:40:00.704Z" }, + { url = "https://files.pythonhosted.org/packages/b9/57/1983a2c4febe009d8000c46c2aae91fa2a063ff006cbdce08c19ff3b691c/loro-1.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:236f0b5a606890782e9830233f049e9bf711999fe12163b6e57b2f16656477ab", size = 3631426, upload-time = "2026-06-14T09:41:33.545Z" }, + { url = "https://files.pythonhosted.org/packages/b4/6c/b825f30215048473451bbfc68570dac846d6b5c9a566c38e69fe4cf2c171/loro-1.13.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:86500c195f3542c1b286564ff283dd2d29acf87c029e3a968fce658f492eb70c", size = 3747911, upload-time = "2026-06-14T09:42:11.925Z" }, + { url = "https://files.pythonhosted.org/packages/b4/76/7c80c176de31b068cce0b608664541f7c45dc1522a0afac323454d6ea565/loro-1.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1e57c441619090013730a1ff64f4a541df56230f56f386004da54d4bbdfa5b49", size = 3836322, upload-time = "2026-06-14T09:42:51.032Z" }, + { url = "https://files.pythonhosted.org/packages/d4/b8/f26e7e04146a0958eeb136ce9e1584d6a0a663e917eee0019cbbfcd3fd95/loro-1.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c3115e10224001e517b262760126bd998c871168cbcfe822ecf0f32ba843d13a", size = 3758094, upload-time = "2026-06-14T09:43:29.478Z" }, + { url = "https://files.pythonhosted.org/packages/70/50/ff05b94aad55319162982fc81c7f5beab8c1fb4c77dec1364f79a2dab4d9/loro-1.13.1-cp311-cp311-win32.whl", hash = "sha256:6367d8b6f0e682e17f281df51f502d8c7034b3e115899169fffe5c47943f044a", size = 2859108, upload-time = "2026-06-14T09:44:30.842Z" }, + { url = "https://files.pythonhosted.org/packages/63/d9/9268de0d259470e9f5ccd64b502d5267cb83e22e5b9eb9680cfde916314e/loro-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:10ee34b857c0d0fe175874abc20aca2d900f464fda0873eb40eea64a13d120c5", size = 3087237, upload-time = "2026-06-14T09:44:10.946Z" }, + { url = "https://files.pythonhosted.org/packages/1d/63/9d58b58fa07691682056a1869332acde98ad3f5604c5167bc2c67a3499ac/loro-1.13.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:62944de6a84c71f7fb87dc5ea59e70f68c3910dbee08fa29f1d87d2aa79361f9", size = 3372403, upload-time = "2026-06-14T09:41:16.448Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a7/1e00449128a0124f05a0070ed042f6e6e6a1ae9d42462f341e5782d0b1c6/loro-1.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18483499a361ca72dd6512a889fea097ddd203d6d048cdeb202cfdb454daa6da", size = 3201108, upload-time = "2026-06-14T09:40:59.18Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a1/94c3ae7ca30252caed58895e9f0c711045f93903891f59cb17c255b1832b/loro-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f24b880c6ef075e092956e6cc8a1ca10d75dd0c42100c93314d941d21ce7e825", size = 3458390, upload-time = "2026-06-14T09:37:32.334Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e5/fbe091e3da9518b22cf81227d102a792c5f2427f011461192262082b4981/loro-1.13.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1692f6cd32715106fa6512cffd77dae386c19aa3d269e0aa540f562c901b4fb3", size = 3481720, upload-time = "2026-06-14T09:38:09.71Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c5/efe3ed6991bc2fca078e6f065e9b3c6af9caabcd37de9c45d001e27d02f7/loro-1.13.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c974a0c7534cdb3885922fac0762276726f2c6212ea42ff9f94fb66458646163", size = 3868987, upload-time = "2026-06-14T09:38:46.803Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a4/3e879c36de0d0726686a8863cc4c7cbd27977ab54602d9d5b519a6c038ad/loro-1.13.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a0ad60e62ee97de40e345a79dd0e8e1890589827517a85f2795499ecbe0c772d", size = 3563937, upload-time = "2026-06-14T09:39:25.155Z" }, + { url = "https://files.pythonhosted.org/packages/1b/24/384088890d15867dcec61420afcfe37c6a185dc38302426b53b77ab9a944/loro-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4bb41b0fc46d7310f9b863359bd9073e8be042906792418810c45246d52f3e2c", size = 3506609, upload-time = "2026-06-14T09:40:31.647Z" }, + { url = "https://files.pythonhosted.org/packages/59/fa/93a33f1e4f4482dfc3463be3395d29ad5769c213cc89890167aca6c7d682/loro-1.13.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fc251ab7ec8d4f45d6afba76b5c0fa804815df8322a7c5b4855c69d574fa3e4", size = 3820553, upload-time = "2026-06-14T09:40:02.103Z" }, + { url = "https://files.pythonhosted.org/packages/5e/77/209513ad828529b52bcadd43b438aa0c419d951c67dd8573c2d7fea29c5c/loro-1.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:799f93099a7397e3d4975507769beaca49b1545c28ce2f565e0c6a9d7114c7c5", size = 3635451, upload-time = "2026-06-14T09:41:35.078Z" }, + { url = "https://files.pythonhosted.org/packages/66/dc/9a800ea8c377af8b839d052e1c6dce090a5bb1a8d62f9bb72d8ac74d7fae/loro-1.13.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:dddaa54ab925096efae42d017e3ec3e1a5e96109f011c9457a364370346d8d57", size = 3751844, upload-time = "2026-06-14T09:42:13.511Z" }, + { url = "https://files.pythonhosted.org/packages/48/c9/0d4ea932f2a2aa5143ddc5abda9afc45036d99af068ed9176baa19f8e5b0/loro-1.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5438af35c12fcdfed73515bfec9d675ccee9ae0f6206402d42699f13921c489b", size = 3839548, upload-time = "2026-06-14T09:42:52.713Z" }, + { url = "https://files.pythonhosted.org/packages/ee/9b/ab9b732f43ccc895b97e848003f72edcdf46dc6adac77f5313e520699c86/loro-1.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aa9ab0684b64eea6bb4088697063c536e322688828946ef29ada34cdd7ab97fe", size = 3766156, upload-time = "2026-06-14T09:43:31.825Z" }, + { url = "https://files.pythonhosted.org/packages/ca/c3/7f7e28ccac959d2d0085fadec194f6b45713517155bb67939392a8675b0f/loro-1.13.1-cp312-cp312-win32.whl", hash = "sha256:f13be6490fd9afb86474cf8c20270c64efcb0b3a9b32309bae5611ffc5d4ec30", size = 2852620, upload-time = "2026-06-14T09:44:32.61Z" }, + { url = "https://files.pythonhosted.org/packages/22/10/42e780e90c5a0a8e35c4e4cc6467dd2158324f389d6183bf763434c51e89/loro-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:b40a60be6900f84704824d44d11d7a721e5b3602f472b71d9aa8310b301478f3", size = 3096879, upload-time = "2026-06-14T09:44:12.476Z" }, + { url = "https://files.pythonhosted.org/packages/93/06/e369dac8102485a1140d18c4823b88c97c97577adb5c0029e5163917f726/loro-1.13.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:969916c8747b8f3fc0234c1d8c04f3433d3dbf88ce1f5f90c816c7e2c80c8d5d", size = 3371860, upload-time = "2026-06-14T09:41:17.899Z" }, + { url = "https://files.pythonhosted.org/packages/4d/cc/0f661d4779efe66e61d825b31ff13da3277140820a1db2f846e466d5b5f2/loro-1.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:00b85365ff695dbff3b4e0513976025d92e80741475449857f0b18a143694c39", size = 3200732, upload-time = "2026-06-14T09:41:00.581Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ba/314c26e89492b4486634e96844bf5acf4ea88b1dc4fa04b06e47e92403be/loro-1.13.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c46783fa28dd5144f7ca5f20ac949ad7568ef6d511e4aec077566bd0e011f3da", size = 3457717, upload-time = "2026-06-14T09:37:33.867Z" }, + { url = "https://files.pythonhosted.org/packages/e8/42/990572d3e7f71f6930420185b9fb6669490078b88a8ff2e8171310b861b5/loro-1.13.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2ba4caa9b52d64dd2d1aaa0672cb058618e4fc722ab26393f5b2db51bd715fc5", size = 3481891, upload-time = "2026-06-14T09:38:11.153Z" }, + { url = "https://files.pythonhosted.org/packages/e7/b7/3a1dc21ba8b5ea4362df8d21f61b4273fdcc84504969a4868621faf428a4/loro-1.13.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8bd1b7b65b552b7863c2d8d0e041dc35b3ccaf86422c2d03cbd89903feee890", size = 3868735, upload-time = "2026-06-14T09:38:48.233Z" }, + { url = "https://files.pythonhosted.org/packages/9c/64/838072fc73ae993316df8d80aedc537092c73b5e0f27132de60b9cfaa31b/loro-1.13.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a1591e115e874d9e0ad7cf7b413390d26cafb5750d707897fd79a5d1f69b7e7", size = 3564176, upload-time = "2026-06-14T09:39:26.573Z" }, + { url = "https://files.pythonhosted.org/packages/57/c2/8c122a862b8f773ba40c6ade7a89afb9ed26382d23e024f4e66f47ad639f/loro-1.13.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05703492391f21bb251181a2ee9f1f68425d73efa94810271e25087faca5c23d", size = 3506207, upload-time = "2026-06-14T09:40:33.132Z" }, + { url = "https://files.pythonhosted.org/packages/35/ff/49426f106fcc8b3cdc22dc46af68a1f74ab1675aea07c5244616e2b7b805/loro-1.13.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:862f4948e3a0da4eee417d159a728a8f75a309d02e9aeb4309cbc96e5e48f3c6", size = 3820941, upload-time = "2026-06-14T09:40:05.009Z" }, + { url = "https://files.pythonhosted.org/packages/ab/23/a40c73fb33eb5cc4bbf44ea81efe147cb804c1b9c4d41c37e2dc5e1e1a21/loro-1.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f44bb3e017b851c518a75253a5648c168796080a7dd7c370f991e4c26c929eb", size = 3634400, upload-time = "2026-06-14T09:41:36.483Z" }, + { url = "https://files.pythonhosted.org/packages/3b/06/5e1460d701c1617d424e6fc930f73772e4aba7ba4c0afc35c14aaea66b64/loro-1.13.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee1819567c55821d18bae00779427ed57e9287e2620dc773922d9efc9215d361", size = 3750693, upload-time = "2026-06-14T09:42:15.047Z" }, + { url = "https://files.pythonhosted.org/packages/0a/70/abd113d4211f843b2fc5425049225bd0453ba51dd4536be0f170c59cd89f/loro-1.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8e0ed665870c2e63e3b56a56e4cf4dcecd483e4658ecbbe7c913d09f251ccd9a", size = 3839587, upload-time = "2026-06-14T09:42:54.423Z" }, + { url = "https://files.pythonhosted.org/packages/b9/42/a04aa38208dfe8d66619a5a5dda9b586ca30069347ffd5eafb9f2e3c645d/loro-1.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:063a137ecd547259058abfecaa0dccfebbece1bb852960f811fff8ff07ff1549", size = 3765347, upload-time = "2026-06-14T09:43:33.507Z" }, + { url = "https://files.pythonhosted.org/packages/66/43/016a4904c0973965904b795599631becf74f42f7e5ab569790dcc02bc2ea/loro-1.13.1-cp313-cp313-win32.whl", hash = "sha256:89fa0c1d0ce435ff593d65bd6173ef7da0b1794cb8b542a275cc36052d147d13", size = 2852202, upload-time = "2026-06-14T09:44:34.904Z" }, + { url = "https://files.pythonhosted.org/packages/d9/7c/7f24a599aa6f1f6bd52052937f8e7089f8daf4269998e48836bc38c4c353/loro-1.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:31060b96a1a085dc585b7d81de56816ee52f50b3328580e6b0b778058c673b02", size = 3095693, upload-time = "2026-06-14T09:44:14.105Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c3/a4e18b1159a4d558e81ea566b98ea7dd402f1e81796db8b7ca31f6f23ecf/loro-1.13.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f2fc60ffbe4106701f33d5734a16cc491a1fb6b33bb3bc8d03faa22433be144", size = 3350416, upload-time = "2026-06-14T09:41:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/54/61/92bfb79c7c49d5e28fe380f71cbddbcd992ff5b94068864d43d0cc6fa928/loro-1.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dea742fc32b90a4215a3b253d254491487fe277e8dd93930c08fe98b931cb86d", size = 3188854, upload-time = "2026-06-14T09:41:02.049Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3b/95de0c3cf5c47a889b13ec0f8e163ba8d8cde4764f7f6212cfb6a87e109f/loro-1.13.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32a07707a786b8e187f421a1ac37180e96fd66dbe638672ca97e66432127ab53", size = 3451027, upload-time = "2026-06-14T09:37:35.301Z" }, + { url = "https://files.pythonhosted.org/packages/ab/4b/16c526348e9aa002676ca3be0e5d9ec6b2b9dc19871a63fdc66654614310/loro-1.13.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f9ca634f3f8fafa6e71eaf6949893ceb3142aba6d13e8e0ce2050945a5e99be", size = 3462773, upload-time = "2026-06-14T09:38:12.611Z" }, + { url = "https://files.pythonhosted.org/packages/e3/01/b267a962fd67120fe88696f959d92a2f706c1845e68c56270b5388208510/loro-1.13.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6241dcaeb655775dd6bb170646f8efc6697449535cc6a6af6834bb7a9817ab7", size = 3859936, upload-time = "2026-06-14T09:38:49.49Z" }, + { url = "https://files.pythonhosted.org/packages/0f/9b/8ff0df1c7a95440f78b8034007189aaf57e3a7d1758cca8009b38ab0911e/loro-1.13.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd4e186c1332dedef5dfec6acd3f8b31d9f57040ea4cbd5332ab6c7fdd2a97db", size = 3547317, upload-time = "2026-06-14T09:39:28.181Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a6/824feabb3d22feb506c945c53c3548df954ca2666206b05aa151f7cbe110/loro-1.13.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09afd86ff1c7ebc1a208cb0323fe05024d7f7eeea7025f0d029e1b23b606ad58", size = 3485675, upload-time = "2026-06-14T09:40:34.624Z" }, + { url = "https://files.pythonhosted.org/packages/28/1e/ae9fb467ef99998c8ecb216ce1e101064440ad1b6dabe2e1bfbc10cf4369/loro-1.13.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d4fcecf5dca67b3987dc33858e675565f7ff997a50f9f9b3d3ca77c0ee5a3094", size = 3805507, upload-time = "2026-06-14T09:40:06.601Z" }, + { url = "https://files.pythonhosted.org/packages/f2/85/e55ee4140ce9521ecd625479ee453afc13e46b7db1bd3efae36c27db4bd6/loro-1.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f245ba8648555adb4e11f8e0d1c87a9864428e103fd80cbd758d4a800c848e8", size = 3628421, upload-time = "2026-06-14T09:41:38.084Z" }, + { url = "https://files.pythonhosted.org/packages/63/95/c2d1e00c06c5b79af1de3d575d583894039859f63a0bb8b9cd64c78a1d9f/loro-1.13.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d85fe33fae897cb7d94adf350c7593290f07fa72b0a576b4d233228bf77eb75d", size = 3733318, upload-time = "2026-06-14T09:42:16.762Z" }, + { url = "https://files.pythonhosted.org/packages/5e/91/ec18db26e5b1054438d283bd3c45db6006a5c2e8f649cbc8b96b6e86de89/loro-1.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:e97be90bb75b83a0e1e5d01697e422b37795c0972d5c0f209d6f598a32abcedb", size = 3832278, upload-time = "2026-06-14T09:42:55.931Z" }, + { url = "https://files.pythonhosted.org/packages/8f/2c/aff8793496d5b39ce77f2b29744d8ade1a0649e196674e8bed2fae0ebe49/loro-1.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f02dab4ab8e8c3d4f38434b2724fc5793d088de663ff7cd9b3e5c545d4601d62", size = 3748142, upload-time = "2026-06-14T09:43:35.066Z" }, + { url = "https://files.pythonhosted.org/packages/10/af/768d2390576ca67a971146682426002467f871dd9681ad9527457c603d0a/loro-1.13.1-cp314-cp314-win32.whl", hash = "sha256:a1defe484248b0611c95b70792ac26021eb953fa4b090a5fd119c5d676e20e31", size = 2842651, upload-time = "2026-06-14T09:44:36.383Z" }, + { url = "https://files.pythonhosted.org/packages/b5/54/b486cd95bd4368f5558bb246cfa12343959808f3633f7439f3123eab5e5f/loro-1.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:5234b6363ce558a477d2ae68ba50e082e26f30f86e05d0e24ae364c75d004718", size = 3074789, upload-time = "2026-06-14T09:44:15.645Z" }, + { url = "https://files.pythonhosted.org/packages/1d/c4/4924e599909c31013075a96b208abcdfddf5679efa42ee0831d149f09573/loro-1.13.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c52a31d2cc73017df7cf648d375e27f99db07634df4a090b08e1a6aab9b36fc4", size = 3447525, upload-time = "2026-06-14T09:37:37.028Z" }, + { url = "https://files.pythonhosted.org/packages/ab/76/dffe9959f110c565f4782a96edf647729cc73dade7a1aae199d33457e60e/loro-1.13.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49c9a8f10f81835202c3bd708c8a4ac34e6a3917b69aac144b55b100a85caf36", size = 3449374, upload-time = "2026-06-14T09:38:13.965Z" }, + { url = "https://files.pythonhosted.org/packages/2b/80/d184dce3bbd17200842e5f29ae20cc35bfc5e261e36f359a80efa0ea0815/loro-1.13.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:06b53fb6ce7d173ec74c12ed694782ab3fbc8a07bd42bee646d714f405cdb27e", size = 3843999, upload-time = "2026-06-14T09:38:50.96Z" }, + { url = "https://files.pythonhosted.org/packages/e7/00/fce97f14e6c01e17e2b7127bcfb37d7bd0ff33f7e95a2fbf8be3b4cb7b56/loro-1.13.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ab26c84a3be3007f17e00947953e5f6a8acafefbb28fb51763c87314f94ac7d", size = 3536236, upload-time = "2026-06-14T09:39:29.537Z" }, + { url = "https://files.pythonhosted.org/packages/0c/10/48432999bea2c0fe456a78a3e6279997e61d8fce628137bc1d82943b80f3/loro-1.13.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f673c066c0eb5c442f067f5bd90fd3d70abef3d8b5ee1fc58672b029743a4785", size = 3481566, upload-time = "2026-06-14T09:40:36.234Z" }, + { url = "https://files.pythonhosted.org/packages/14/47/bf5f76066ba7634a7b48ad23d0d0979a2c4169c227ce9e1295016c3d2ca4/loro-1.13.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0ffa14f843ba3ddaf53b176f205f0e37621afb76e417afedcc2dbbe6925975f", size = 3797204, upload-time = "2026-06-14T09:40:08.159Z" }, + { url = "https://files.pythonhosted.org/packages/41/cd/4924259b0840e1ce3b468e259e2e708d00502540bea3bc6b39f6dabf6ef8/loro-1.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a50ded264d5db56e880ebcf92c80bfd9b1780fdfdf87d56e11d31ee5a2c6b1fb", size = 3625068, upload-time = "2026-06-14T09:41:39.607Z" }, + { url = "https://files.pythonhosted.org/packages/fe/d8/6fd76c38094bb2aaea9c3515708ff9f758ace45e722dd416ec37ca0385a5/loro-1.13.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:9628d59233e5f993758ac914f97555296e758aaa43892cad871a27a3ad072a3b", size = 3721177, upload-time = "2026-06-14T09:42:18.213Z" }, + { url = "https://files.pythonhosted.org/packages/67/a7/eeb8356e51779ca80820df203f33589e4387630c2e04711c5723848d5f62/loro-1.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:d0c5af71cf2a08bef1cc4dfe8baccba403bd8d9fb0700a95cc9bc3f736399661", size = 3824690, upload-time = "2026-06-14T09:42:57.55Z" }, + { url = "https://files.pythonhosted.org/packages/89/9d/471258dbf471160816e41dcdd9abf4c31b33c74462df6a5f4a9fa9bfb3db/loro-1.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3bcd020a12b899db8d24b6f7a0db8d92eb186ecb283ed50baac43014cacb7ff9", size = 3742862, upload-time = "2026-06-14T09:43:36.664Z" }, + { url = "https://files.pythonhosted.org/packages/9e/96/1a5d508afe5570af35a37c6af65152f58848f54b498083f445fb4daf27ae/loro-1.13.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0962308ad8e53d3cb74a151062f3d075b994b3324b3217af67b136225af5204", size = 3453807, upload-time = "2026-06-14T09:37:41.689Z" }, + { url = "https://files.pythonhosted.org/packages/b8/5a/164b13e23a9897d895b4612eb0a7c2140b4c941eb12925c350bca96d01a5/loro-1.13.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99385246166f3bddc0bb686cdf61909969d214ff0c1f0e36db5fec9aa1244579", size = 3478688, upload-time = "2026-06-14T09:38:18.911Z" }, + { url = "https://files.pythonhosted.org/packages/38/d6/a57a182629f6ff20c3e650d303b5d821e21edb1539cd64886c2659f171af/loro-1.13.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a29d5cbb17e8f317a1818dc87280c389e3c22c0f0da8e5f1e34c95333d311c48", size = 3862744, upload-time = "2026-06-14T09:38:55.463Z" }, + { url = "https://files.pythonhosted.org/packages/17/89/2eb3b0643c5249ae98cbb259ecd6a737986ecec215ff31e7c682ebd198d1/loro-1.13.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:837251c39e43c99e241bb253e3b05a1a5424282ddc905d34d0f554ede8bcbe39", size = 3555358, upload-time = "2026-06-14T09:39:33.923Z" }, + { url = "https://files.pythonhosted.org/packages/6d/3a/8ec8674a323b9872b4b7b59e2c2bfad14aa825a7d551df0ae0ffe1001c3e/loro-1.13.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:70dc63bbf6dcbf20cfe0210e23beb0a509d3608d5ad4d3004c6d85107f945d1d", size = 3630381, upload-time = "2026-06-14T09:41:44.317Z" }, + { url = "https://files.pythonhosted.org/packages/70/19/9993e6b0bb882f219d6768ffc0cde96efd6bf07d54db92fa02dbca40a8d0/loro-1.13.1-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:3caa4acb70cba3d562cd939618b4d72c2a46000e060b5077a0712c53e9276747", size = 3747925, upload-time = "2026-06-14T09:42:23.226Z" }, + { url = "https://files.pythonhosted.org/packages/87/3c/b0500c59856c6f807ec0459105584ed78e8fc2827520ca198dc5d94e4a47/loro-1.13.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:01ed5d2fae9cb10c7ad0c8cb31f7b25bef938077a1094a1449340c48cca130cd", size = 3838478, upload-time = "2026-06-14T09:43:02.285Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4e/a8d486bc7cce088e50124b25da24a3347d4a909f315562d2cf8bbc7cd742/loro-1.13.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:19823f7fcedb24e7c90708c25135311dacc87b00091a8c28688eeac7e59d17be", size = 3759483, upload-time = "2026-06-14T09:43:41.507Z" }, + { url = "https://files.pythonhosted.org/packages/66/57/35da08c298f0f74212a5b03aa7b2eabb49f03d79d7c7d1dc9b85795a1f53/loro-1.13.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df3cc735e6e514fa700c1142ef5aa003eb2fa972cfe6cf5a1faad73a5b2b9f35", size = 3452474, upload-time = "2026-06-14T09:37:42.941Z" }, + { url = "https://files.pythonhosted.org/packages/60/7b/ba4a7542bcce0c6c542fcdddf058140fed930c43763faa449d37060a8d1d/loro-1.13.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:774d466c51506e9bd2e2d889078d405642f14474d5b3d97323ba64e1709792cf", size = 3476693, upload-time = "2026-06-14T09:38:20.352Z" }, + { url = "https://files.pythonhosted.org/packages/01/37/2bea377c69e277d5c89fb53e7ca5a55ed025f58ed91ec8b7d879cd7363ad/loro-1.13.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fea37c8a2d307340fefd9ecf9dfe2d0cd050e5fe52d28dc59f3b74e2f4d86a59", size = 3862157, upload-time = "2026-06-14T09:38:57.247Z" }, + { url = "https://files.pythonhosted.org/packages/03/08/2c7a178b5a761f081b72a8001e42b71c0f7b1d088242bdd739fda7332d71/loro-1.13.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a18812dd5129975c89ed3c279b96d05a7129d671b3c4e07d47da5d2a5a44143", size = 3553687, upload-time = "2026-06-14T09:39:35.415Z" }, + { url = "https://files.pythonhosted.org/packages/ab/91/b61a37a2044cd36f0b89c1ab97a6444fd5eb4256f17ed5e41b855f05ecd5/loro-1.13.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a252b6badfcd7d43f45c58c61282a256a2bebb50315d363409d34eebb576fa86", size = 3497885, upload-time = "2026-06-14T09:40:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/9f/1c/ae412692eb78f270ce31b95fe212902c3906869426c11a5d4905a22ca776/loro-1.13.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:26be461277ea678a47e937b077b4fcc9b7985083b3b7eba5e33bcdcffb511ab0", size = 3816446, upload-time = "2026-06-14T09:40:11.179Z" }, + { url = "https://files.pythonhosted.org/packages/4a/fe/e65cead425aa667b2d4b484c418609f088b23321062103bdb3751d4fafaa/loro-1.13.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:88359d12071cb96c1c96912a90c4102f538354bef2100b3797c225418aca8588", size = 3629093, upload-time = "2026-06-14T09:41:45.795Z" }, + { url = "https://files.pythonhosted.org/packages/2e/12/479dfce8e159f6b238a9dc5e0daf8b55626ff245a292415d902d3dda3b58/loro-1.13.1-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:403ca90311137f94d6a26723dbb0bf60af0a83c98577668f3939e5b67b9359e6", size = 3747230, upload-time = "2026-06-14T09:42:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/26/53/0a56911f810bfbcf426a85048ad51413f80d48643ee9e75e359e7f301309/loro-1.13.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:24ac898cf8089df7a7dd1adb116acba8e1a68dde0060db5bb29f4067858fa068", size = 3838314, upload-time = "2026-06-14T09:43:03.81Z" }, + { url = "https://files.pythonhosted.org/packages/85/e7/1f4d4b588d1c20bececfb3e90d8d035ec4f8e7c0d9b45925eaa6c9b5e511/loro-1.13.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:531635135306ef42681837d8a0f7bafd34fcf146435ae9077f31fd89e949e5cf", size = 3758462, upload-time = "2026-06-14T09:43:43.649Z" }, +] + +[[package]] +name = "marimo" +version = "0.23.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click", marker = "sys_platform != 'emscripten'" }, + { name = "docutils" }, + { name = "itsdangerous" }, + { name = "jedi" }, + { name = "loro", marker = "sys_platform != 'android' and sys_platform != 'emscripten'" }, + { name = "markdown" }, + { name = "msgspec" }, + { name = "narwhals" }, + { name = "packaging" }, + { name = "psutil", marker = "sys_platform != 'android' and sys_platform != 'emscripten'" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "python-multipart" }, + { name = "pyyaml" }, + { name = "pyzmq", marker = "python_full_version < '3.15' and sys_platform != 'emscripten'" }, + { name = "starlette", marker = "sys_platform != 'emscripten'" }, + { name = "tomlkit" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, + { name = "websockets", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/56/e4b50516ae5ab7e6e14aae81e04cc49c36a97cdd01c2c213019dc84cd538/marimo-0.23.11.tar.gz", hash = "sha256:f32fc3095aa1e46d3b9cf9f67729b3e121eb91bffffb75f380e1449d83288878", size = 38616146, upload-time = "2026-06-25T20:04:37.585Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/fd/e4468116b197585d800c57e0da16678b087f870d4746772acc4a3f7eb373/marimo-0.23.11-py3-none-any.whl", hash = "sha256:69dbf51993988fb854b513da768b1a64cbc36750610b93569b4c1d10fe7e5c2a", size = 39057161, upload-time = "2026-06-25T20:04:34.51Z" }, +] + +[[package]] +name = "markdown" +version = "3.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, +] + [[package]] name = "markdown-it-py" version = "4.0.0" @@ -359,6 +629,9 @@ dependencies = [ ] [package.optional-dependencies] +demo = [ + { name = "marimo" }, +] dev = [ { name = "bandit" }, { name = "hypothesis" }, @@ -378,6 +651,7 @@ requires-dist = [ { name = "bandit", marker = "extra == 'dev'" }, { name = "httpx", specifier = ">=0.27" }, { name = "hypothesis", marker = "extra == 'dev'" }, + { name = "marimo", marker = "extra == 'demo'", specifier = ">=0.14" }, { name = "mloda", specifier = ">=0.9.0" }, { name = "mloda-testing", specifier = ">=0.3.1" }, { name = "mypy", marker = "extra == 'dev'" }, @@ -393,7 +667,7 @@ requires-dist = [ { name = "tox-uv", marker = "extra == 'dev'" }, { name = "uv", marker = "extra == 'dev'", specifier = ">=0.11.15" }, ] -provides-extras = ["dev"] +provides-extras = ["demo", "dev"] [[package]] name = "mloda-testing" @@ -407,6 +681,62 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/05/22/e8d25f5291c24cf9cc3add81516b19e1e4aeef29231f9f32ccf107e428d0/mloda_testing-0.3.2-py3-none-any.whl", hash = "sha256:f20db2d425d3c0372d15d5bd1a9b13d3d3769e37343124c6548a5cb848f5b911", size = 81356, upload-time = "2026-05-17T17:19:24.04Z" }, ] +[[package]] +name = "msgspec" +version = "0.21.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/60/f79b9b013a16fa3a58350c9295ddc6789f2e335f36ea61ed10a21b215364/msgspec-0.21.1.tar.gz", hash = "sha256:2313508e394b0d208f8f56892ca9b2799e2561329de9763b19619595a6c0f72c", size = 319193, upload-time = "2026-04-12T21:44:50.394Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/38/d591d9f66d43d897ecbd249f2833665823d19c8b043f16619bc8343e23df/msgspec-0.21.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72d9cd03241b8b2edb2e12dcc66c500fa480d8cbd71a8bac105809d468882064", size = 195172, upload-time = "2026-04-12T21:43:45.062Z" }, + { url = "https://files.pythonhosted.org/packages/69/1a/6899188b5982ec1324e0c629b7801eed2db987f6634fab58abd9fc82d317/msgspec-0.21.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed2ab278200e743a1d2610a4e0c8fc74f6cecb8548544cdec43f927bd9265238", size = 188316, upload-time = "2026-04-12T21:43:46.641Z" }, + { url = "https://files.pythonhosted.org/packages/9e/95/7e591b4fa11fdbbf9891164473c23420a8c781ef553295abe416bf335f42/msgspec-0.21.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd677e3001fdfed9186de72eab434da2976303cd5eb9550921d3d0c3e3e168ce", size = 216565, upload-time = "2026-04-12T21:43:48.081Z" }, + { url = "https://files.pythonhosted.org/packages/19/86/714feeaf3b84cf2027235681725593840153dedd2868578f9f2715e296bb/msgspec-0.21.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f667b90b37fad734a91671abd68e0d7f4d066862771b87e91c53996dcb7a9027", size = 222689, upload-time = "2026-04-12T21:43:49.385Z" }, + { url = "https://files.pythonhosted.org/packages/7d/b9/4384243e814f2579e5205e17d170b9c1a30121afd1393298d904817a7fa7/msgspec-0.21.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:49880fd20fdbcfe1b793f07dd83f12572bab679c9800352c8b2240289aa46a06", size = 222343, upload-time = "2026-04-12T21:43:50.612Z" }, + { url = "https://files.pythonhosted.org/packages/04/01/4b227d9c4057346271043632bad41979cf8c3dca372e41bb1f7d546395b2/msgspec-0.21.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ae0162e22849a5e91eaad907766525107523b0daea3df267a9fcb5ba4e0936ae", size = 225607, upload-time = "2026-04-12T21:43:52.129Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ce/27021d1c3e5da837743092a7b7a5e8818397e1f4c05ee8b068bd7d1fd78a/msgspec-0.21.1-cp310-cp310-win_amd64.whl", hash = "sha256:f041a2279f31e3a53319005e4d60ba77c085cfcbe394cdc7ce803c2d01fe9449", size = 188392, upload-time = "2026-04-12T21:43:53.384Z" }, + { url = "https://files.pythonhosted.org/packages/80/2b/daf7a8d6d7cf00e0dcd0439178b284ade701234abdcadf3385601da04fbd/msgspec-0.21.1-cp310-cp310-win_arm64.whl", hash = "sha256:1bf17cbd7b28a5dffc7e764c654eed8ccde5e0f1de7970628608304640d4ce4e", size = 174191, upload-time = "2026-04-12T21:43:54.6Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7f/bbc4e74cd33d316b75541149e4d35b163b63bce066530ae185a2ec3b5bfc/msgspec-0.21.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b504b6e7f7a22a24b27232b73034421692147865162daaec9f3bf62439007c87", size = 193131, upload-time = "2026-04-12T21:43:56.094Z" }, + { url = "https://files.pythonhosted.org/packages/c1/60/504886af1aaf854112663b842d5eea9a15d9588f9bf7d0d2df736424b84d/msgspec-0.21.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4692b7c1609155708c4418f88e92f63c13fdf08aa095c84bae82bad75b53389b", size = 186597, upload-time = "2026-04-12T21:43:57.242Z" }, + { url = "https://files.pythonhosted.org/packages/fa/54/d24ddeaa65b5278c9e67f48ce3c17a9831e8f3722f3c8322ee120aca22ef/msgspec-0.21.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d3124010b3815451494c85ff345e693cb9fe5889cfcbbef39ed8622e0e72319c", size = 215158, upload-time = "2026-04-12T21:43:58.442Z" }, + { url = "https://files.pythonhosted.org/packages/9f/75/bb79c8b89a93ae23cd33c0d802373f16feaf9633f05d8af77091350dda0a/msgspec-0.21.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6badc03b9725352219cca017bfe71c61f2fbd0fb5982b410ac17c97c213deb30", size = 219856, upload-time = "2026-04-12T21:44:00.015Z" }, + { url = "https://files.pythonhosted.org/packages/b4/9c/c5ca26b46f0ebbd3a6683695ef89396712cb9e4199fd1f0bc1dd968216b1/msgspec-0.21.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5d2d4116ebe3035a78d9ec76e99a9d64e5fa6d44fe61a9c5de7fd1acf54bcc69", size = 220314, upload-time = "2026-04-12T21:44:01.548Z" }, + { url = "https://files.pythonhosted.org/packages/c8/31/645a351c4285dce40ed6755c3dcc0aa648e26dacb20a98018fe2cce5e87b/msgspec-0.21.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0d1009f6715f5bff3b54d4ff5c7428ad96197e0534e1645b8e9b955890c84664", size = 223215, upload-time = "2026-04-12T21:44:02.884Z" }, + { url = "https://files.pythonhosted.org/packages/09/af/8bf15736a6dd3cb4f90c5467f6dc39197d2daaf10754490cdc0aa17b7312/msgspec-0.21.1-cp311-cp311-win_amd64.whl", hash = "sha256:c6faffe5bb644ec884052679af4dfd776d4b5ca90e4a7ec7e7e319e4e6b93a6e", size = 188554, upload-time = "2026-04-12T21:44:04.151Z" }, + { url = "https://files.pythonhosted.org/packages/ef/29/cc7db3a165b62d16e64a83f82eccb79655055cb5bc1f60459a6f9d7c82f2/msgspec-0.21.1-cp311-cp311-win_arm64.whl", hash = "sha256:ee9e3f11fa94603f7d673bf795cfa31b549c4a2c723bc39b45beb1e7f5a3fb99", size = 174517, upload-time = "2026-04-12T21:44:05.66Z" }, + { url = "https://files.pythonhosted.org/packages/6e/cf/317224852c00248c620a9bcf4b26e2e4ab8afd752f18d2a6ef73ebd423b6/msgspec-0.21.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4248cf0b6129b7d230eacd493c17cc2d4f3989f3bb7f633a928a85b7dcfa251", size = 196188, upload-time = "2026-04-12T21:44:07.181Z" }, + { url = "https://files.pythonhosted.org/packages/6d/81/074612945c0666078f7366f40000013de9f6ba687491d450df699bceebc9/msgspec-0.21.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5102c7e9b3acff82178449b85006d96310e690291bb1ea0142f1b24bcb8aabcb", size = 188473, upload-time = "2026-04-12T21:44:08.736Z" }, + { url = "https://files.pythonhosted.org/packages/8a/37/655101799590bcc5fddb2bd3fe0e6194e816c2d1da7c361725f5eb89a910/msgspec-0.21.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:846758412e9518252b2ac9bffd6f0e54d9ff614f5f9488df7749f81ff5c80920", size = 218871, upload-time = "2026-04-12T21:44:09.917Z" }, + { url = "https://files.pythonhosted.org/packages/b5/d1/d4cd9fe89c7d400d7a18f86ccc94daa3f0927f53558846fcb60791dce5d6/msgspec-0.21.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21995e74b5c598c2e004110ad66ec7f1b8c20bf2bcf3b2de8fd9a3094422d3ff", size = 225025, upload-time = "2026-04-12T21:44:11.191Z" }, + { url = "https://files.pythonhosted.org/packages/24/bf/e20549e602b9edccadeeff98760345a416f9cce846a657e8b18e3396b212/msgspec-0.21.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6129f0cca52992e898fd5344187f7c8127b63d810b2fd73e36fca73b4c6475ee", size = 222672, upload-time = "2026-04-12T21:44:12.481Z" }, + { url = "https://files.pythonhosted.org/packages/b4/68/04d7a8f0f786545cf9b8c280c57aa6befb5977af6e884b8b54191cbe44b3/msgspec-0.21.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ef3ec2296248d1f8b9231acb051b6d471dfde8f21819e86c9adaaa9f42918521", size = 227303, upload-time = "2026-04-12T21:44:13.709Z" }, + { url = "https://files.pythonhosted.org/packages/cc/4d/619866af2840875be408047bf9e70ceafbae6ab50660de7134ed1b25eb86/msgspec-0.21.1-cp312-cp312-win_amd64.whl", hash = "sha256:d4ab834a054c6f0cbeef6df9e7e1b33d5f1bc7b86dea1d2fd7cad003873e783d", size = 190017, upload-time = "2026-04-12T21:44:14.977Z" }, + { url = "https://files.pythonhosted.org/packages/5e/2e/a8f9eca8fd00e097d7a9e99ba8a4685db994494448e3d4f0b7f6e9a3c0f7/msgspec-0.21.1-cp312-cp312-win_arm64.whl", hash = "sha256:628aaa35c74950a8c59da330d7e98917e1c7188f983745782027748ee4ca573e", size = 175345, upload-time = "2026-04-12T21:44:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/7e/74/f11ede02839b19ff459f88e3145df5d711626ca84da4e23520cebf819367/msgspec-0.21.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:764173717a01743f007e9f74520ed281f24672c604514f7d76c1c3a10e8edb66", size = 196176, upload-time = "2026-04-12T21:44:17.613Z" }, + { url = "https://files.pythonhosted.org/packages/bb/40/4476c1bd341418a046c4955aff632ec769315d1e3cb94e6acf86d461f9ed/msgspec-0.21.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:344c7cd0eaed1fb81d7959f99100ef71ec9b536881a376f11b9a6c4803365697", size = 188524, upload-time = "2026-04-12T21:44:18.815Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d9/9e9d7d7e5061b47540d03d640fab9b3965ba7ae49c1b2154861c8f007518/msgspec-0.21.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48943e278b3854c2f89f955ddc6f9f430d3f0784b16e47d10604ee0463cd21f5", size = 218880, upload-time = "2026-04-12T21:44:20.028Z" }, + { url = "https://files.pythonhosted.org/packages/74/66/2bb344f34abb4b57e60c7c9c761994e0417b9718ec1460bf00c296f2a7ea/msgspec-0.21.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9aa659ebb0101b1cbc31461212b87e341d961f0ab0772aaf068a99e001ec4aa", size = 225050, upload-time = "2026-04-12T21:44:21.577Z" }, + { url = "https://files.pythonhosted.org/packages/1a/84/7c1e412f76092277bf760cef12b7979d03314d259ab5b5cafde5d0c1722d/msgspec-0.21.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7b27d1a8ead2b6f5b0c4f2d07b8be1ccfcc041c8a0e704781edebe3ae13c484", size = 222713, upload-time = "2026-04-12T21:44:22.83Z" }, + { url = "https://files.pythonhosted.org/packages/4e/27/0bba04b2b4ef05f3d068429410bc71d2cea925f1596a8f41152cccd5edb8/msgspec-0.21.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:38fe93e86b61328fe544cb7fd871fad5a27c8734bfda90f65e5dbe288ae50f61", size = 227259, upload-time = "2026-04-12T21:44:24.11Z" }, + { url = "https://files.pythonhosted.org/packages/b0/2d/09574b0eea02fed2c2c1383dbaae2c7f79dc16dcd6487a886000afb5d7c4/msgspec-0.21.1-cp313-cp313-win_amd64.whl", hash = "sha256:8bc666331c35fcce05a7cd2d6221adbe0f6058f8e750711413d22793c080ac6a", size = 189857, upload-time = "2026-04-12T21:44:25.359Z" }, + { url = "https://files.pythonhosted.org/packages/46/34/105b1576ad182879914f0c821f17ee1d13abb165cb060448f96fe2aff078/msgspec-0.21.1-cp313-cp313-win_arm64.whl", hash = "sha256:42bb1241e0750c1a4346f2aa84db26c5ffd99a4eb3a954927d9f149ff2f42898", size = 175403, upload-time = "2026-04-12T21:44:26.608Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ad/86954e987d1d6a5c579e2c2e7832b65e0fff194179fdac4f581536086024/msgspec-0.21.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fab48eb45fdbfbdb2c0edfec00ffc53b6b6085beefc6b50b61e01659f9f8757f", size = 196261, upload-time = "2026-04-12T21:44:27.807Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a1/c5e46c3e42b866199365e35d11dddfd1fbd8bba4fdb3c52f965b1607ce94/msgspec-0.21.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3cb779ea0c35bc807ff941d415875c1f69ca0be91a2e907ab99a171811d86a9a", size = 188729, upload-time = "2026-04-12T21:44:28.99Z" }, + { url = "https://files.pythonhosted.org/packages/85/7d/1e29a319d678d6cb962ae5bdf32a6858ebdf38f73bc654c0e9c742a0c2c8/msgspec-0.21.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68604db36b3b4dd9bf160e436e12798a4738848144cea1aca1cb984011eb160f", size = 219866, upload-time = "2026-04-12T21:44:31.104Z" }, + { url = "https://files.pythonhosted.org/packages/25/1f/cca084ca2572810fff12ea9dbdcbe39eac048f40daf4a9077b49fcbe8cee/msgspec-0.21.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3d6b9dc50948eaf65df54d2fd0ff66e6d8c32f116037209ee861810eb9b676cb", size = 224993, upload-time = "2026-04-12T21:44:32.649Z" }, + { url = "https://files.pythonhosted.org/packages/71/94/d2120fc9d419a89a3a7c13e5b7078798c4b392a96a02a6e2b3ce43a8766c/msgspec-0.21.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:52c5e21930942302394429c5a582ce7e6b62c7f983b3760834c2ce107e0dd6df", size = 223535, upload-time = "2026-04-12T21:44:33.839Z" }, + { url = "https://files.pythonhosted.org/packages/75/17/42418b66a3ad972a89bab73dd78b79cc6282bb488a25e73c853cee7443b9/msgspec-0.21.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:abbb39d65681fa24ed394e01af3d59d869068324f900c61d06062b7fb9980f2f", size = 227222, upload-time = "2026-04-12T21:44:35.093Z" }, + { url = "https://files.pythonhosted.org/packages/c4/33/265c894268cca88ff67b144ca2b4c522fc8b9a6f1966a3640c70516e78e1/msgspec-0.21.1-cp314-cp314-win_amd64.whl", hash = "sha256:5666b1b560b97b6ec2eb3fca8a502298ebac56e13bbca1f88523538ce83d01ea", size = 193810, upload-time = "2026-04-12T21:44:36.612Z" }, + { url = "https://files.pythonhosted.org/packages/3b/8f/a6d35f25bf1fc63c492fdd88fdce01ba0875ead48c2b91f90f33653b4131/msgspec-0.21.1-cp314-cp314-win_arm64.whl", hash = "sha256:d8b8578e4c83b14ceea4cef0d0b747e31d9330fe4b03b2b2ad4063866a178f93", size = 179125, upload-time = "2026-04-12T21:44:38.198Z" }, + { url = "https://files.pythonhosted.org/packages/c6/39/74839641e64b99d87da55af0fc472854d42b46e2183b9e2a67fe1bb2a512/msgspec-0.21.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:15f523d51c00ebad412213bfe9f06f0a50ec2b93e0c19e824a2d267cabb48ea2", size = 200171, upload-time = "2026-04-12T21:44:39.414Z" }, + { url = "https://files.pythonhosted.org/packages/70/9b/ce0cca6d2d87fcd4b6ff97600790494e64f26a2c55d61507cd2755c16193/msgspec-0.21.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4e47390360583ba3d5c6cb44cf0a9f61b0a06a899d3c2c00627cedebb2e2884b", size = 192879, upload-time = "2026-04-12T21:44:40.882Z" }, + { url = "https://files.pythonhosted.org/packages/a7/08/673a7bb05e5702dc787ddd3011195b509f9867927970da59052211929987/msgspec-0.21.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f60800e6299b798142dc40b0644da77ceac5ea0568be58228417eae14135c847", size = 226281, upload-time = "2026-04-12T21:44:42.181Z" }, + { url = "https://files.pythonhosted.org/packages/7d/45/86508cf57283e9070b3c447e3ab25b792a7a0855a3ea4e0c6d111ac34c97/msgspec-0.21.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5f8e9dfcd98419cf7568808470c4317a3fb30bef0e3715b568730a2b272a20d7", size = 229863, upload-time = "2026-04-12T21:44:43.442Z" }, + { url = "https://files.pythonhosted.org/packages/2c/62/e7c9367cd08d590559faacd711edbae36840342843e669440363f33c7d36/msgspec-0.21.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92d89dfad13bd1ea640dc3e37e724ed380da1030b272bdf5ecafb983c3ad7c75", size = 230445, upload-time = "2026-04-12T21:44:44.806Z" }, + { url = "https://files.pythonhosted.org/packages/42/b4/c0f54632103846b658a10930025f4de41c8724b5e4805a5f3b395586cb7e/msgspec-0.21.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0d03867786e5d7ba25d666df4b11320c27170f4aeafcb8e3a8b0a50a4fb742ca", size = 231822, upload-time = "2026-04-12T21:44:46.343Z" }, + { url = "https://files.pythonhosted.org/packages/ea/1d/0d85cc79d0ccf5508e9c846cc66552a6a16bf92abd1dbd8362617f7b35cd/msgspec-0.21.1-cp314-cp314t-win_amd64.whl", hash = "sha256:740fbf1c9d59992ca3537d6fbe9ebbf9eaf726a65fbf31448e0ecbc710697a63", size = 206650, upload-time = "2026-04-12T21:44:47.601Z" }, + { url = "https://files.pythonhosted.org/packages/90/91/56c5d560f20e6c20e9e4f55bd0e458f7f162aa689ee350346c04c48eac0b/msgspec-0.21.1-cp314-cp314t-win_arm64.whl", hash = "sha256:0d2cc73df6058d811a126ac3a8ad63a4dfa210c82f9cf5a004802eaf4712de90", size = 183149, upload-time = "2026-04-12T21:44:48.833Z" }, +] + [[package]] name = "mypy" version = "2.1.0" @@ -475,6 +805,15 @@ 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 = "narwhals" +version = "2.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/62/3c/c4ef2164a71c1a63d7f1ae411c4082c5fa872405106db60a4b7114989ad7/narwhals-2.22.1.tar.gz", hash = "sha256:d62920805a0a43b7ff8b54b0c0d3142d796f8a9301836ada37e573d6a33cbcd9", size = 647493, upload-time = "2026-06-05T12:34:34.051Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/ca/36339329c4604adbcc99c899b7eb1ce1a555c499b6a6860757dc9bfed36d/narwhals-2.22.1-py3-none-any.whl", hash = "sha256:60567d774edf77db53906f89d9fbd164e66e56d66d388e1e6990f17ac33cfb53", size = 454815, upload-time = "2026-06-05T12:34:32.289Z" }, +] + [[package]] name = "numpy" version = "2.2.6" @@ -877,6 +1216,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0b/e0/99ec5b02203c4e9ce878bc63d8caa06ac1f891e4d63bded9a5ced70fcb4f/pandas_stubs-3.0.3.260530-py3-none-any.whl", hash = "sha256:a6277eb1c8cebf48d9b2413fcd2e9a6b4ff479c934a223c29eacbc3058c4cb55", size = 173780, upload-time = "2026-05-30T17:47:39.13Z" }, ] +[[package]] +name = "parso" +version = "0.8.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/4b/90c937815137d43ce71ba043cd3566221e9df6b9c805f24b5d138c9d40a7/parso-0.8.7.tar.gz", hash = "sha256:eaaac4c9fdd5e9e8852dc778d2d7405897ec510f2a298071453e5e3a07914bb1", size = 401824, upload-time = "2026-05-01T23:13:02.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025, upload-time = "2026-05-01T23:12:58.867Z" }, +] + [[package]] name = "pathspec" version = "1.0.4" @@ -904,6 +1252,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + [[package]] name = "pyarrow" version = "24.0.0" @@ -961,6 +1337,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/51/be/6f79d55816d5c22557cf27533543d5d70dfe692adfbee4b99f2760674f38/pyarrow-24.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:c91d00057f23b8d353039520dc3a6c09d8608164c692e9f59a175a42b2ae0c19", size = 28131282, upload-time = "2026-04-21T10:51:16.815Z" }, ] +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + [[package]] name = "pydantic" version = "2.13.4" @@ -1101,6 +1486,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] +[[package]] +name = "pymdown-extensions" +version = "10.21.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/26/d1015444da4d952a1ca487a236b522eb979766f0295a0bd0c5fc089989a9/pymdown_extensions-10.21.3.tar.gz", hash = "sha256:72cfcf55f07aea0d4af2c4f11dd4e52466ddfb1bb819673146398e0bd3a77354", size = 854140, upload-time = "2026-05-13T12:57:32.267Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/85/545a951eecc270fcd688288c600017e2050a1aacb56c711d208586d3e470/pymdown_extensions-10.21.3-py3-none-any.whl", hash = "sha256:d7a5d08014fc571e80ca21dd6f854e31f94c489800350564d55d15b3c41e76b6", size = 269002, upload-time = "2026-05-13T12:57:30.296Z" }, +] + [[package]] name = "pyproject-api" version = "1.10.0" @@ -1157,6 +1555,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d8/db/795879cc3ddfe338599bddea6388cc5100b088db0a4caf6e6c1af1c27e04/python_discovery-1.2.2-py3-none-any.whl", hash = "sha256:e1ae95d9af875e78f15e19aed0c6137ab1bb49c200f21f5061786490c9585c7a", size = 31894, upload-time = "2026-04-07T17:28:48.09Z" }, ] +[[package]] +name = "python-multipart" +version = "0.0.32" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, +] + [[package]] name = "pytz" version = "2026.2" @@ -1230,6 +1637,79 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, ] +[[package]] +name = "pyzmq" +version = "27.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "(python_full_version < '3.15' and implementation_name == 'pypy' and sys_platform != 'emscripten') or (python_full_version < '3.11' and implementation_name == 'pypy' and sys_platform == 'emscripten')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/b9/52aa9ec2867528b54f1e60846728d8b4d84726630874fee3a91e66c7df81/pyzmq-27.1.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:508e23ec9bc44c0005c4946ea013d9317ae00ac67778bd47519fdf5a0e930ff4", size = 1329850, upload-time = "2025-09-08T23:07:26.274Z" }, + { url = "https://files.pythonhosted.org/packages/99/64/5653e7b7425b169f994835a2b2abf9486264401fdef18df91ddae47ce2cc/pyzmq-27.1.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:507b6f430bdcf0ee48c0d30e734ea89ce5567fd7b8a0f0044a369c176aa44556", size = 906380, upload-time = "2025-09-08T23:07:29.78Z" }, + { url = "https://files.pythonhosted.org/packages/73/78/7d713284dbe022f6440e391bd1f3c48d9185673878034cfb3939cdf333b2/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf7b38f9fd7b81cb6d9391b2946382c8237fd814075c6aa9c3b746d53076023b", size = 666421, upload-time = "2025-09-08T23:07:31.263Z" }, + { url = "https://files.pythonhosted.org/packages/30/76/8f099f9d6482450428b17c4d6b241281af7ce6a9de8149ca8c1c649f6792/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03ff0b279b40d687691a6217c12242ee71f0fba28bf8626ff50e3ef0f4410e1e", size = 854149, upload-time = "2025-09-08T23:07:33.17Z" }, + { url = "https://files.pythonhosted.org/packages/59/f0/37fbfff06c68016019043897e4c969ceab18bde46cd2aca89821fcf4fb2e/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:677e744fee605753eac48198b15a2124016c009a11056f93807000ab11ce6526", size = 1655070, upload-time = "2025-09-08T23:07:35.205Z" }, + { url = "https://files.pythonhosted.org/packages/47/14/7254be73f7a8edc3587609554fcaa7bfd30649bf89cd260e4487ca70fdaa/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd2fec2b13137416a1c5648b7009499bcc8fea78154cd888855fa32514f3dad1", size = 2033441, upload-time = "2025-09-08T23:07:37.432Z" }, + { url = "https://files.pythonhosted.org/packages/22/dc/49f2be26c6f86f347e796a4d99b19167fc94503f0af3fd010ad262158822/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:08e90bb4b57603b84eab1d0ca05b3bbb10f60c1839dc471fc1c9e1507bef3386", size = 1891529, upload-time = "2025-09-08T23:07:39.047Z" }, + { url = "https://files.pythonhosted.org/packages/a3/3e/154fb963ae25be70c0064ce97776c937ecc7d8b0259f22858154a9999769/pyzmq-27.1.0-cp310-cp310-win32.whl", hash = "sha256:a5b42d7a0658b515319148875fcb782bbf118dd41c671b62dae33666c2213bda", size = 567276, upload-time = "2025-09-08T23:07:40.695Z" }, + { url = "https://files.pythonhosted.org/packages/62/b2/f4ab56c8c595abcb26b2be5fd9fa9e6899c1e5ad54964e93ae8bb35482be/pyzmq-27.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0bb87227430ee3aefcc0ade2088100e528d5d3298a0a715a64f3d04c60ba02f", size = 632208, upload-time = "2025-09-08T23:07:42.298Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e3/be2cc7ab8332bdac0522fdb64c17b1b6241a795bee02e0196636ec5beb79/pyzmq-27.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:9a916f76c2ab8d045b19f2286851a38e9ac94ea91faf65bd64735924522a8b32", size = 559766, upload-time = "2025-09-08T23:07:43.869Z" }, + { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" }, + { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038, upload-time = "2025-09-08T23:07:51.234Z" }, + { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531, upload-time = "2025-09-08T23:07:52.795Z" }, + { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786, upload-time = "2025-09-08T23:07:55.047Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220, upload-time = "2025-09-08T23:07:57.172Z" }, + { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155, upload-time = "2025-09-08T23:07:59.05Z" }, + { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428, upload-time = "2025-09-08T23:08:00.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497, upload-time = "2025-09-08T23:08:02.15Z" }, + { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, + { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, + { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" }, + { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" }, + { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" }, + { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" }, + { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" }, + { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" }, + { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" }, + { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" }, + { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" }, + { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" }, + { url = "https://files.pythonhosted.org/packages/f3/81/a65e71c1552f74dec9dff91d95bafb6e0d33338a8dfefbc88aa562a20c92/pyzmq-27.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c17e03cbc9312bee223864f1a2b13a99522e0dc9f7c5df0177cd45210ac286e6", size = 836266, upload-time = "2025-09-08T23:09:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/58/ed/0202ca350f4f2b69faa95c6d931e3c05c3a397c184cacb84cb4f8f42f287/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f328d01128373cb6763823b2b4e7f73bdf767834268c565151eacb3b7a392f90", size = 800206, upload-time = "2025-09-08T23:09:41.902Z" }, + { url = "https://files.pythonhosted.org/packages/47/42/1ff831fa87fe8f0a840ddb399054ca0009605d820e2b44ea43114f5459f4/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c1790386614232e1b3a40a958454bdd42c6d1811837b15ddbb052a032a43f62", size = 567747, upload-time = "2025-09-08T23:09:43.741Z" }, + { url = "https://files.pythonhosted.org/packages/d1/db/5c4d6807434751e3f21231bee98109aa57b9b9b55e058e450d0aef59b70f/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:448f9cb54eb0cee4732b46584f2710c8bc178b0e5371d9e4fc8125201e413a74", size = 747371, upload-time = "2025-09-08T23:09:45.575Z" }, + { url = "https://files.pythonhosted.org/packages/26/af/78ce193dbf03567eb8c0dc30e3df2b9e56f12a670bf7eb20f9fb532c7e8a/pyzmq-27.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:05b12f2d32112bf8c95ef2e74ec4f1d4beb01f8b5e703b38537f8849f92cb9ba", size = 544862, upload-time = "2025-09-08T23:09:47.448Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" }, + { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371, upload-time = "2025-09-08T23:09:54.563Z" }, + { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, +] + [[package]] name = "respx" version = "0.23.1" @@ -1298,6 +1778,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, ] +[[package]] +name = "starlette" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "python_full_version < '3.11' or sys_platform != 'emscripten'" }, + { name = "typing-extensions", marker = "(python_full_version < '3.13' and sys_platform != 'emscripten') or (python_full_version < '3.11' and sys_platform == 'emscripten')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/e3/7c1dc7381d9f8ab7d854328ebfa884e62cb3f3d8549ddfd37c7814f42afa/starlette-1.3.1.tar.gz", hash = "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0", size = 2703240, upload-time = "2026-06-12T09:23:11.602Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/bb/2799cc2ede3ed41131f8975621e7213dfc7ef4acbbaadfa440f32500c370/starlette-1.3.1-py3-none-any.whl", hash = "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6", size = 73632, upload-time = "2026-06-12T09:23:10.017Z" }, +] + [[package]] name = "stevedore" version = "5.6.0" @@ -1379,6 +1872,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" }, ] +[[package]] +name = "tomlkit" +version = "0.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/db/03eaf4331631ef6b27d6e3c9b68c54dc6f0d63d87201fed600cc409307fd/tomlkit-0.15.0.tar.gz", hash = "sha256:7d1a9ecba3086638211b13814ea79c90dd54dd11993564376f3aa92271f5c7a3", size = 161875, upload-time = "2026-05-10T07:38:22.245Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/43/8bd850ee71a191bf072e31302c73a66be413fecdd98fdcd111ecbcce13ca/tomlkit-0.15.0-py3-none-any.whl", hash = "sha256:4dbc8f0fc024412b57ced8757ac7461305126a648ff8c2c807fcb8e133a78738", size = 41328, upload-time = "2026-05-10T07:38:23.517Z" }, +] + [[package]] name = "tox" version = "4.54.0" @@ -1493,6 +1995,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/7d/fa3a9960c95af9bbe2a629048760d0b9b4fead8ccd4f2235af747ec7cdf0/uv-0.11.15-py3-none-win_arm64.whl", hash = "sha256:4f39426a13dee24897aed60c4b98058c66f18bd983885ac5f4a54a04b24fbddf", size = 23198178, upload-time = "2026-05-18T19:57:14.68Z" }, ] +[[package]] +name = "uvicorn" +version = "0.49.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click", marker = "python_full_version < '3.11' or sys_platform != 'emscripten'" }, + { name = "h11", marker = "python_full_version < '3.11' or sys_platform != 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/1f/fa18009dea8469069cca78a4e877a008ab78f08b064bfc9ab891579077ff/uvicorn-0.49.0.tar.gz", hash = "sha256:ebf4271aa580d9de97f93192d4595176df6e91f9aae919ca73e4fc07df1e66a3", size = 91284, upload-time = "2026-06-03T22:01:30.448Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/fa/e1388bbcf24ef3274f45c0c1c7b501fd14971037c1b6ee23610553307497/uvicorn-0.49.0-py3-none-any.whl", hash = "sha256:ba3d14c3ee7e41c6c654c46c9eb489d33213cdd30aa1696eab1374337c13f68f", size = 71376, upload-time = "2026-06-03T22:01:29.037Z" }, +] + [[package]] name = "virtualenv" version = "21.1.0" @@ -1508,3 +2024,71 @@ sdist = { url = "https://files.pythonhosted.org/packages/2f/c9/18d4b36606d609184 wheels = [ { url = "https://files.pythonhosted.org/packages/78/55/896b06bf93a49bec0f4ae2a6f1ed12bd05c8860744ac3a70eda041064e4d/virtualenv-21.1.0-py3-none-any.whl", hash = "sha256:164f5e14c5587d170cf98e60378eb91ea35bf037be313811905d3a24ea33cc07", size = 5825072, upload-time = "2026-02-27T08:49:27.516Z" }, ] + +[[package]] +name = "websockets" +version = "16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/74/221f58decd852f4b59cc3354cccaf87e8ef695fede361d03dc9a7396573b/websockets-16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04cdd5d2d1dacbad0a7bf36ccbcd3ccd5a30ee188f2560b7a62a30d14107b31a", size = 177343, upload-time = "2026-01-10T09:22:21.28Z" }, + { url = "https://files.pythonhosted.org/packages/19/0f/22ef6107ee52ab7f0b710d55d36f5a5d3ef19e8a205541a6d7ffa7994e5a/websockets-16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8ff32bb86522a9e5e31439a58addbb0166f0204d64066fb955265c4e214160f0", size = 175021, upload-time = "2026-01-10T09:22:22.696Z" }, + { url = "https://files.pythonhosted.org/packages/10/40/904a4cb30d9b61c0e278899bf36342e9b0208eb3c470324a9ecbaac2a30f/websockets-16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:583b7c42688636f930688d712885cf1531326ee05effd982028212ccc13e5957", size = 175320, upload-time = "2026-01-10T09:22:23.94Z" }, + { url = "https://files.pythonhosted.org/packages/9d/2f/4b3ca7e106bc608744b1cdae041e005e446124bebb037b18799c2d356864/websockets-16.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7d837379b647c0c4c2355c2499723f82f1635fd2c26510e1f587d89bc2199e72", size = 183815, upload-time = "2026-01-10T09:22:25.469Z" }, + { url = "https://files.pythonhosted.org/packages/86/26/d40eaa2a46d4302becec8d15b0fc5e45bdde05191e7628405a19cf491ccd/websockets-16.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df57afc692e517a85e65b72e165356ed1df12386ecb879ad5693be08fac65dde", size = 185054, upload-time = "2026-01-10T09:22:27.101Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ba/6500a0efc94f7373ee8fefa8c271acdfd4dca8bd49a90d4be7ccabfc397e/websockets-16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2b9f1e0d69bc60a4a87349d50c09a037a2607918746f07de04df9e43252c77a3", size = 184565, upload-time = "2026-01-10T09:22:28.293Z" }, + { url = "https://files.pythonhosted.org/packages/04/b4/96bf2cee7c8d8102389374a2616200574f5f01128d1082f44102140344cc/websockets-16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:335c23addf3d5e6a8633f9f8eda77efad001671e80b95c491dd0924587ece0b3", size = 183848, upload-time = "2026-01-10T09:22:30.394Z" }, + { url = "https://files.pythonhosted.org/packages/02/8e/81f40fb00fd125357814e8c3025738fc4ffc3da4b6b4a4472a82ba304b41/websockets-16.0-cp310-cp310-win32.whl", hash = "sha256:37b31c1623c6605e4c00d466c9d633f9b812ea430c11c8a278774a1fde1acfa9", size = 178249, upload-time = "2026-01-10T09:22:32.083Z" }, + { url = "https://files.pythonhosted.org/packages/b4/5f/7e40efe8df57db9b91c88a43690ac66f7b7aa73a11aa6a66b927e44f26fa/websockets-16.0-cp310-cp310-win_amd64.whl", hash = "sha256:8e1dab317b6e77424356e11e99a432b7cb2f3ec8c5ab4dabbcee6add48f72b35", size = 178685, upload-time = "2026-01-10T09:22:33.345Z" }, + { url = "https://files.pythonhosted.org/packages/f2/db/de907251b4ff46ae804ad0409809504153b3f30984daf82a1d84a9875830/websockets-16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31a52addea25187bde0797a97d6fc3d2f92b6f72a9370792d65a6e84615ac8a8", size = 177340, upload-time = "2026-01-10T09:22:34.539Z" }, + { url = "https://files.pythonhosted.org/packages/f3/fa/abe89019d8d8815c8781e90d697dec52523fb8ebe308bf11664e8de1877e/websockets-16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:417b28978cdccab24f46400586d128366313e8a96312e4b9362a4af504f3bbad", size = 175022, upload-time = "2026-01-10T09:22:36.332Z" }, + { url = "https://files.pythonhosted.org/packages/58/5d/88ea17ed1ded2079358b40d31d48abe90a73c9e5819dbcde1606e991e2ad/websockets-16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af80d74d4edfa3cb9ed973a0a5ba2b2a549371f8a741e0800cb07becdd20f23d", size = 175319, upload-time = "2026-01-10T09:22:37.602Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ae/0ee92b33087a33632f37a635e11e1d99d429d3d323329675a6022312aac2/websockets-16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:08d7af67b64d29823fed316505a89b86705f2b7981c07848fb5e3ea3020c1abe", size = 184631, upload-time = "2026-01-10T09:22:38.789Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c5/27178df583b6c5b31b29f526ba2da5e2f864ecc79c99dae630a85d68c304/websockets-16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be95cfb0a4dae143eaed2bcba8ac23f4892d8971311f1b06f3c6b78952ee70b", size = 185870, upload-time = "2026-01-10T09:22:39.893Z" }, + { url = "https://files.pythonhosted.org/packages/87/05/536652aa84ddc1c018dbb7e2c4cbcd0db884580bf8e95aece7593fde526f/websockets-16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d6297ce39ce5c2e6feb13c1a996a2ded3b6832155fcfc920265c76f24c7cceb5", size = 185361, upload-time = "2026-01-10T09:22:41.016Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e2/d5332c90da12b1e01f06fb1b85c50cfc489783076547415bf9f0a659ec19/websockets-16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c1b30e4f497b0b354057f3467f56244c603a79c0d1dafce1d16c283c25f6e64", size = 184615, upload-time = "2026-01-10T09:22:42.442Z" }, + { url = "https://files.pythonhosted.org/packages/77/fb/d3f9576691cae9253b51555f841bc6600bf0a983a461c79500ace5a5b364/websockets-16.0-cp311-cp311-win32.whl", hash = "sha256:5f451484aeb5cafee1ccf789b1b66f535409d038c56966d6101740c1614b86c6", size = 178246, upload-time = "2026-01-10T09:22:43.654Z" }, + { url = "https://files.pythonhosted.org/packages/54/67/eaff76b3dbaf18dcddabc3b8c1dba50b483761cccff67793897945b37408/websockets-16.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7f0659570eefb578dacde98e24fb60af35350193e4f56e11190787bee77dac", size = 178684, upload-time = "2026-01-10T09:22:44.941Z" }, + { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" }, + { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" }, + { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" }, + { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" }, + { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" }, + { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" }, + { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" }, + { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" }, + { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" }, + { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" }, + { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" }, + { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" }, + { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, + { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, + { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, + { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, + { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, + { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, + { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, + { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, + { url = "https://files.pythonhosted.org/packages/72/07/c98a68571dcf256e74f1f816b8cc5eae6eb2d3d5cfa44d37f801619d9166/websockets-16.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:349f83cd6c9a415428ee1005cadb5c2c56f4389bc06a9af16103c3bc3dcc8b7d", size = 174947, upload-time = "2026-01-10T09:23:36.166Z" }, + { url = "https://files.pythonhosted.org/packages/7e/52/93e166a81e0305b33fe416338be92ae863563fe7bce446b0f687b9df5aea/websockets-16.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4a1aba3340a8dca8db6eb5a7986157f52eb9e436b74813764241981ca4888f03", size = 175260, upload-time = "2026-01-10T09:23:37.409Z" }, + { url = "https://files.pythonhosted.org/packages/56/0c/2dbf513bafd24889d33de2ff0368190a0e69f37bcfa19009ef819fe4d507/websockets-16.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f4a32d1bd841d4bcbffdcb3d2ce50c09c3909fbead375ab28d0181af89fd04da", size = 176071, upload-time = "2026-01-10T09:23:39.158Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8f/aea9c71cc92bf9b6cc0f7f70df8f0b420636b6c96ef4feee1e16f80f75dd/websockets-16.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0298d07ee155e2e9fda5be8a9042200dd2e3bb0b8a38482156576f863a9d457c", size = 176968, upload-time = "2026-01-10T09:23:41.031Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3f/f70e03f40ffc9a30d817eef7da1be72ee4956ba8d7255c399a01b135902a/websockets-16.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767", size = 178735, upload-time = "2026-01-10T09:23:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, +]