From a6304342b8f53f4bb9772127d5695423a72dadf0 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 14:00:20 +0200 Subject: [PATCH 01/51] feat: add endpoint registry generator --- pyproject.toml | 2 + scripts/gen_registry.py | 167 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 scripts/gen_registry.py diff --git a/pyproject.toml b/pyproject.toml index fb01526..3c9be20 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,8 @@ packages = ["src/pse_sdk"] [tool.pytest.ini_options] testpaths = ["tests"] +pythonpath = ["scripts"] [tool.ruff] src = ["src"] +extend-exclude = ["src/pse_sdk/_registry.py"] diff --git a/scripts/gen_registry.py b/scripts/gen_registry.py new file mode 100644 index 0000000..048772c --- /dev/null +++ b/scripts/gen_registry.py @@ -0,0 +1,167 @@ +"""Generate ``src/pse_sdk/_registry.py`` from the PSE OpenAPI spec. + +Usage:: + + python scripts/gen_registry.py # regenerate + python scripts/gen_registry.py --check # exit 1 if out of date + python scripts/gen_registry.py --spec openapi.json # use a local spec file +""" + +from __future__ import annotations + +import argparse +import json +import sys +import urllib.request +from pathlib import Path + +SPEC_URL = "https://api.raporty.pse.pl/api/openapi" +OUTPUT = Path(__file__).resolve().parents[1] / "src" / "pse_sdk" / "_registry.py" + +META_COLUMNS = ( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", +) +DATE_COLUMN = "business_date" + +HEADER = '''\ +# Generated by scripts/gen_registry.py from {url} +# (spec version {version}). Do not edit by hand. +from dataclasses import dataclass +from typing import Literal + +ColumnType = Literal["string", "number", "integer", "boolean"] + + +@dataclass(frozen=True) +class EndpointSpec: + """Schema of one PSE API endpoint.""" + + columns: dict[str, ColumnType] # all columns in API order -> JSON type + date_column: str | None # column for date-range filtering, None = no filter + meta_columns: tuple[str, ...] # dtime/period/business_date/publication_ts (+ _utc) + keys: tuple[str, ...] # primary key columns +''' + + +def load_spec(source: str) -> dict: + """Load the OpenAPI spec from a URL or a local file path.""" + if source.startswith(("http://", "https://")): + with urllib.request.urlopen(source, timeout=60) as resp: + return json.load(resp) + return json.loads(Path(source).read_text(encoding="utf-8")) + + +def parse_keys(paths: dict) -> dict[str, tuple[str, ...]]: + """Extract primary key columns from the by-key paths. + + ``/gen-jw/dtime_utc/{dtime_utc}/resource_code/{resource_code}`` + → ``{"gen-jw": ("dtime_utc", "resource_code")}`` + """ + keys: dict[str, tuple[str, ...]] = {} + for path in paths: + if "{" not in path: + continue + name, *parts = path.strip("/").split("/") + keys[name] = tuple(parts[::2]) + return keys + + +def build_registry(spec: dict) -> dict[str, dict]: + """Build a registry entry for every endpoint in *spec*, sorted by name.""" + keys = parse_keys(spec["paths"]) + registry: dict[str, dict] = {} + for name, schema in sorted(spec["components"]["schemas"].items()): + columns = {col: prop["type"] for col, prop in schema["properties"].items()} + registry[name] = { + "columns": columns, + "date_column": DATE_COLUMN if DATE_COLUMN in columns else None, + "meta_columns": tuple(c for c in META_COLUMNS if c in columns), + "keys": keys.get(name, ()), + } + return registry + + +def render(registry: dict[str, dict], spec_version: str) -> str: + """Render *registry* as the source code of ``_registry.py``.""" + lines = [HEADER.format(url=SPEC_URL, version=spec_version), ""] + + lines.append("Endpoint = Literal[") + lines += [f" {_quote(name)}," for name in registry] + lines += ["]", "", "REGISTRY: dict[Endpoint, EndpointSpec] = {"] + + for name, entry in registry.items(): + date_column = _quote(entry["date_column"]) if entry["date_column"] else "None" + lines.append(f" {_quote(name)}: EndpointSpec(") + lines.append(" columns={") + lines += [ + f" {_quote(col)}: {_quote(type_)}," + for col, type_ in entry["columns"].items() + ] + lines.append(" },") + lines.append(f" date_column={date_column},") + lines += _render_tuple("meta_columns", entry["meta_columns"]) + lines += _render_tuple("keys", entry["keys"]) + lines.append(" ),") + + lines.append("}") + return "\n".join(lines) + "\n" + + +def _render_tuple(field: str, items: tuple[str, ...]) -> list[str]: + if not items: + return [f" {field}=(),"] + return [ + f" {field}=(", + *(f" {_quote(item)}," for item in items), + " ),", + ] + + +def _quote(value: str) -> str: + """Return *value* as a double-quoted Python string literal.""" + return json.dumps(value) + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.add_argument( + "--spec", + default=SPEC_URL, + help="spec URL or local file path (default: %(default)s)", + ) + parser.add_argument( + "--check", + action="store_true", + help="exit 1 if the committed registry is out of date", + ) + args = parser.parse_args(argv) + + spec = load_spec(args.spec) + registry = build_registry(spec) + source = render(registry, spec["info"]["version"]) + + if args.check: + if not OUTPUT.exists() or OUTPUT.read_text(encoding="utf-8") != source: + print( + f"{OUTPUT.name} is out of date. Run: python scripts/gen_registry.py", + file=sys.stderr, + ) + return 1 + print(f"{OUTPUT.name} is up to date.") + return 0 + + OUTPUT.write_text(source, encoding="utf-8", newline="\n") + print(f"Wrote {len(registry)} endpoints to {OUTPUT}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From d62c2382ed036b50f6473a26f17c83c44242bd94 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 14:00:20 +0200 Subject: [PATCH 02/51] feat: add generated endpoint registry --- src/pse_sdk/_registry.py | 2748 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 2748 insertions(+) create mode 100644 src/pse_sdk/_registry.py diff --git a/src/pse_sdk/_registry.py b/src/pse_sdk/_registry.py new file mode 100644 index 0000000..89a6de7 --- /dev/null +++ b/src/pse_sdk/_registry.py @@ -0,0 +1,2748 @@ +# Generated by scripts/gen_registry.py from https://api.raporty.pse.pl/api/openapi +# (spec version 2.1.5). Do not edit by hand. +from dataclasses import dataclass +from typing import Literal + +ColumnType = Literal["string", "number", "integer", "boolean"] + + +@dataclass(frozen=True) +class EndpointSpec: + """Schema of one PSE API endpoint.""" + + columns: dict[str, ColumnType] # all columns in API order -> JSON type + date_column: str | None # column for date-range filtering, None = no filter + meta_columns: tuple[str, ...] # dtime/period/business_date/publication_ts (+ _utc) + keys: tuple[str, ...] # primary key columns + + +Endpoint = Literal[ + "afrr-status", + "cmbp-tp", + "cmbu-tu", + "codn", + "cor", + "cor-q-1", + "cordmax", + "crb-rozl", + "csdac-pln", + "csire-kpi1", + "csire-kpi10", + "csire-kpi2", + "csire-kpi3", + "csire-kpi4", + "csire-kpi5", + "csire-kpi6", + "csire-kpi7", + "csire-kpi8", + "csire-kpi9", + "eb-rozl", + "en-rozl", + "energy-prices", + "gen-jw", + "his-bil-mocy", + "his-gen-pal", + "his-gen-pal-sire", + "his-wlk-cal", + "it-oeb-rbb", + "it-oeb-rbn", + "it-omb-rbb", + "it-omb-rbn", + "it-opmb-rmb", + "it-ot-rbn", + "it-pp-rbb", + "it-pp-rbn", + "it-use-rbb", + "it-use-rbn", + "kmb-kro-rozl", + "krb-rozl", + "kse-load", + "lolp", + "mbp-tp", + "mbu-tu", + "oeb-bpkdbo", + "ogr-b", + "ogr-b-head", + "ogr-d1", + "ogr-d1-head", + "ogr-d2", + "ogr-d2-head", + "ogr-d39", + "ogr-d39-head", + "ogr-m", + "ogr-m-head", + "ogr-oper", + "ogr-oper-head", + "ogr-r", + "ogr-r-head", + "ogr-rmb", + "pdgobpkd", + "pdgopkd", + "pdgsz", + "pdwkseub", + "pk5l-wp", + "poeb-rbb", + "poeb-rbn", + "pomb-rbn", + "popmb-rmb", + "poze-redoze", + "price-cost", + "price-fcst", + "przeplywy-mocy", + "pwm-dost-op", + "pwm-igcc-in", + "pwm-rdb", + "pwm-rdn", + "pwm-rdn-rdb", + "rcco2", + "rce-pln", + "ro-prog", + "ro-rozl", + "sk", + "specwezlow", + "unav-pk5l", + "use-sprz-rbb", + "use-sprz-rbn", + "wmb", + "wsp-wrm", + "zeb-rozl", + "zmb", +] + +REGISTRY: dict[Endpoint, EndpointSpec] = { + "afrr-status": EndpointSpec( + columns={ + "event_time": "string", + "event_time_utc": "string", + "afrr_status": "integer", + "reason_code": "integer", + "reason": "string", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "event_time_utc", + "afrr_status", + "reason_code", + ), + ), + "cmbp-tp": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "onmb": "string", + "fcr_g": "number", + "fcr_d": "number", + "afrr_g": "number", + "afrr_d": "number", + "mfrrd_g": "number", + "mfrrd_d": "number", + "rr_d": "number", + "rr_g": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), + "cmbu-tu": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "period": "string", + "period_utc": "string", + "fcr_g": "number", + "fcr_d": "number", + "afrr_g": "number", + "afrr_d": "number", + "mfrrd_g": "number", + "mfrrd_d": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), + "codn": EndpointSpec( + columns={ + "period_from_date": "string", + "period_to_date": "string", + "price": "number", + "business_date": "string", + "publication_ts_utc": "string", + "publication_ts": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "period_from_date", + "period_to_date", + ), + ), + "cor": EndpointSpec( + columns={ + "dtime_utc": "string", + "dtime": "string", + "period": "string", + "period_utc": "string", + "cor_cost": "number", + "cor_fcst": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), + "cor-q-1": EndpointSpec( + columns={ + "period_from_date": "string", + "period_to_date": "string", + "cor_excl": "number", + "cor_inc": "number", + "business_date": "string", + "publication_ts_utc": "string", + "publication_ts": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "period_to_date", + "period_from_date", + ), + ), + "cordmax": EndpointSpec( + columns={ + "cordmax_fcst": "number", + "cordmax_cost": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "cordmax_fcst", + "cordmax_cost", + ), + ), + "crb-rozl": EndpointSpec( + columns={ + "period": "string", + "period_utc": "string", + "dtime": "string", + "dtime_utc": "string", + "cen_cost": "number", + "ckoeb_cost": "number", + "ceb_pp_cost": "number", + "ceb_sr_cost": "number", + "ceb_sr_afrrg_cost": "number", + "ceb_sr_afrrd_cost": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), + "csdac-pln": EndpointSpec( + columns={ + "period": "string", + "period_utc": "string", + "dtime": "string", + "dtime_utc": "string", + "csdac_pln": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), + "csire-kpi1": EndpointSpec( + columns={ + "row_no": "string", + "eic_code": "string", + "tax_id": "string", + "name": "string", + "date_1d": "string", + "completeness_1d": "number", + "date_from_5d": "string", + "date_to_5d": "string", + "completeness_5d": "number", + "date_from_1m": "string", + "date_to_1m": "string", + "completeness_1m": "number", + "date_from_6m": "string", + "date_to_6m": "string", + "completeness_6m": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "row_no", + ), + ), + "csire-kpi10": EndpointSpec( + columns={ + "row_no": "string", + "eic_code": "string", + "tax_id": "string", + "name": "string", + "date_from_1m": "string", + "date_to_1m": "string", + "timeliness_1m": "number", + "date_from_6m": "string", + "date_to_6m": "string", + "timeliness_6m": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "row_no", + ), + ), + "csire-kpi2": EndpointSpec( + columns={ + "row_no": "string", + "eic_code": "string", + "tax_id": "string", + "name": "string", + "date_from_1m": "string", + "date_to_1m": "string", + "completeness_1m": "number", + "date_from_6m": "string", + "date_to_6m": "string", + "completeness_6m": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "row_no", + ), + ), + "csire-kpi3": EndpointSpec( + columns={ + "row_no": "string", + "eic_code": "string", + "tax_id": "string", + "name": "string", + "date_from_1m": "string", + "date_to_1m": "string", + "completeness_1m": "number", + "date_from_6m": "string", + "date_to_6m": "string", + "completeness_6m": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "row_no", + ), + ), + "csire-kpi4": EndpointSpec( + columns={ + "row_no": "string", + "eic_code": "string", + "tax_id": "string", + "name": "string", + "date_1d": "string", + "completeness_1d": "number", + "date_from_5d": "string", + "date_to_5d": "string", + "completeness_5d": "number", + "date_from_1m": "string", + "date_to_1m": "string", + "completeness_1m": "number", + "date_from_6m": "string", + "date_to_6m": "string", + "completeness_6m": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "row_no", + ), + ), + "csire-kpi5": EndpointSpec( + columns={ + "row_no": "string", + "eic_code": "string", + "tax_id": "string", + "name": "string", + "date_1d": "string", + "timeliness_1d": "number", + "date_from_5d": "string", + "date_to_5d": "string", + "timeliness_5d": "number", + "date_from_1m": "string", + "date_to_1m": "string", + "timeliness_1m": "number", + "date_from_6m": "string", + "date_to_6m": "string", + "timeliness_6m": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "row_no", + ), + ), + "csire-kpi6": EndpointSpec( + columns={ + "row_no": "string", + "eic_code": "string", + "tax_id": "string", + "name": "string", + "date_from_1m": "string", + "date_to_1m": "string", + "timeliness_1m": "number", + "date_from_6m": "string", + "date_to_6m": "string", + "timeliness_6m": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "row_no", + ), + ), + "csire-kpi7": EndpointSpec( + columns={ + "row_no": "string", + "eic_code": "string", + "tax_id": "string", + "name": "string", + "date_from_1m": "string", + "date_to_1m": "string", + "timeliness_1m": "number", + "date_from_6m": "string", + "date_to_6m": "string", + "timeliness_6m": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "row_no", + ), + ), + "csire-kpi8": EndpointSpec( + columns={ + "row_no": "string", + "eic_code": "string", + "tax_id": "string", + "name": "string", + "date_1d": "string", + "timeliness_1d": "number", + "date_from_5d": "string", + "date_to_5d": "string", + "timeliness_5d": "number", + "date_from_1m": "string", + "date_to_1m": "string", + "timeliness_1m": "number", + "date_from_6m": "string", + "date_to_6m": "string", + "timeliness_6m": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "row_no", + ), + ), + "csire-kpi9": EndpointSpec( + columns={ + "row_no": "string", + "eic_code": "string", + "tax_id": "string", + "name": "string", + "date_1d": "string", + "correctness_1d": "number", + "date_from_5d": "string", + "date_to_5d": "string", + "correctness_5d": "number", + "date_from_1m": "string", + "date_to_1m": "string", + "correctness_1m": "number", + "date_from_6m": "string", + "date_to_6m": "string", + "correctness_6m": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "row_no", + ), + ), + "eb-rozl": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "period": "string", + "period_utc": "string", + "eb_d_pp": "number", + "eb_w_pp": "number", + "eb_afrrg": "number", + "eb_afrrd": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), + "en-rozl": EndpointSpec( + columns={ + "period": "string", + "period_utc": "string", + "dtime": "string", + "dtime_utc": "string", + "en_d": "number", + "en_w": "number", + "balance": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), + "energy-prices": EndpointSpec( + columns={ + "dtime_utc": "string", + "dtime": "string", + "period": "string", + "period_utc": "string", + "cor_cost": "number", + "ceb_pp_cost": "number", + "ceb_sr_cost": "number", + "cen_cost": "number", + "csdac_pln": "number", + "sk_cost": "number", + "balance": "number", + "balance_power": "number", + "sk_cost_power": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), + "gen-jw": EndpointSpec( + columns={ + "dtime_utc": "string", + "dtime": "string", + "resource_code": "string", + "power_plant": "string", + "operating_mode": "string", + "operating_mode_eng": "string", + "period": "string", + "period_utc": "string", + "value": "number", + "business_date": "string", + "publication_ts_utc": "string", + "publication_ts": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + "resource_code", + "operating_mode", + ), + ), + "his-bil-mocy": EndpointSpec( + columns={ + "peak": "string", + "peak_hour": "string", + "demand": "number", + "swm": "number", + "pos_jg": "number", + "pos_prb": "number", + "pos_sum": "number", + "rk_jg": "number", + "rk_prb": "number", + "rs_jg": "number", + "rs_prb": "number", + "rb_jg": "number", + "rb_prb": "number", + "ra_jg": "number", + "ra_prb": "number", + "we_jg": "number", + "we_prb": "number", + "ciep_jg": "number", + "ciep_prb": "number", + "inw_jg": "number", + "inw_prb": "number", + "pdysp_jg": "number", + "pdysp_prb": "number", + "sub": "number", + "sub_jg": "number", + "sub_prb": "number", + "rez": "number", + "rez_jg": "number", + "rez_jgw": "number", + "rez_jgw_wir": "number", + "rez_jgw_zim": "number", + "rez_jgm": "number", + "rez_jgz": "number", + "rez_jga": "number", + "rez_prb": "number", + "obc_sum": "number", + "obc_jg": "number", + "obc_prb": "number", + "ub_sum": "number", + "pdysp_sum": "number", + "business_date": "string", + "publication_ts_utc": "string", + "publication_ts": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "peak_hour", + "peak", + ), + ), + "his-gen-pal": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "period_utc": "string", + "period": "string", + "date": "string", + "alias_entsoe": "string", + "value": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + "business_date": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "dtime_utc", + "alias_entsoe", + ), + ), + "his-gen-pal-sire": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "period_utc": "string", + "period": "string", + "date": "string", + "alias_entsoe": "string", + "value": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + "business_date": "string", + "alias_sire": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "dtime_utc", + "alias_sire", + "alias_entsoe", + ), + ), + "his-wlk-cal": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "period": "string", + "period_utc": "string", + "demand": "number", + "swm_p": "number", + "swm_np": "number", + "jg": "number", + "jgo": "string", + "jgw1": "number", + "jgw2": "number", + "jgm1": "number", + "jgm2": "number", + "jgz1": "number", + "jgz2": "number", + "jgz3": "number", + "jga": "number", + "jnwrb": "number", + "jgm": "number", + "wi": "number", + "pv": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), + "it-oeb-rbb": EndpointSpec( + columns={ + "iteration_no": "integer", + "time_iteration_from_ts": "string", + "time_iteration_from_ts_utc": "string", + "time_iteration_to_ts": "string", + "time_iteration_to_ts_utc": "string", + "last_doc_dtime": "string", + "last_doc_dtime_utc": "string", + "time_period_from_dtime": "string", + "time_period_from_dtime_utc": "string", + "time_period_to_dtime": "string", + "time_period_to_dtime_utc": "string", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "iteration_no", + ), + ), + "it-oeb-rbn": EndpointSpec( + columns={ + "iteration_no": "integer", + "iteration_type": "string", + "time_iteration_from_ts": "string", + "time_iteration_from_ts_utc": "string", + "time_iteration_to_ts": "string", + "time_iteration_to_ts_utc": "string", + "last_doc_dtime": "string", + "last_doc_dtime_utc": "string", + "business_date": "string", + "publication_ts_utc": "string", + "publication_ts": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "iteration_no", + ), + ), + "it-omb-rbb": EndpointSpec( + columns={ + "iteration_no": "integer", + "time_iteration_from_ts": "string", + "time_iteration_from_ts_utc": "string", + "time_iteration_to_ts": "string", + "time_iteration_to_ts_utc": "string", + "last_doc_dtime": "string", + "last_doc_dtime_utc": "string", + "time_period_from_dtime": "string", + "time_period_from_dtime_utc": "string", + "time_period_to_dtime": "string", + "time_period_to_dtime_utc": "string", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "iteration_no", + ), + ), + "it-omb-rbn": EndpointSpec( + columns={ + "iteration_no": "integer", + "iteration_type": "string", + "time_iteration_from_ts": "string", + "time_iteration_from_ts_utc": "string", + "time_iteration_to_ts": "string", + "time_iteration_to_ts_utc": "string", + "last_doc_dtime": "string", + "last_doc_dtime_utc": "string", + "business_date": "string", + "publication_ts_utc": "string", + "publication_ts": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "iteration_no", + ), + ), + "it-opmb-rmb": EndpointSpec( + columns={ + "iteration_no": "integer", + "iteration_type": "string", + "time_iteration_from_ts": "string", + "time_iteration_from_ts_utc": "string", + "time_iteration_to_ts": "string", + "time_iteration_to_ts_utc": "string", + "last_doc_dtime": "string", + "last_doc_dtime_utc": "string", + "business_date": "string", + "publication_ts_utc": "string", + "publication_ts": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "iteration_no", + ), + ), + "it-ot-rbn": EndpointSpec( + columns={ + "iteration_no": "integer", + "iteration_type": "string", + "time_iteration_from_ts": "string", + "time_iteration_from_ts_utc": "string", + "time_iteration_to_ts": "string", + "time_iteration_to_ts_utc": "string", + "last_doc_dtime": "string", + "last_doc_dtime_utc": "string", + "business_date": "string", + "publication_ts_utc": "string", + "publication_ts": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "iteration_no", + ), + ), + "it-pp-rbb": EndpointSpec( + columns={ + "iteration_no": "integer", + "time_iteration_from_ts": "string", + "time_iteration_from_ts_utc": "string", + "time_iteration_to_ts": "string", + "time_iteration_to_ts_utc": "string", + "last_doc_dtime": "string", + "last_doc_dtime_utc": "string", + "time_period_from_dtime": "string", + "time_period_from_dtime_utc": "string", + "time_period_to_dtime": "string", + "time_period_to_dtime_utc": "string", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "iteration_no", + ), + ), + "it-pp-rbn": EndpointSpec( + columns={ + "iteration_no": "integer", + "iteration_type": "string", + "time_iteration_from_ts": "string", + "time_iteration_from_ts_utc": "string", + "time_iteration_to_ts": "string", + "time_iteration_to_ts_utc": "string", + "last_doc_dtime": "string", + "last_doc_dtime_utc": "string", + "business_date": "string", + "publication_ts_utc": "string", + "publication_ts": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "iteration_no", + ), + ), + "it-use-rbb": EndpointSpec( + columns={ + "iteration_no": "integer", + "time_iteration_from_ts": "string", + "time_iteration_from_ts_utc": "string", + "time_iteration_to_ts": "string", + "time_iteration_to_ts_utc": "string", + "last_doc_dtime": "string", + "last_doc_dtime_utc": "string", + "time_period_from_dtime": "string", + "time_period_from_dtime_utc": "string", + "time_period_to_dtime": "string", + "time_period_to_dtime_utc": "string", + "volume": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "iteration_no", + ), + ), + "it-use-rbn": EndpointSpec( + columns={ + "iteration_no": "integer", + "iteration_type": "string", + "time_iteration_from_ts": "string", + "time_iteration_from_ts_utc": "string", + "time_iteration_to_ts": "string", + "time_iteration_to_ts_utc": "string", + "last_doc_dtime": "string", + "last_doc_dtime_utc": "string", + "business_date": "string", + "publication_ts_utc": "string", + "publication_ts": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "iteration_no", + ), + ), + "kmb-kro-rozl": EndpointSpec( + columns={ + "period": "string", + "period_utc": "string", + "dtime": "string", + "dtime_utc": "string", + "kmb": "number", + "kro": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), + "krb-rozl": EndpointSpec( + columns={ + "period": "string", + "period_utc": "string", + "dtime": "string", + "dtime_utc": "string", + "kcz": "number", + "kb": "number", + "ko": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), + "kse-load": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "period": "string", + "period_utc": "string", + "load_actual": "number", + "load_fcst": "number", + "publication_ts": "string", + "publication_ts_utc": "string", + "business_date": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), + "lolp": EndpointSpec( + columns={ + "period": "string", + "period_utc": "string", + "dtime": "string", + "dtime_utc": "string", + "ojnz_id": "string", + "b0": "number", + "b1": "number", + "b2": "number", + "b3": "number", + "b4": "number", + "b5": "number", + "b6": "number", + "b7": "number", + "b8": "number", + "b9": "number", + "p0": "number", + "p1": "number", + "p2": "number", + "p3": "number", + "p4": "number", + "p5": "number", + "p6": "number", + "p7": "number", + "p8": "number", + "p9": "number", + "business_date": "string", + "publication_ts_utc": "string", + "publication_ts": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "dtime_utc", + ), + ), + "mbp-tp": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "onmbp": "string", + "fcr_g": "number", + "fcr_d": "number", + "afrr_g": "number", + "afrr_d": "number", + "mfrrd_g": "number", + "mfrrd_d": "number", + "rr_g": "number", + "rr_d": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), + "mbu-tu": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "period": "string", + "period_utc": "string", + "fcr_g": "number", + "fcr_d": "number", + "afrr_g": "number", + "afrr_d": "number", + "mfrrd_g": "number", + "mfrrd_d": "number", + "publication_ts": "string", + "publication_ts_utc": "string", + "business_date": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), + "oeb-bpkdbo": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "period": "string", + "period_utc": "string", + "ofp": "number", + "ofc": "number", + "cdo": "number", + "activ_direction": "string", + "sort": "integer", + "business_date": "string", + "publication_ts_utc": "string", + "publication_ts": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + "activ_direction", + "ofp", + "ofc", + "cdo", + ), + ), + "ogr-b": EndpointSpec( + columns={ + "node": "string", + "resource_name": "string", + "resource_code": "string", + "direction": "string", + "pol_min_power_of_unit": "number", + "pol_min_power_of_unit_plant": "integer", + "pol_max_power_of_unit_plant": "number", + "pol_max_power_of_unit": "integer", + "from_dtime": "string", + "from_dtime_utc": "string", + "to_dtime": "string", + "to_dtime_utc": "string", + "time_conditions": "string", + "limiting_element": "string", + "add_cond": "string", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + "year": "number", + "month": "number", + "period": "string", + "is_active": "boolean", + "valid_from_ts_utc": "string", + "valid_to_ts_utc": "string", + "version": "string", + }, + date_column="business_date", + meta_columns=( + "period", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "node", + "business_date", + "to_dtime_utc", + "from_dtime_utc", + "period", + ), + ), + "ogr-b-head": EndpointSpec( + columns={ + "title": "string", + "business_date": "string", + "remarks": "string", + "version": "string", + "is_active": "boolean", + }, + date_column="business_date", + meta_columns=( + "business_date", + ), + keys=( + "business_date", + ), + ), + "ogr-d1": EndpointSpec( + columns={ + "node": "string", + "resource_name": "string", + "resource_code": "string", + "direction": "string", + "pol_min_power_of_unit": "number", + "pol_min_power_of_unit_plant": "integer", + "pol_max_power_of_unit_plant": "number", + "pol_max_power_of_unit": "integer", + "from_dtime": "string", + "from_dtime_utc": "string", + "to_dtime": "string", + "to_dtime_utc": "string", + "time_conditions": "string", + "limiting_element": "string", + "add_cond": "string", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "node", + "business_date", + "to_dtime", + "from_dtime", + ), + ), + "ogr-d1-head": EndpointSpec( + columns={ + "title": "string", + "period": "string", + "remarks": "string", + }, + date_column=None, + meta_columns=( + "period", + ), + keys=( + "period", + ), + ), + "ogr-d2": EndpointSpec( + columns={ + "node": "string", + "resource_name": "string", + "resource_code": "string", + "direction": "string", + "pol_min_power_of_unit": "number", + "pol_min_power_of_unit_plant": "integer", + "pol_max_power_of_unit_plant": "number", + "pol_max_power_of_unit": "integer", + "from_dtime": "string", + "from_dtime_utc": "string", + "to_dtime": "string", + "to_dtime_utc": "string", + "time_conditions": "string", + "limiting_element": "string", + "add_cond": "string", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "node", + "business_date", + "to_dtime", + "from_dtime", + ), + ), + "ogr-d2-head": EndpointSpec( + columns={ + "title": "string", + "period": "string", + "remarks": "string", + }, + date_column=None, + meta_columns=( + "period", + ), + keys=( + "period", + ), + ), + "ogr-d39": EndpointSpec( + columns={ + "node": "string", + "resource_name": "string", + "resource_code": "string", + "direction": "string", + "pol_min_power_of_unit": "number", + "pol_min_power_of_unit_plant": "integer", + "pol_max_power_of_unit_plant": "number", + "pol_max_power_of_unit": "integer", + "from_dtime": "string", + "from_dtime_utc": "string", + "to_dtime": "string", + "to_dtime_utc": "string", + "time_conditions": "string", + "limiting_element": "string", + "add_cond": "string", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "node", + "business_date", + "to_dtime", + "from_dtime", + ), + ), + "ogr-d39-head": EndpointSpec( + columns={ + "title": "string", + "period": "string", + "from_period": "string", + "to_period": "string", + "remarks": "string", + }, + date_column=None, + meta_columns=( + "period", + ), + keys=( + "period", + ), + ), + "ogr-m": EndpointSpec( + columns={ + "node": "string", + "resource_name": "string", + "resource_code": "string", + "direction": "string", + "pol_min_power_of_unit": "number", + "pol_min_power_of_unit_plant": "integer", + "pol_max_power_of_unit_plant": "number", + "pol_max_power_of_unit": "integer", + "from_dtime": "string", + "from_dtime_utc": "string", + "to_dtime": "string", + "to_dtime_utc": "string", + "time_conditions": "string", + "limiting_element": "string", + "add_cond": "string", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "node", + "business_date", + "to_dtime", + "from_dtime", + ), + ), + "ogr-m-head": EndpointSpec( + columns={ + "title": "string", + "period": "string", + "from_period": "string", + "to_period": "string", + "remarks": "string", + }, + date_column=None, + meta_columns=( + "period", + ), + keys=( + "period", + ), + ), + "ogr-oper": EndpointSpec( + columns={ + "node": "string", + "resource_name": "string", + "resource_code": "string", + "direction": "string", + "pol_min_power_of_unit": "number", + "pol_min_power_of_unit_plant": "integer", + "pol_max_power_of_unit_plant": "number", + "pol_max_power_of_unit": "integer", + "from_dtime": "string", + "from_dtime_utc": "string", + "to_dtime": "string", + "to_dtime_utc": "string", + "time_conditions": "string", + "limiting_element": "string", + "add_cond": "string", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "node", + "business_date", + "to_dtime", + "from_dtime", + ), + ), + "ogr-oper-head": EndpointSpec( + columns={ + "title": "string", + "period": "string", + "remarks": "string", + }, + date_column=None, + meta_columns=( + "period", + ), + keys=( + "period", + ), + ), + "ogr-r": EndpointSpec( + columns={ + "node": "string", + "resource_name": "string", + "resource_code": "string", + "direction": "string", + "pol_min_power_of_unit": "number", + "pol_min_power_of_unit_plant": "integer", + "pol_max_power_of_unit_plant": "number", + "pol_max_power_of_unit": "integer", + "from_dtime": "string", + "from_dtime_utc": "string", + "to_dtime": "string", + "to_dtime_utc": "string", + "time_conditions": "string", + "limiting_element": "string", + "add_cond": "string", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + "year": "number", + "month": "number", + "period": "string", + }, + date_column="business_date", + meta_columns=( + "period", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "node", + "business_date", + "to_dtime", + "from_dtime", + "period", + ), + ), + "ogr-r-head": EndpointSpec( + columns={ + "title": "string", + "period": "string", + "from_period": "string", + "to_period": "string", + "remarks": "string", + }, + date_column=None, + meta_columns=( + "period", + ), + keys=( + "period", + ), + ), + "ogr-rmb": EndpointSpec( + columns={ + "id": "integer", + "node": "string", + "from_dtime": "string", + "from_dtime_utc": "string", + "to_dtime": "string", + "to_dtime_utc": "string", + "unit_name": "string", + "direction": "string", + "sminp": "integer", + "smaxp": "integer", + "smaxp_sminp": "integer", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "from_dtime", + "node", + ), + ), + "pdgobpkd": EndpointSpec( + columns={ + "dtime_utc": "string", + "dtime": "string", + "period": "string", + "period_utc": "string", + "kse_pow_dem": "number", + "capacity_jgw_jgm_jgz_jga": "number", + "capacity_jgw_zak_1": "number", + "capacity_jgw_zak_2": "number", + "capacity_jgm_zak_1": "number", + "capacity_jgm_zak_2": "number", + "capacity_jgz_zak_1": "number", + "capacity_jgz_zak_2": "number", + "capacity_jgz_zak_3": "number", + "capacity_jga": "number", + "capacity_jgo": "number", + "gen_jgw_jgm_jgz_jga": "number", + "gen_jgw_zak_1": "number", + "gen_jgw_zak_2": "number", + "gen_jgm_zak_1": "number", + "gen_jgm_zak_2": "number", + "gen_jgz_zak_1": "number", + "gen_jgz_zak_2": "number", + "gen_jgz_zak_3": "number", + "gen_jga": "number", + "gen_jgo": "number", + "gen_not_activ_part": "number", + "ogr_mwe": "number", + "tot_jgm_char_pow": "number", + "gen_wi": "number", + "gen_fv": "number", + "dom_balance_exchange_non_par": "number", + "dom_balance_exchange_par": "number", + "rez_over_demand": "number", + "rez_under": "number", + "business_date": "string", + "publication_ts_utc": "string", + "publication_ts": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + "business_date", + ), + ), + "pdgopkd": EndpointSpec( + columns={ + "dtime_utc": "string", + "dtime": "string", + "period": "string", + "period_utc": "string", + "kse_pow_dem": "number", + "capacity_jgw_jgm_jgz_jga": "number", + "capacity_jgw_zak_1": "number", + "capacity_jgw_zak_2": "number", + "capacity_jgm_zak_1": "number", + "capacity_jgm_zak_2": "number", + "capacity_jgz_zak_1": "number", + "capacity_jgz_zak_2": "number", + "capacity_jgz_zak_3": "number", + "capacity_jga": "number", + "capacity_jgo": "number", + "gen_jgw_jgm_jgz_jga": "number", + "gen_jgw_zak_1": "number", + "gen_jgw_zak_2": "number", + "gen_jgm_zak_1": "number", + "gen_jgm_zak_2": "number", + "gen_jgz_zak_1": "number", + "gen_jgz_zak_2": "number", + "gen_jgz_zak_3": "number", + "gen_jga": "number", + "gen_jgo": "number", + "gen_not_activ_part": "number", + "ogr_mwe": "number", + "tot_jgm_char_pow": "number", + "gen_wi": "number", + "gen_fv": "number", + "dom_balance_exchange_non_par": "number", + "dom_balance_exchange_par": "number", + "rez_over_demand": "number", + "rez_under": "number", + "business_date": "string", + "publication_ts_utc": "string", + "publication_ts": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + "business_date", + ), + ), + "pdgsz": EndpointSpec( + columns={ + "dtime": "string", + "usage_fcst": "integer", + "total_power_demand": "number", + "dtime_utc": "string", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + "is_active": "boolean", + "valid_from_ts": "string", + "valid_to_ts": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + "valid_from_ts", + "valid_to_ts", + ), + ), + "pdwkseub": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "period": "string", + "period_utc": "string", + "power_plant": "string", + "resource_code": "string", + "non_us_cap": "number", + "grid_lim": "number", + "available_capacity": "number", + "publication_ts": "string", + "publication_ts_utc": "string", + "business_date": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + "resource_code", + ), + ), + "pk5l-wp": EndpointSpec( + columns={ + "period": "string", + "plan_dtime": "string", + "plan_dtime_utc": "string", + "grid_demand_fcst": "number", + "req_pow_res": "number", + "surplus_cap_avail_tso": "number", + "gen_surplus_avail_tso_above": "number", + "avail_cap_gen_units_stor_prov": "number", + "avail_cap_gen_units_stor_prov_tso": "number", + "fcst_gen_unit_stor_prov": "number", + "fcst_gen_unit_stor_non_prov": "number", + "fcst_wi_tot_gen": "number", + "fcst_pv_tot_gen": "number", + "planned_exchange": "number", + "fcst_unav_energy": "number", + "planned_restr_mwe_avail_shutdown": "number", + "sum_unav_oper_cond": "number", + "pred_gen_res_not_cov": "number", + "cap_market_obligation": "number", + "business_date": "string", + "publication_ts_utc": "string", + "publication_ts": "string", + }, + date_column="business_date", + meta_columns=( + "period", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "plan_dtime_utc", + ), + ), + "poeb-rbb": EndpointSpec( + columns={ + "dtime_utc": "string", + "dtime": "string", + "period": "string", + "period_utc": "string", + "ofcg": "number", + "ofcd": "number", + "ofp": "number", + "business_date": "string", + "publication_ts_utc": "string", + "publication_ts": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "dtime_utc", + "ofcg", + "ofcd", + "ofp", + ), + ), + "poeb-rbn": EndpointSpec( + columns={ + "dtime_utc": "string", + "dtime": "string", + "period": "string", + "period_utc": "string", + "ofcg": "number", + "ofcd": "number", + "ofp": "number", + "business_date": "string", + "publication_ts_utc": "string", + "publication_ts": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "dtime_utc", + "ofcg", + "ofcd", + "ofp", + ), + ), + "pomb-rbn": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "period": "string", + "period_utc": "string", + "reserve_type": "string", + "pofmin": "number", + "pofmax": "number", + "plofmin": "number", + "plofmax": "number", + "ofc": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + "pofmin", + "pofmax", + "plofmin", + "plofmax", + "ofc", + "reserve_type", + ), + ), + "popmb-rmb": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "onmbp": "string", + "reserve_type": "string", + "pom": "number", + "com": "number", + "comrr": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + "pom", + "com", + "comrr", + "reserve_type", + ), + ), + "poze-redoze": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "period": "string", + "period_utc": "string", + "pv_red_balance": "number", + "wi_red_balance": "number", + "pv_red_network": "number", + "wi_red_network": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "period", + ), + ), + "price-cost": EndpointSpec( + columns={ + "cen_cost": "number", + "ckoeb_cost": "number", + "ceb_sr_cost": "number", + "cor_pp_cost": "number", + "imb_energy": "number", + "contracting": "string", + "dtime": "string", + "dtime_utc": "string", + "period": "string", + "period_utc": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + "business_date": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), + "price-fcst": EndpointSpec( + columns={ + "cen_fcst": "number", + "ckoeb_fcst": "number", + "ceb_sr_fcst": "number", + "cor_fcst": "number", + "imb_energy": "number", + "contracting": "string", + "dtime": "string", + "dtime_utc": "string", + "period": "string", + "period_utc": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + "business_date": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), + "przeplywy-mocy": EndpointSpec( + columns={ + "section_code": "string", + "dtime": "string", + "dtime_utc": "string", + "period": "string", + "period_utc": "string", + "value": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + "section_code", + ), + ), + "pwm-dost-op": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "period": "string", + "period_utc": "string", + "pl_cz": "number", + "cz_pl": "number", + "pl_sk": "number", + "sk_pl": "number", + "pl_de": "number", + "de_pl": "number", + "pl_se": "number", + "se_pl": "number", + "pl_ua": "number", + "ua_pl": "number", + "pl_lt": "number", + "lt_pl": "number", + "saldo": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "dtime_utc", + ), + ), + "pwm-igcc-in": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "period": "string", + "period_utc": "string", + "energy_afrr_exp": "number", + "energy_afrr_imp": "number", + "afrr_exp_pln": "number", + "afrr_imp_pln": "number", + "afrr_exp_eur": "number", + "afrr_imp_eur": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "dtime_utc", + ), + ), + "pwm-rdb": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "period": "string", + "period_utc": "string", + "pl_cz": "number", + "cz_pl": "number", + "pl_sk": "number", + "sk_pl": "number", + "pl_de": "number", + "de_pl": "number", + "pl_se": "number", + "se_pl": "number", + "pl_ua": "number", + "ua_pl": "number", + "pl_lt": "number", + "lt_pl": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "dtime_utc", + ), + ), + "pwm-rdn": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "period": "string", + "period_utc": "string", + "pl_cz": "number", + "cz_pl": "number", + "pl_sk": "number", + "sk_pl": "number", + "pl_de": "number", + "de_pl": "number", + "pl_se": "number", + "se_pl": "number", + "pl_ua_dob": "number", + "ua_dob_pl": "number", + "pl_ua": "number", + "ua_pl": "number", + "pl_lt": "number", + "lt_pl": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "dtime_utc", + ), + ), + "pwm-rdn-rdb": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "period": "string", + "period_utc": "string", + "pl_cz": "number", + "cz_pl": "number", + "pl_sk": "number", + "sk_pl": "number", + "pl_de": "number", + "de_pl": "number", + "pl_se": "number", + "se_pl": "number", + "pl_ua_dob": "number", + "ua_dob_pl": "number", + "pl_ua": "number", + "ua_pl": "number", + "pl_lt": "number", + "lt_pl": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "dtime_utc", + ), + ), + "rcco2": EndpointSpec( + columns={ + "rcco2_pln": "number", + "rcco2_eur": "number", + "publication_ts": "string", + "publication_ts_utc": "string", + "business_date": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "rcco2_pln", + ), + ), + "rce-pln": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "period": "string", + "period_utc": "string", + "rce_pln": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), + "ro-prog": EndpointSpec( + columns={ + "period": "string", + "period_utc": "string", + "dtime": "string", + "dtime_utc": "string", + "ro_fcst": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), + "ro-rozl": EndpointSpec( + columns={ + "period": "string", + "period_utc": "string", + "dtime": "string", + "dtime_utc": "string", + "ro_cost": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), + "sk": EndpointSpec( + columns={ + "dtime_utc": "string", + "dtime": "string", + "period": "string", + "period_utc": "string", + "sk_d1_fcst": "number", + "sk_d_fcst": "number", + "sk_cost": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), + "specwezlow": EndpointSpec( + columns={ + "regional_dispatch_center": "string", + "dso_branch": "string", + "station_name": "string", + "switching_station_code": "string", + "switching_station_voltage": "string", + "busbar_code": "string", + "busbar_name": "string", + "business_date": "string", + "publication_ts_utc": "string", + "publication_ts": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "business_date", + "regional_dispatch_center", + ), + ), + "unav-pk5l": EndpointSpec( + columns={ + "power_plant": "string", + "type": "string", + "state": "string", + "unit_code": "string", + "reason": "string", + "start_dtime": "string", + "start_dtime_utc": "string", + "end_dtime": "string", + "end_dtime_utc": "string", + "direction": "string", + "mrid_zas": "string", + "snd_dtime": "string", + "snd_dtime_utc": "string", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "power_plant", + "unit_code", + "start_dtime_utc", + "end_dtime_utc", + ), + ), + "use-sprz-rbb": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "sprz_volume": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), + "use-sprz-rbn": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "day_ahead_tr_vol": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), + "wmb": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "period": "string", + "period_utc": "string", + "fcrg": "integer", + "fcrd": "integer", + "frrg": "integer", + "frrd": "integer", + "afrrg": "integer", + "afrrd": "integer", + "rrg": "integer", + "rrd": "integer", + "publication_ts": "string", + "publication_ts_utc": "string", + "business_date": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), + "wsp-wrm": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "period": "string", + "period_utc": "string", + "az": "number", + "afw": "number", + "apv": "number", + "ws": "number", + "wmb_max": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + "ojnz_id": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), + "zeb-rozl": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "period": "string", + "period_utc": "string", + "zebpp": "number", + "pzeb_afrrg_cost": "number", + "pzeb_afrrd_cost": "number", + "business_date": "string", + "publication_ts": "string", + "publication_ts_utc": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime", + ), + ), + "zmb": EndpointSpec( + columns={ + "dtime": "string", + "dtime_utc": "string", + "period": "string", + "period_utc": "string", + "zmb_fcrg": "number", + "zmb_fcrd": "number", + "zmb_frrg": "number", + "zmb_frrd": "number", + "zmb_afrrg": "number", + "zmb_afrrd": "number", + "zmb_rrg": "number", + "zmb_rrd": "number", + "publication_ts": "string", + "publication_ts_utc": "string", + "business_date": "string", + }, + date_column="business_date", + meta_columns=( + "dtime", + "dtime_utc", + "period", + "period_utc", + "business_date", + "publication_ts", + "publication_ts_utc", + ), + keys=( + "dtime_utc", + ), + ), +} From 2cc85483153040bbbf0529113d6d30fba1c3bce9 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 14:00:20 +0200 Subject: [PATCH 03/51] test: add endpoint registry tests --- tests/test_registry.py | 185 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 tests/test_registry.py diff --git a/tests/test_registry.py b/tests/test_registry.py new file mode 100644 index 0000000..5303317 --- /dev/null +++ b/tests/test_registry.py @@ -0,0 +1,185 @@ +import typing + +import pytest + +from gen_registry import build_registry, parse_keys, render +from pse_sdk._registry import REGISTRY, Endpoint + + +def _props(**types): + return {col: {"type": type_, "format": ""} for col, type_ in types.items()} + + +SPEC = { + "info": {"version": "1.0"}, + "paths": { + "/price-cost": {}, + "/price-cost/dtime_utc/{dtime_utc}": {}, + "/unav-pk5l": {}, + "/unav-pk5l/unit_code/{unit_code}/start_dtime_utc/{start_dtime_utc}": {}, + "/ogr-d1-head": {}, + "/ogr-d1-head/period/{period}": {}, + }, + "components": { + "schemas": { + "price-cost": { + "type": "object", + "properties": _props( + cen_cost="number", + contracting="string", + publication_ts="string", + business_date="string", + dtime_utc="string", + dtime="string", + ), + }, + "unav-pk5l": { + "type": "object", + "properties": _props( + unit_code="string", + start_dtime_utc="string", + business_date="string", + ), + }, + "ogr-d1-head": { + "type": "object", + "properties": _props(title="string", period="string"), + }, + } + }, +} + + +class TestParseKeys: + def test_single_key(self): + assert parse_keys(SPEC["paths"])["price-cost"] == ("dtime_utc",) + + def test_composite_key(self): + keys = parse_keys(SPEC["paths"]) + assert keys["unav-pk5l"] == ("unit_code", "start_dtime_utc") + + def test_collection_paths_ignored(self): + assert set(parse_keys(SPEC["paths"])) == { + "price-cost", + "unav-pk5l", + "ogr-d1-head", + } + + +class TestBuildRegistry: + def test_endpoints_sorted(self): + assert list(build_registry(SPEC)) == ["ogr-d1-head", "price-cost", "unav-pk5l"] + + def test_columns_keep_spec_order_and_types(self): + columns = build_registry(SPEC)["price-cost"]["columns"] + assert list(columns) == [ + "cen_cost", + "contracting", + "publication_ts", + "business_date", + "dtime_utc", + "dtime", + ] + assert columns["cen_cost"] == "number" + assert columns["contracting"] == "string" + + def test_date_column_business_date(self): + assert build_registry(SPEC)["price-cost"]["date_column"] == "business_date" + + def test_date_column_none_without_business_date(self): + assert build_registry(SPEC)["ogr-d1-head"]["date_column"] is None + + def test_meta_columns_fixed_order(self): + assert build_registry(SPEC)["price-cost"]["meta_columns"] == ( + "dtime", + "dtime_utc", + "business_date", + "publication_ts", + ) + + def test_utc_data_columns_not_meta(self): + meta = build_registry(SPEC)["unav-pk5l"]["meta_columns"] + assert meta == ("business_date",) + + def test_keys(self): + assert build_registry(SPEC)["unav-pk5l"]["keys"] == ( + "unit_code", + "start_dtime_utc", + ) + + +class TestRender: + def test_round_trip(self): + registry = build_registry(SPEC) + namespace: dict = {} + exec(render(registry, "1.0"), namespace) + spec_cls = namespace["EndpointSpec"] + assert namespace["REGISTRY"] == { + name: spec_cls(**entry) for name, entry in registry.items() + } + + def test_endpoint_literal(self): + namespace: dict = {} + exec(render(build_registry(SPEC), "1.0"), namespace) + assert typing.get_args(namespace["Endpoint"]) == ( + "ogr-d1-head", + "price-cost", + "unav-pk5l", + ) + + def test_header_has_spec_version(self): + assert "(spec version 1.0)" in render(build_registry(SPEC), "1.0") + + +class TestCommittedRegistry: + def test_endpoint_literal_matches_registry(self): + assert typing.get_args(Endpoint) == tuple(REGISTRY) + + def test_contains_client_endpoints(self): + used = { + "price-cost", + "csdac-pln", + "krb-rozl", + "kmb-kro-rozl", + "sk", + "his-wlk-cal", + } + assert used <= set(REGISTRY) + + def test_price_cost(self): + spec = REGISTRY["price-cost"] + assert spec.columns["cen_cost"] == "number" + assert spec.date_column == "business_date" + assert spec.keys == ("dtime_utc",) + + def test_rcco2_has_no_dtime(self): + assert "dtime" not in REGISTRY["rcco2"].meta_columns + + @pytest.mark.parametrize( + "name", + [ + "ogr-oper-head", + "ogr-d1-head", + "ogr-d2-head", + "ogr-d39-head", + "ogr-m-head", + "ogr-r-head", + ], + ) + def test_head_endpoints_have_no_date_column(self, name): + assert REGISTRY[name].date_column is None + + +@pytest.mark.parametrize("name", REGISTRY) +class TestRegistryInvariants: + def test_meta_columns_are_columns(self, name): + spec = REGISTRY[name] + assert set(spec.meta_columns) <= set(spec.columns) + + def test_keys_are_columns(self, name): + spec = REGISTRY[name] + assert set(spec.keys) <= set(spec.columns) + + def test_date_column_is_column(self, name): + spec = REGISTRY[name] + assert spec.date_column is None or spec.date_column in spec.columns From d5ac8914e7a30205133c65e0b43222c4e500d5e0 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 14:00:20 +0200 Subject: [PATCH 04/51] docs: document endpoint registry regeneration --- CONTRIBUTING.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 88d2dca..9fbbac9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,14 +50,26 @@ Run these from the project root with your venv activated: pytest # Lint -ruff check src/ +ruff check src/ scripts/ # Format -ruff format src/ +ruff format src/ scripts/ ``` Fix any lint/test failures before opening a PR. +## Endpoint Registry + +`src/pse_sdk/_registry.py` is generated from the PSE OpenAPI spec (`https://api.raporty.pse.pl/api/openapi`) — don't edit it by hand. + +```bash +# Regenerate from the live spec +python scripts/gen_registry.py + +# Check that the committed registry is up to date +python scripts/gen_registry.py --check +``` + ## Commit Convention Use [Conventional Commits](https://www.conventionalcommits.org/): From b124c5b24d6f9a9a0135abc3ef6feff0b732c503 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 14:50:10 +0200 Subject: [PATCH 05/51] chore: move dev deps to uv dependency group, add uv.lock and .python-version --- .python-version | 1 + pyproject.toml | 2 + uv.lock | 932 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 935 insertions(+) create mode 100644 .python-version create mode 100644 uv.lock diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/pyproject.toml b/pyproject.toml index fb01526..29e05a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,8 @@ dependencies = [ [project.optional-dependencies] pandas = ["pandas>=1.5"] + +[dependency-groups] dev = [ "pytest", "responses", diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..1593e42 --- /dev/null +++ b/uv.lock @@ -0,0 +1,932 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" +resolution-markers = [ + "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'", +] + +[[package]] +name = "certifi" +version = "2026.10.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/1436c50cc46699de0230fbaacc1d1de7bfec413176099949e9d5112e88f2/certifi-2026.10.19.tar.gz", hash = "sha256:035c1e85572b141bf1c3a3eff0e33a8eb74917522f2559075ff8d2fea780a1ae", size = 137598, upload-time = "2026-10-20T02:24:16.604Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/51/29046e48f693c54e5b7412216d447a1d8362c9653148d98c02cdf65fa733/certifi-2026.10.19-py3-none-any.whl", hash = "sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b", size = 136464, upload-time = "2026-10-20T02:24:15.072Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/1c/f41d4e74c28ab327ff3acd36053f7ea506c55872d7a90b0fa71aa3ab0c89/charset_normalizer-3.5.2.tar.gz", hash = "sha256:39de2a259fc954455c57274dc94c79d5842774e1247a016aff30bc0efed0f4ef", size = 172659, upload-time = "2026-09-30T04:39:23.398Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/77/46e87bcfc45d25ab4db7cfc9bb544bfa3ffd302289ed31ae93f5433eb899/charset_normalizer-3.5.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:195c26fb65950f8fce54e26349852b7bdd7c5f120aeefbcc440b8a20faaed4a3", size = 373348, upload-time = "2026-09-30T04:34:42.843Z" }, + { url = "https://files.pythonhosted.org/packages/52/fc/e518013affcc43c9f919c3ba41bffe9b4ee4aceb0a6462a6243efcca5f2d/charset_normalizer-3.5.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9373ad13ef0d2c0fb761e04e55bfdee5a08b52cef2c882c8fbe9935b1517152e", size = 260860, upload-time = "2026-09-30T04:34:44.817Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ad/2a895c945ee61988dfd9ccee64f0b78dc09f29c9b34d1dd545246d78e0ad/charset_normalizer-3.5.2-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ddf19c062bea7a0cc80f519243d2c01dd091be0cf952a0750d4ad576709559f5", size = 246491, upload-time = "2026-09-30T04:34:46.227Z" }, + { url = "https://files.pythonhosted.org/packages/c9/05/5d958bc8ea503e26be25ada5430fd409cfb45dc22ff33f9f96e649613c99/charset_normalizer-3.5.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3d14b50de6bf4d0edf857a9386836846f982b8f524e188e2e68b96d702bcf4aa", size = 290875, upload-time = "2026-09-30T04:34:47.691Z" }, + { url = "https://files.pythonhosted.org/packages/55/e2/06bad57dfdb49cad92c0ba85b6b4fa5827a67df37897287cfef0553843c2/charset_normalizer-3.5.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:28a15fdad492a99b6eccfaaed66ef3f74050680545ea61ec8b2f4c538f1f1320", size = 286987, upload-time = "2026-09-30T04:34:49.356Z" }, + { url = "https://files.pythonhosted.org/packages/c4/9f/9f52d2886d52645987d603425482c63c5045a3005db1354a7097e5ed1ae9/charset_normalizer-3.5.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a893cc101149f80a653f82062ebc95b34525a2614382e1da5458fe7c6997249", size = 271105, upload-time = "2026-09-30T04:34:50.957Z" }, + { url = "https://files.pythonhosted.org/packages/4a/41/e05e19578b7b87e7db2c3ecb884bd09d065274ab41e1f535542e8bae9b06/charset_normalizer-3.5.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:619799369eeef6366ed3e8755a5670f4f2f0fb6b30a0fd7264dc0fdc2357058e", size = 269833, upload-time = "2026-09-30T04:34:52.623Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cd/fc7152414561ff78f9c61a4a627025f65fd244abaefc99ac4e75c6169b33/charset_normalizer-3.5.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:447441e76ec720b15e64418d32e092297340387053047c7c694f579efb0ee1d9", size = 263048, upload-time = "2026-09-30T04:34:54.013Z" }, + { url = "https://files.pythonhosted.org/packages/71/71/fb379e399b1013962716a059d551e03bd80b3e02f05d81246886af6c0958/charset_normalizer-3.5.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:62588a277bfb59def052abd940703fa35107152bf479781a878617d60faf8fb5", size = 249237, upload-time = "2026-09-30T04:34:55.373Z" }, + { url = "https://files.pythonhosted.org/packages/33/4f/aeadccd6d20882909eb2597ec40eccc05d505e2262f14dc76d7620700657/charset_normalizer-3.5.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:44bd4fbb29dfbeba60e7d2bd000c59e4b21ddb3cc53912b14048d37092706d7c", size = 290756, upload-time = "2026-09-30T04:34:56.867Z" }, + { url = "https://files.pythonhosted.org/packages/b9/7b/e8a92613236b257d3b75d532496eb21f68ad8231d7d56df94497e460112e/charset_normalizer-3.5.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:30fcd120b732aa79317f08dee04d7de0847822e4cf7ee0e9f445bb958832252c", size = 269026, upload-time = "2026-09-30T04:34:58.278Z" }, + { url = "https://files.pythonhosted.org/packages/ff/ac/21d5c6b972285c5f095ff78afdc99f3539e3985dfcf7ff1cfbe9e772f529/charset_normalizer-3.5.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:50e3adfb96fc189eb27b1cf62d3b598b89b4bb0420d93a3d3e42e137409011be", size = 287601, upload-time = "2026-09-30T04:34:59.703Z" }, + { url = "https://files.pythonhosted.org/packages/59/72/263491ec1494a194b16fcbff88a0220f2af633738c79e92b4d7189226322/charset_normalizer-3.5.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b736353c0a625bbd5fcec108576e2385db3496f4f771f785ff32e108d3c3bc45", size = 272660, upload-time = "2026-09-30T04:35:01.26Z" }, + { url = "https://files.pythonhosted.org/packages/38/7a/81f1186c57e406233969ae96b5fd0c907254ab8b6e47da8ae24cd20fb9d7/charset_normalizer-3.5.2-cp310-cp310-win32.whl", hash = "sha256:f5833ad231be5eb6553de524a70f48d71b2c8563101750531e0b80184e175cd4", size = 187301, upload-time = "2026-09-30T04:35:02.659Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ab/fb8b5178fdf7bf68efa61cbe892286bc9f73846f26147d4725fb89388475/charset_normalizer-3.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:1461ac396c4fdb983a675f20aa555624f0ee18ac83d832b9244ffff3d8055275", size = 215446, upload-time = "2026-09-30T04:35:04.011Z" }, + { url = "https://files.pythonhosted.org/packages/36/40/e270b74e0d3f583d78d902960845a149e80eb7c737ee5613ccc6efc7264b/charset_normalizer-3.5.2-cp310-cp310-win_arm64.whl", hash = "sha256:c6708715abcf3c73b99508253e961a9967f02fe536532834149574eda6de0d1c", size = 203389, upload-time = "2026-09-30T04:35:05.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/67/6a0b94a7960d5e1b5eacd2fb529f3fccc47db4644f7f0a7cfdcfc3be578a/charset_normalizer-3.5.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d21b8b13c7592db2ac5e544a6d83187b995257472b0c9e8351b6d507ae37ed6", size = 370642, upload-time = "2026-09-30T04:35:06.91Z" }, + { url = "https://files.pythonhosted.org/packages/fb/94/01009e13b94041599004edf32e56e382c24e570f60f79bab8efe45cfe1eb/charset_normalizer-3.5.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d760fe2a4d7c3b226cb9026d6a842868d52a7901bd98420e1baf14e80da85cf5", size = 259280, upload-time = "2026-09-30T04:35:08.448Z" }, + { url = "https://files.pythonhosted.org/packages/66/85/3b5358f60a13210f0b67d3755c168ef758701b021e655d88d4da28554467/charset_normalizer-3.5.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c9790464842f85f437dbbb54417eda1e0e6bfc52dd8d22d6fd1c994b73b2dc74", size = 245335, upload-time = "2026-09-30T04:35:10.104Z" }, + { url = "https://files.pythonhosted.org/packages/74/75/77c1c479b09ecd751d1e767b251ea5c14d4d50ff757bf404afab2692f600/charset_normalizer-3.5.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4685902cf26edf013ed7a3da0f426ebba7a00ebb9541386d835afbf002c11cab", size = 291707, upload-time = "2026-09-30T04:35:11.575Z" }, + { url = "https://files.pythonhosted.org/packages/0b/0d/363f78cacb70f58f15f4b083961bbd9d292f335d3f5c66fc4f1cfe69cb90/charset_normalizer-3.5.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4495c5002a7b28557e7e222e77e0b661183e432b7d6d2e788101e3f240e05b8c", size = 286747, upload-time = "2026-09-30T04:35:13.022Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ed/cf505d3011ffceb12c2067a7a5d3cfe92b875d4d44bb0ff0d69375e2c184/charset_normalizer-3.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:211d5a3eb6af8f513b8d4ca19a8c1b7accab1b5f0d3175f9826b03c1a920dc1f", size = 269972, upload-time = "2026-09-30T04:35:14.606Z" }, + { url = "https://files.pythonhosted.org/packages/15/d8/f0a93a431d170e7ca681d4f6650fee3de934d18560e474e7267eb4b0f987/charset_normalizer-3.5.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef4fcbf3327382cd4c9f540babd61248208af7b93eec4de397b4d5f58a09e288", size = 269339, upload-time = "2026-09-30T04:35:16.087Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/9b2bd1c5b7af02462c9752d33994834ff972a96b4c483eefde9e594488e2/charset_normalizer-3.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd16aabe4a02a297c23417aa17ac6299dbd8c49f673bcd645b4929b11f5a4400", size = 261683, upload-time = "2026-09-30T04:35:17.488Z" }, + { url = "https://files.pythonhosted.org/packages/76/a5/cac540ab0fd61f3fec88ad3dbb64509e71424593d73cfdfff5ab3e4db279/charset_normalizer-3.5.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:fb9e68df06293761f9fe66ade60a9bc6d0f5e42b8acf2939a9158af86ab0e5bd", size = 248143, upload-time = "2026-09-30T04:35:18.849Z" }, + { url = "https://files.pythonhosted.org/packages/71/7a/ff467301deef2089fad87f72df9e000a26a78fec7acbb18e1999371b8369/charset_normalizer-3.5.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:59f63901b0031c3136cf64704dcb21de0bbae62ce2c9529bc39d27665463de37", size = 292358, upload-time = "2026-09-30T04:35:20.326Z" }, + { url = "https://files.pythonhosted.org/packages/ad/77/22d7e785d1e210afc2e2f58600dd1799d17a35665faf84383f002826c5f8/charset_normalizer-3.5.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:304d5463e65a35d7bb0850550e0780395395f6fcf452f04db7d5ca7cecc425ac", size = 268766, upload-time = "2026-09-30T04:35:21.72Z" }, + { url = "https://files.pythonhosted.org/packages/ae/91/e8e946267f1c2d9e2bd651726e2fbd2addf02c4d36cea5069e32ca9d7bb5/charset_normalizer-3.5.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9cf9b1a857e25c4baceeb3624e92a56df3668f398c4acba74e174d81fb4d1d3a", size = 287432, upload-time = "2026-09-30T04:35:23.273Z" }, + { url = "https://files.pythonhosted.org/packages/4e/88/7561d8a88d555e7df6623abe7c0070b4baf47549b9408783a2ae0a1a6cf7/charset_normalizer-3.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:114e4d0c92d618409ed82a99e22b5c5e768fe995f2973f78265f4524f49d4640", size = 272470, upload-time = "2026-09-30T04:35:24.655Z" }, + { url = "https://files.pythonhosted.org/packages/35/7e/578c702301ec036f01455f30744a08d2b42f6ab35b9b2d4bf8cae0ef2a80/charset_normalizer-3.5.2-cp311-cp311-win32.whl", hash = "sha256:2625388c6c754520c37abaf3b41eb34d1cc4a373f457898f08606c8e362b891d", size = 186688, upload-time = "2026-09-30T04:35:26.225Z" }, + { url = "https://files.pythonhosted.org/packages/e8/fc/fdf8cf52ff21cd5bf158f20978991cf985325842f74283eb6df26c8a39d8/charset_normalizer-3.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:87e50a3e7cb90af586b6c5faf23e302a970415ac73bd7bd90a515a04b427ef96", size = 214932, upload-time = "2026-09-30T04:35:27.796Z" }, + { url = "https://files.pythonhosted.org/packages/97/66/3e45a506d8110b632541faf9a9470185aa9878f1ed44020f31346c1c5e5b/charset_normalizer-3.5.2-cp311-cp311-win_arm64.whl", hash = "sha256:254eb48b9fa5ee9898a3c445825a1f340fe53712a098904b39b0bddba8ea3cb1", size = 202828, upload-time = "2026-09-30T04:35:29.259Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c8/693809898870237d82785a03f3b2b58fe4c9f14669f84a7d4e623c92a59e/charset_normalizer-3.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed2a239c0ea213acc1908150a3037257083c7c083128f1a4cec2ec4b97dca491", size = 367780, upload-time = "2026-09-30T04:35:30.888Z" }, + { url = "https://files.pythonhosted.org/packages/c9/87/2fea8c13dc24b3ca9c6f803a5b2dfdeae73eb4f9e12c7885ed908ff0433c/charset_normalizer-3.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b91363207bd9dc966a691e959bb47f64b30f7ac4b072be9968b366982f7db77c", size = 246730, upload-time = "2026-09-30T04:35:32.286Z" }, + { url = "https://files.pythonhosted.org/packages/a8/9e/09efac30b937722f46d3110ba30b875b24b2e3a266ed746cc4e376a94d80/charset_normalizer-3.5.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:38a873987f3be698494da8b2e3085e29da02da7b633dce73e79c699a113d7bf0", size = 237707, upload-time = "2026-09-30T04:35:33.709Z" }, + { url = "https://files.pythonhosted.org/packages/9e/18/70d76670b13686237863a379928d60bd10e021f17d243ab3d7014c4a5f4e/charset_normalizer-3.5.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:355ad8011081dec5412240c087a9a0c9d4d5039f3ed11a3f13e18c2b29b56c51", size = 273050, upload-time = "2026-09-30T04:35:35.138Z" }, + { url = "https://files.pythonhosted.org/packages/54/e2/77a8b09d5adc013ed07b95b01b8b8fa5441c4e810e83ee7e4aae2fa4d91a/charset_normalizer-3.5.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ee21e28f0430bd6dc9086c6e525d5e818a44a5ad19720c8a0ef766792f3eb5e5", size = 270345, upload-time = "2026-09-30T04:35:36.502Z" }, + { url = "https://files.pythonhosted.org/packages/7f/c5/38806a25ab5e65fc178f39affeda20858efafede2fce1ffc2556cfc9fe73/charset_normalizer-3.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3d31298449090ab8d47b7b1b2a555ff73cac7ed438a08b7ac160980c7ebed649", size = 257601, upload-time = "2026-09-30T04:35:37.919Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8d/213565184708fdb263ae55e2c04ee1ff748129dd65d48ed0e3502da9c85a/charset_normalizer-3.5.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5cde776b7cc66e4f6c99612cea4aa7269aa65863f7a15841b2c264f103822f4e", size = 252222, upload-time = "2026-09-30T04:35:39.544Z" }, + { url = "https://files.pythonhosted.org/packages/7e/24/76d2cefc25472531e4c5c7dfff68865eb1c39b78482f0fdc15b46f047830/charset_normalizer-3.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ae4f5fea5b8b8ccff88238cc8569303e5ee95efae67fa62922a311397a71f346", size = 248482, upload-time = "2026-09-30T04:35:41.088Z" }, + { url = "https://files.pythonhosted.org/packages/7d/dc/65a801b66ab4c197e22c433ab25e7ac24324ac6f45a2269aca42cce309bf/charset_normalizer-3.5.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:f7d486c83842422badd511868fd8a9a20e9407ace71564b6af47ce7e60a336c1", size = 241206, upload-time = "2026-09-30T04:35:42.59Z" }, + { url = "https://files.pythonhosted.org/packages/a7/95/ca9b5eabde673002c6f1e7ada1b223916fe18f6d661da7aabd4d643718f1/charset_normalizer-3.5.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:11a4d68a6ecda3292cb1e50239e111543ba5d709bb62a6b4ea1afcfa729d8875", size = 273190, upload-time = "2026-09-30T04:35:44.347Z" }, + { url = "https://files.pythonhosted.org/packages/2d/8b/803b4d2a3f6e1740f63f1e87b04d14b42f3d4fdfe6ed7d4db2d34102b14f/charset_normalizer-3.5.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d6734d2ef8a50fbf8445c139477da401f50d62a0606bf00e20ec6d87773fefb1", size = 253527, upload-time = "2026-09-30T04:35:45.915Z" }, + { url = "https://files.pythonhosted.org/packages/a9/55/93c0e5dbd085ae0471346026abbe7e0db9ea2d6fea74e51f0b5a46f233a7/charset_normalizer-3.5.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a815775b6c38d4e0ff7bcffbeba67feded90202bb6a226b8dd35f1c855217413", size = 271285, upload-time = "2026-09-30T04:35:47.49Z" }, + { url = "https://files.pythonhosted.org/packages/95/69/0dbd0e0b9b16cfa816cdfcb3e2e3854a1f680dc07fb1245ea125e7448060/charset_normalizer-3.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:23851fb4e1b85ed3f6c2a27b777cdfe2e19fb5b38429a8faf38c7542b7665869", size = 260010, upload-time = "2026-09-30T04:35:48.996Z" }, + { url = "https://files.pythonhosted.org/packages/58/9d/e7b88e7b1bf403590c3b573277b5e1e488c68c7a6fbacca310a2c324e90c/charset_normalizer-3.5.2-cp312-cp312-win32.whl", hash = "sha256:db19d07e2e0129e974a0e65d0064fc222a446cd5122c2fd4184d2af9fc734a9e", size = 184125, upload-time = "2026-09-30T04:35:50.777Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e6/e6e083884cbcfd49c64865af05027fe7011be7b2d9179524f099a1b611f3/charset_normalizer-3.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:780fbe7cab297b81dad9fb8dc5eb003c0468ffb0d9e5f65068c53a34661a96bc", size = 207486, upload-time = "2026-09-30T04:35:52.194Z" }, + { url = "https://files.pythonhosted.org/packages/c4/e3/017aea0911ada7405a825c7d937eb3a13009664e2f5b38e8c4bbf2abf894/charset_normalizer-3.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:e2af3aad578aa6bd1384bcf4750fc285e5a9de53f40b7d41e5a0bf748edeb2b3", size = 196734, upload-time = "2026-09-30T04:35:53.636Z" }, + { url = "https://files.pythonhosted.org/packages/c5/34/68292d68512768591aaff07c59bb53ee31341c87759433a859c4641a50c2/charset_normalizer-3.5.2-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:ed905975ab14056a2e5eb1c376cb2e1ebc5396baf84163939c518556fccde9f5", size = 225946, upload-time = "2026-09-30T04:35:55.313Z" }, + { url = "https://files.pythonhosted.org/packages/e3/80/bee0b01b90ccd5322ae1d0abb33fab1bd95b7c2eadaf02aeccf22e04ee83/charset_normalizer-3.5.2-cp313-cp313-android_24_x86_64.whl", hash = "sha256:a66c3bc5ab1f0ff2164fc9965ddd611ff0802173f4b9d24554c563f6ab7e1d6e", size = 238619, upload-time = "2026-09-30T04:35:56.863Z" }, + { url = "https://files.pythonhosted.org/packages/78/6e/60ce52a85a7fd631ae8482ae6d74521014ca2f255892679484dc04d7ef56/charset_normalizer-3.5.2-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d2374b62878abb00cd8309b32af6c0b715cd02dec0ca74ef12e5069bdc64144a", size = 206701, upload-time = "2026-09-30T04:35:58.639Z" }, + { url = "https://files.pythonhosted.org/packages/36/8c/71aafad23f971afc84c2b295bc0c560739ce1dac558aad9fec22e39f3639/charset_normalizer-3.5.2-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d376bbd28b3a8999db1a103b3b388aee6f1ddeb3e51bc2172993efdcd86e064d", size = 210506, upload-time = "2026-09-30T04:36:00.147Z" }, + { url = "https://files.pythonhosted.org/packages/91/da/3c5a7798c046df7d2d68ad653cf5b6c5a8bfee225055a843c6f2f42aac1a/charset_normalizer-3.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6045373d5a89a5ec71afde535db987ca28e76dfa276c2d4c818265b375d4b055", size = 366626, upload-time = "2026-09-30T04:36:01.77Z" }, + { url = "https://files.pythonhosted.org/packages/e1/16/710ac3de2ee354e2bd1a9c94efe45a2d27b5c6ad39b2d6a905be2c094b6c/charset_normalizer-3.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:849df64e889b2e17230d58410a03dba311a65b163508fd33679b2b737d4b7858", size = 244624, upload-time = "2026-09-30T04:36:03.389Z" }, + { url = "https://files.pythonhosted.org/packages/d6/39/45c7439f5b63d24f7d5b2a1d760f34af7628782d7144b4cc8ded45c2d4bc/charset_normalizer-3.5.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:15c44f7edfd477b06f517a5cc317fc1707edb9de2c865f43d4b6513907473234", size = 235731, upload-time = "2026-09-30T04:36:04.987Z" }, + { url = "https://files.pythonhosted.org/packages/4d/34/38f3154785ce92e9f56eb226f4d35bdfae6b008480dd055f58837a89c810/charset_normalizer-3.5.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a89012d6d5476ee112d20d998570ed58df2260a852afb1758809cd6900411d21", size = 270352, upload-time = "2026-09-30T04:36:06.412Z" }, + { url = "https://files.pythonhosted.org/packages/04/f3/859f74e7babc977705026b30593b3be04049632a522fb7000f83c033d747/charset_normalizer-3.5.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0c951d5e6dd9c2ff60609476752bee49da4206adde960ebc247766937f72e718", size = 267393, upload-time = "2026-09-30T04:36:07.865Z" }, + { url = "https://files.pythonhosted.org/packages/4b/85/41d27f234b82e47c167a5f6c0f62501dc0c640585ff4aba79e08a390336a/charset_normalizer-3.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7218e8f32b0956cfcd048fd42d9d5779809745ca1d86113ca56f66e7ae1549c4", size = 254660, upload-time = "2026-09-30T04:36:09.248Z" }, + { url = "https://files.pythonhosted.org/packages/58/ca/5d1a997587febe5b26d8daffe363b5c1a091cece19828eec6502fd09c5ef/charset_normalizer-3.5.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a19a731138fc27d5682277d3b9df22855cea1239bce7fcec5f78f42ef2d1f3c3", size = 249996, upload-time = "2026-09-30T04:36:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/b3/1f/d1e78246f7ed60c8c8d606b4ac27f66ce49cc3e95f24893ccbeba9f77302/charset_normalizer-3.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:62603db9a7caa0802eaa28c1c46fecd7b3a263a774069c24c3c28c302448721c", size = 247028, upload-time = "2026-09-30T04:36:12.294Z" }, + { url = "https://files.pythonhosted.org/packages/8e/37/eba316edd4f0c4d3a5d945924c4eeeae59abac4056aa815d8a4268f863a2/charset_normalizer-3.5.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b6856554c4f44d79fc2307d5768854310a8f0096e501c75637542c82292b0429", size = 240562, upload-time = "2026-09-30T04:36:13.887Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8e/aaa037d40ca9ef045977f1a661048b1aa33f223adfce3452fe9be9f79d14/charset_normalizer-3.5.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1bc0baf5ef96b6ede57d47f4b8fe4d9d84019c3bfcbeb20a41edc6a6ee341f1f", size = 271699, upload-time = "2026-09-30T04:36:15.41Z" }, + { url = "https://files.pythonhosted.org/packages/26/19/1c1c9f75974adf523b87f34b8a2adc5a435cd65916812bcbd0dfa45f9a29/charset_normalizer-3.5.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:56bc200a365efb37383b7852e4cc5898d3b2da5987289b543956cf8cad71018a", size = 252030, upload-time = "2026-09-30T04:36:16.839Z" }, + { url = "https://files.pythonhosted.org/packages/bc/90/0660ef18e18df0a4d2a1a0edff7dfbba42d4e50ef2425557a5bb7051f77b/charset_normalizer-3.5.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2c9ad19a6cfcd5ea5c0d41161d22f9df1dcc277e9bef2751391334546a314c00", size = 268565, upload-time = "2026-09-30T04:36:18.468Z" }, + { url = "https://files.pythonhosted.org/packages/79/ba/57adc269824e8658f1a0f97a9e514c247445a9632b3419b97e0ba37f16dc/charset_normalizer-3.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e243bd13217235fc7290c621941c3f5cc8b66e4872495be821d7436ba2fb838d", size = 257627, upload-time = "2026-09-30T04:36:19.938Z" }, + { url = "https://files.pythonhosted.org/packages/9a/85/33abd4315c052d3d4f54c92b1ee49bfbc0dc7115a981e462a793b6d2ab87/charset_normalizer-3.5.2-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:a090bb2c68df85450502e3e20d665e3a5af9c65a84d6508ed477badd49166fd3", size = 143407, upload-time = "2026-09-30T04:36:21.376Z" }, + { url = "https://files.pythonhosted.org/packages/4f/de/6435e18d1aaa5d910b896d551411c96af1f42a0c56c29afc2016c61ccc2e/charset_normalizer-3.5.2-cp313-cp313-win32.whl", hash = "sha256:2b7b3bbfb4fe8ef40600792d762fbaa9057559f9d3fad209525b7a22b99e91fd", size = 183207, upload-time = "2026-09-30T04:36:22.776Z" }, + { url = "https://files.pythonhosted.org/packages/9c/76/b8ec57f4e9ee3253541abf95e4a462c0175fe8032dcd070f1f2421240942/charset_normalizer-3.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:78456a747de8dc58360ffa581f30a002baf5aa28cb262536545e91f113ed7639", size = 206489, upload-time = "2026-09-30T04:36:24.306Z" }, + { url = "https://files.pythonhosted.org/packages/3e/60/c647c6ae47480221e875ea5d743ff94946f7416e3c69415ab772928e8d32/charset_normalizer-3.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:11912e4bb14baae7c5d8791aa55ba0a3a03ec6729073307b0f57270abaa713d3", size = 195501, upload-time = "2026-09-30T04:36:25.846Z" }, + { url = "https://files.pythonhosted.org/packages/58/ca/7aa91362a2f77ac8e9e28a9b902a74f7d0e11a851ef0d27a74308da8cd90/charset_normalizer-3.5.2-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:1afb975bd5d68d5ce9f6b6d44fdf2f7e34b895a35e95708a7a91b20a3b51d187", size = 226010, upload-time = "2026-09-30T04:36:27.669Z" }, + { url = "https://files.pythonhosted.org/packages/a8/cf/ac8878d0322cf88a1aad4c7b147db32ca0bd806eb0060957b2e31486dbe6/charset_normalizer-3.5.2-cp314-cp314-android_24_x86_64.whl", hash = "sha256:bbbfc8e28816f19d7c0f1816664980c0a9875d01b27cdf8eedddb639d9e108ad", size = 239266, upload-time = "2026-09-30T04:36:29.434Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6d/9a08d7e0b29b7208e2c6c01dc56c8e0520e7c7beadbbfb024b58fd69c8a5/charset_normalizer-3.5.2-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7967d08cf06dee78443b874f98c98036f624f3a4e73e11f9f64f5be4d25393cf", size = 206783, upload-time = "2026-09-30T04:36:30.872Z" }, + { url = "https://files.pythonhosted.org/packages/82/44/b0aa350280e6ff5a5492d17cf10460dd39d5ee848f872f7ba2df10607f60/charset_normalizer-3.5.2-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4c2b5031f63e331e3839b40aed2dd6f191e9c07edbde303e7876846ea1946995", size = 210449, upload-time = "2026-09-30T04:36:32.625Z" }, + { url = "https://files.pythonhosted.org/packages/7c/8a/40db9aa9f5907bb0e6f8b6d64064bf8852fb33d4b813ff9414911df7647c/charset_normalizer-3.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fcff63213e8e6e47770541a4607175404f47cbb3ebea7b6058cc82d524a0e424", size = 367815, upload-time = "2026-09-30T04:36:34.197Z" }, + { url = "https://files.pythonhosted.org/packages/7f/72/9c5e7707b57c8ddfa9ddf7b0b1d009d7fbab9e9e887d5b721060f37e307d/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8d86d6fc60743dc916eb79e2eb1ec4818e21e427731543af40a3021851174a13", size = 247095, upload-time = "2026-09-30T04:36:35.803Z" }, + { url = "https://files.pythonhosted.org/packages/83/09/71e453691e927de4ddf792770cfaab3f49d494e222f66ea5e404bbd5e39c/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a881931aa470808df94a8c380eed2bbbc76cd9dc622310f99665658c821eb6d", size = 233942, upload-time = "2026-09-30T04:36:37.407Z" }, + { url = "https://files.pythonhosted.org/packages/9f/86/85c84e4da8b27dd409577d9437926ff581c5f9d3c66038dc68c1a526de51/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8024d00c3faf3fc0c16e07a69f4405e8eac7cc0ab15f65fe6cf43827c4cf72b4", size = 273762, upload-time = "2026-09-30T04:36:38.904Z" }, + { url = "https://files.pythonhosted.org/packages/92/08/564955a4b5f2ccb410ab480bbe8c6a18063ff27f2d35458731c4a5335df9/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4d48f2d08b9de5864e2c8744d4461b862fb149a18274abc8b698c45975573438", size = 269514, upload-time = "2026-09-30T04:36:40.469Z" }, + { url = "https://files.pythonhosted.org/packages/18/24/bad3ac4271589df29cf5ce2f5ae490518a5739358052bd0d61209e6fea54/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34276fd796040bf0993ab33a369aa572e6979c7aab225a88893667ad8eac8f7a", size = 255056, upload-time = "2026-09-30T04:36:42.02Z" }, + { url = "https://files.pythonhosted.org/packages/d6/3e/350d89ad49916b86554d6f5f2d03ec1152148f87e5ff735106c6a03b1a36/charset_normalizer-3.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0521c5665880b33d603717defa76c094048900010897909952397feb3039da56", size = 251311, upload-time = "2026-09-30T04:36:43.577Z" }, + { url = "https://files.pythonhosted.org/packages/56/5b/4970a2d154df502e133402906dd04e3ae7cada7b3011283c88d0479a2585/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:eff0ac9dbe711a4aee69bf04a83896aa9b85f19641264053a9f6d48573abb7dd", size = 250190, upload-time = "2026-09-30T04:36:45.185Z" }, + { url = "https://files.pythonhosted.org/packages/88/8c/f1a91bddc8fb47c2889e29ea7ea49a194eb0d9868675d786806519c00d76/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1503bccbeb36d5527790c3930327704c39af22de3112f1b1666a9f3ce15ee204", size = 237098, upload-time = "2026-09-30T04:36:46.689Z" }, + { url = "https://files.pythonhosted.org/packages/24/0e/bb5dace3cc7e79068425386a6589c19b5a2ab5fefc2a46abea6919683332/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:52aa6992700996af31f375de0c6bacd402b0097fe40b53c426b9f51a90ebabc7", size = 274228, upload-time = "2026-09-30T04:36:48.31Z" }, + { url = "https://files.pythonhosted.org/packages/9d/79/b849ad523017ea9f5a45581bbebed91439e0cf42fd2860a6f64e358eb5a6/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e09a3942ecbdee5cce73ea9d42da82b81b72ac1bf031ce069b93b5adf4eac8cd", size = 252401, upload-time = "2026-09-30T04:36:50.091Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/75469d690cf47200bce8f6cad7655724fc23148e147abfc5ce78b5f65863/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c7c9ab723cde841fefb34efbad91e87f00a674b1fe1cd0784fde742bf2c154dc", size = 271846, upload-time = "2026-09-30T04:36:51.719Z" }, + { url = "https://files.pythonhosted.org/packages/26/cd/6d52d3c7437cdcf2e310ce9f28f282e733d4ef60ed19105d1819c356255f/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ddc7dacc8ece3a182e7f15cb862d1fd616b46d076cb1ae9dd232b2c38b655874", size = 258151, upload-time = "2026-09-30T04:36:53.234Z" }, + { url = "https://files.pythonhosted.org/packages/f7/4c/070b38bdb5f49a70199fce923ec0726a49536a63ab262abbfcaaf351110b/charset_normalizer-3.5.2-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:ee43c17b173d46a3212baa6ead3ae258eeabdae48c263a01ccf0218c366dd655", size = 143570, upload-time = "2026-09-30T04:36:54.816Z" }, + { url = "https://files.pythonhosted.org/packages/81/84/9ebfc8ed6c8c4fcd8e726ff6bf220cc8deb3966e31dce9be8dd8aa017e64/charset_normalizer-3.5.2-cp314-cp314-win32.whl", hash = "sha256:4f87960d57feabfb618e4e0af6e7371645fa26a277860739d6e5d6e0012c92f0", size = 186412, upload-time = "2026-09-30T04:36:56.643Z" }, + { url = "https://files.pythonhosted.org/packages/d1/78/5ed86f743d4bc350db307e7636419a0a5ee1d91806d30c7f667bd5c80dae/charset_normalizer-3.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:e4e81e09c1578b8df602e3db08b0b3ea0a6947ad612f52bf8dc5ea8d47691f0c", size = 210965, upload-time = "2026-09-30T04:36:58.205Z" }, + { url = "https://files.pythonhosted.org/packages/53/94/a3a7698e9b1a395e1eb99ccd9a324be9347973bff4e72db2a06496d7cd27/charset_normalizer-3.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:80d02b6f04e92601a081dd97b23d3128033098bff5d35d392ddcc0476ea11253", size = 200406, upload-time = "2026-09-30T04:36:59.764Z" }, + { url = "https://files.pythonhosted.org/packages/c1/48/c5dd00d5ef7791f02666de250a5bb6071e29b7e133cf4b835800b6d3bc27/charset_normalizer-3.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:dca9ab98072a5a54ebacebdc45f53e645336b320c667410b061be1ca588ae709", size = 396871, upload-time = "2026-09-30T04:37:01.543Z" }, + { url = "https://files.pythonhosted.org/packages/12/c8/8379554b42e8368161d898476686947a0fdbd3e8865170d7909dcabfdee8/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f0aa869112ef88429ae17820d99c3dd9504c9e9c671d3c246f3d7442cb051084", size = 249711, upload-time = "2026-09-30T04:37:03.111Z" }, + { url = "https://files.pythonhosted.org/packages/4a/eb/2ddb1035d17320caa9f41682935123a9a250277b261c3efc86b2d2a21343/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c0afc6800ba57ccc350374c5bd6150419915d95ce93cdbab2d783d75eaf30ecb", size = 231146, upload-time = "2026-09-30T04:37:04.721Z" }, + { url = "https://files.pythonhosted.org/packages/4a/24/2ecb4bde104322cd7859d6594fcfa74649f8d90b3221c9feecbef149875b/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dcd882da75ef9adf94903b1e3b9419e8aa8fb4c7396822b834b9ef7fb96954f", size = 271552, upload-time = "2026-09-30T04:37:06.295Z" }, + { url = "https://files.pythonhosted.org/packages/3f/98/9d5f6ebc3aee9fef5d30b4aff11fb2ab7a1222b4064f8ef2c7c87cde217a/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2e06a3a98f916dd41d27f3105e02e7a40181c98c94b9158733d03a6f80506c09", size = 269810, upload-time = "2026-09-30T04:37:07.905Z" }, + { url = "https://files.pythonhosted.org/packages/09/e1/a3b06a10461b1b7628853c934c644e03bc28e42767116afb52f19a56519b/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bd128f206a7752ae1f2ab6c61bf8a24ba28913a10df8b14c2637b973ff97a80", size = 259845, upload-time = "2026-09-30T04:37:09.554Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d3/6f561f74a296cf27d61775a1dc665ad13f3bff6a798810ca05907f37a7c4/charset_normalizer-3.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c8f3d67aeaf55f017982b73683f0e7342ba2f6635a78f69ce89ebb26aa411e5c", size = 252825, upload-time = "2026-09-30T04:37:11.274Z" }, + { url = "https://files.pythonhosted.org/packages/26/9f/69e13ca3b18f43e0eafcd34c04a45b732ae22a43b54a5fc9e119103356eb/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fe9753dfee015c570d73df76f899f18444d41388bffcde097deba51c4fadbb9f", size = 252659, upload-time = "2026-09-30T04:37:12.941Z" }, + { url = "https://files.pythonhosted.org/packages/73/a9/ace29806a0dae18939919c76ba526472d83214afa101105fabff2cf30625/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:92888bb3187c5ba50500b00b3b310c9f2c651709d28036077680cb5255450a03", size = 238819, upload-time = "2026-09-30T04:37:14.659Z" }, + { url = "https://files.pythonhosted.org/packages/f8/c1/6116d52a2e3311ec80f21f5fb5e17b27405f10b9608af8f6e69516841a1b/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d008d90a7f2471519aef0c90dfbe73b3e6e4d5e66ac48e19154c17e89e98b604", size = 272973, upload-time = "2026-09-30T04:37:16.346Z" }, + { url = "https://files.pythonhosted.org/packages/19/aa/9955c7e93bba10a9c7e8f7a5031b7ced66f3a1883a55c00712b8d5850ff3/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31f3930700408d211f13378ccbe1c40845d8da54bd0681fac3a9b5aae81c7aa8", size = 253692, upload-time = "2026-09-30T04:37:18.212Z" }, + { url = "https://files.pythonhosted.org/packages/bb/33/2a6ae7fdc1b10cb581cef91addd8cdfc5f40d50abb5702309369d5834579/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:2a925889534b3748302dae5dead07cc13480de1dac3aea80a941b729b471ef93", size = 268757, upload-time = "2026-09-30T04:37:19.877Z" }, + { url = "https://files.pythonhosted.org/packages/a2/22/80992720a0282cd39bba1db35868e6b9c22f41281160143a836544bc1d8a/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f5ec61164adcec446f8969a3358ec3f9b26bbda3b9213e5586d219afa8df2915", size = 262220, upload-time = "2026-09-30T04:37:21.583Z" }, + { url = "https://files.pythonhosted.org/packages/92/9f/181fd07e1bffea1d95cd80c84ac537354f50699c22cfc4d3c02b6fc16208/charset_normalizer-3.5.2-cp314-cp314t-win32.whl", hash = "sha256:598a11a2c7ebaa5334bf698bf29568c9c390abac6a154d8170fedecd1cea38c5", size = 197797, upload-time = "2026-09-30T04:37:23.235Z" }, + { url = "https://files.pythonhosted.org/packages/49/1c/25d8415ec1c4f2f41f1680435e4c87cfb378ff2f677d950946f2a45d0632/charset_normalizer-3.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:7fdde2c9fd9e3eca40631e024664cf2584272cc8f96308cbe5fdfc930f51d8bc", size = 222765, upload-time = "2026-09-30T04:37:24.891Z" }, + { url = "https://files.pythonhosted.org/packages/3e/b4/46b48f013dadfc0d0d33b375438e31bdf5a989dc68389c6bf627054d4df9/charset_normalizer-3.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d1befeed746d247c81127bb14de9dc3d30edb6e5976d34f83f86ed262b1d9105", size = 210985, upload-time = "2026-09-30T04:37:26.634Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e9/34e597dee616d0b8ee4b34d29399e85c2204ade174157a48505d42baa4ff/charset_normalizer-3.5.2-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:87475fabc8d9996fd9c27debb395e642e8c838d78a00b6e932227a0e06b81e26", size = 367676, upload-time = "2026-09-30T04:37:28.329Z" }, + { url = "https://files.pythonhosted.org/packages/60/9f/a5d1c91c0263745e2cd344c5a4415d787c575501ab1d449f1148ac6b495d/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9409a8bf35cf78353942504b24a57de3d75b708997a1e4bd8db71ac8633ce364", size = 248414, upload-time = "2026-09-30T04:37:30.167Z" }, + { url = "https://files.pythonhosted.org/packages/26/79/e697f77464748a3ee3cf490c83d592459400d4898380d66c38366b03080c/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:498dc3188ca05a68231ac3fdbfc7f57eb67e1343c30e0fea17f8218c1599b253", size = 244775, upload-time = "2026-09-30T04:37:31.964Z" }, + { url = "https://files.pythonhosted.org/packages/ca/87/3d42a42e18ea066e2513936fd678a00696e77878b5ae04528976abdbcb83/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e242bb1c5e76e97dfa9e7f209a71e93a01d7f19ffdd5cfbb2e2d55b4f08f8ab0", size = 273877, upload-time = "2026-09-30T04:37:33.661Z" }, + { url = "https://files.pythonhosted.org/packages/c3/76/8a28136f3938ba9836f84280ce0c4d61ed1cf15a036b2034900c62634162/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:def79fa35ef0cef8d2accec024f4fdc7ead3012ff02f5215c783f39f03ef8cfc", size = 269455, upload-time = "2026-09-30T04:37:35.573Z" }, + { url = "https://files.pythonhosted.org/packages/a0/a1/4fbf5d0f0f1b2a080474c1cf9a2f12c4c6531bb0e8ba591055e846d2b4e9/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3df041de8887954562c9b261cba85ca0e9ded74048daf125f45edcfaa4832229", size = 257336, upload-time = "2026-09-30T04:37:37.397Z" }, + { url = "https://files.pythonhosted.org/packages/ba/a2/8b50aa320adb880ad579518e6f718f24944804b42a88b83d267d5d444125/charset_normalizer-3.5.2-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:04851f73ae72b8413dddadb16a49dfee95263553741fd42d546f7d66907e6be5", size = 257584, upload-time = "2026-09-30T04:37:39.522Z" }, + { url = "https://files.pythonhosted.org/packages/a5/57/50e3fed84e175f40349bd0da7a4fce94c87f0378f52d74f511d89e0bdc20/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:183b88127acdb4fabe59d951ab424faf1af7b63cdbb5f776186c1ea2ffcaed98", size = 251854, upload-time = "2026-09-30T04:37:41.23Z" }, + { url = "https://files.pythonhosted.org/packages/d6/54/f7fbb3493c9f49091213b9c2d6dd65800696f1ce1a3f196a4205f50417b1/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:16fa0eccf81304b79c5cd87f9271c3b85dd9dd99245e4422ae9c0dd45e0f99d3", size = 248767, upload-time = "2026-09-30T04:37:42.883Z" }, + { url = "https://files.pythonhosted.org/packages/d9/37/b3a6385acc5a1e45b39ae9c90bfb9cf838a09b9dd37ef2740ab4c6b4a2eb/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:7441d755b7ab94f8d4eb3e43ec05482d760842fd263d003a99102d742cd835e2", size = 274479, upload-time = "2026-09-30T04:37:44.658Z" }, + { url = "https://files.pythonhosted.org/packages/89/44/809913e2cfd279e635a9294fdbbfb1b1dc62a8189d473d561f649fce98d8/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:ca403d7e4798f525fdfc78e258820419cbbd0f0ecbab9de7840e3c017cf6b8cf", size = 258310, upload-time = "2026-09-30T04:37:46.529Z" }, + { url = "https://files.pythonhosted.org/packages/af/a2/f28400ab13359d91bd39179df8e149376b9bf36588e739a3a4f9de2b84b2/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:df29a0a7107f7011e77f4eebdddec4c7331e24d787a0b21a46d63bdf7445da95", size = 271100, upload-time = "2026-09-30T04:37:48.399Z" }, + { url = "https://files.pythonhosted.org/packages/e9/89/9bab37955edf0adb3b66f8a3a6617d9f2f487e0d56f295a6a286cb640aa6/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:f3c96f633825733f735c5a9cf21d21a257d8e1edf0b1cee0a064b9c424ca0f7d", size = 259936, upload-time = "2026-09-30T04:37:50.023Z" }, + { url = "https://files.pythonhosted.org/packages/23/b5/4459e08d45a679f903d50fea08bc52cfa728cca4d7bd02c757b5e5abda2e/charset_normalizer-3.5.2-cp315-cp315-win32.whl", hash = "sha256:281cb91036248400f4cc957495cccd44c275c2e0c5854f7e45ac5cf7dc193847", size = 186301, upload-time = "2026-09-30T04:37:51.722Z" }, + { url = "https://files.pythonhosted.org/packages/98/e8/55d5fd3935b4bce6da4fe0df61898e8c82653e317e677bd58aceb9c60f13/charset_normalizer-3.5.2-cp315-cp315-win_amd64.whl", hash = "sha256:89b53f3cda69831909888e0494f4fa0bcd3537e3e138dabeb620bd6ad946bae8", size = 210858, upload-time = "2026-09-30T04:37:53.427Z" }, + { url = "https://files.pythonhosted.org/packages/a9/5b/974423c2fd8e524c7a7f64318c1e02240ef954912fa2b4d70344107b9c68/charset_normalizer-3.5.2-cp315-cp315-win_arm64.whl", hash = "sha256:6be488a102b8cf28d0391d8c4ba7748938ae28b78ad901f8585520fca33ead1a", size = 200309, upload-time = "2026-09-30T04:37:55.015Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f9/00ee0195db1013d8f7c416fd770fbeb560bb46eb2e36b054d05cb56f6cfa/charset_normalizer-3.5.2-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:915563965d418f986e7e145accc592eae9e1a1be3566ff98a05d7a9ec42a76e1", size = 395343, upload-time = "2026-09-30T04:37:56.743Z" }, + { url = "https://files.pythonhosted.org/packages/04/3a/c00b50e94c964cf934c7899cd47c97952fc11dad71cc5884b3c61795b09b/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65cd72beeeca9d3aaea1201e5923859f308f952f9c71de93f06063c79f0f7a3b", size = 250114, upload-time = "2026-09-30T04:37:58.607Z" }, + { url = "https://files.pythonhosted.org/packages/50/27/d102dc880bbcffd0479ab64dfc1fb96777a854355a55e2bda72a71efadcb/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b7fd005a73d9e657273b7a10dc71a9e03c8fb9ee6999798d6918ce095b81ac7f", size = 239997, upload-time = "2026-09-30T04:38:00.511Z" }, + { url = "https://files.pythonhosted.org/packages/a5/4a/bf7ef45794dd293fab5f98a9309817977fbb845b9998f171b8cc5d8437a3/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e54da4baf05720032d527874d40b65fa4d7e5c6c6a43d0c3adbeffcaf275a2b3", size = 272313, upload-time = "2026-09-30T04:38:02.509Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ee/008a2837737991474c5754bb3191010007663860979701990982a502cbaf/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:124fbf1a8ff966d87ae05bb8bd45a71f966055ed8bba320d0c7cf450bc5f4d0e", size = 269986, upload-time = "2026-09-30T04:38:04.435Z" }, + { url = "https://files.pythonhosted.org/packages/93/ad/bd74a283940dc910c5b14f8e4f80a248082bc9c0fcbe1f54530cb6d9cc5e/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28b4f0d66fb834ff90f28209ac7bce77868c45d8c93e26f906709d9b7c2e1af9", size = 260202, upload-time = "2026-09-30T04:38:06.549Z" }, + { url = "https://files.pythonhosted.org/packages/8a/7b/ed341c66f69f688723501fac752be3d63c7159ca0d0d4174fc611e5710bb/charset_normalizer-3.5.2-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:58ca3755ee7ff7f59b57789ec9833c9de9ea275405cdd240eda1f193112e398a", size = 259083, upload-time = "2026-09-30T04:38:08.311Z" }, + { url = "https://files.pythonhosted.org/packages/cc/9d/e41588b777965e5031a43128a1e96173ebb35ac75fc53ec3b517e7c21cd4/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:443eae2bf318abeaf6f15d785138f71fd6de770e99a92158b8b814265e079115", size = 253081, upload-time = "2026-09-30T04:38:10.402Z" }, + { url = "https://files.pythonhosted.org/packages/81/35/b761eb6d8c1eb218b9b42b9b4d5ac902afdc399fb6dac6f9a9aac7bda589/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:58f361dcbab699cf8f42db3f47c8e7fd1036f138c23a5d08de9fde5f425a730c", size = 248712, upload-time = "2026-09-30T04:38:12.317Z" }, + { url = "https://files.pythonhosted.org/packages/4d/2c/147169a041b747759f37405c0a97157e8e92de967968373101ff14915cba/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:1b4cbc7c3491ccb4aa17fcd8165649d01cf39f76de1696da8631b5f71b85401d", size = 274176, upload-time = "2026-09-30T04:38:14.138Z" }, + { url = "https://files.pythonhosted.org/packages/f0/2d/0ff8db0d373ba8538db686db11cd7e8912031490b9e4f383b41912e8d594/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:ba0b1d2620edf869789c3879223f52bf2afc5d31b3cb47cc57b3a12c05e2aa9d", size = 259413, upload-time = "2026-09-30T04:38:15.841Z" }, + { url = "https://files.pythonhosted.org/packages/8a/8e/b4a085fb47c9d3a7e43576a4784fdd8fe23f907514a972de8086edaf7a48/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:5e2b6b57e9733d39f0c9fd3185efa6b8e29652c4cd8fe94180272cf6ed9a78c4", size = 269434, upload-time = "2026-09-30T04:38:17.626Z" }, + { url = "https://files.pythonhosted.org/packages/83/1c/d8d8d7322a7c3eecdf3237a4a419cf41d2eaad8e006ce7dfdd9d4c8fa2eb/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:51cf45226a9b588d0d2b4880c62d686934b63ab0bd79ca23ab0e9762eb27441b", size = 262716, upload-time = "2026-09-30T04:38:19.214Z" }, + { url = "https://files.pythonhosted.org/packages/a0/16/0e4c6ba9b44e97a2da150e52d331e8f9c968b21b358fbffa6c856cebcd89/charset_normalizer-3.5.2-cp315-cp315t-win32.whl", hash = "sha256:5fb29fb8cd1a46c27a1bf9613ad5ec2599310d46b4025d9556404a6b6a292800", size = 197159, upload-time = "2026-09-30T04:38:21.037Z" }, + { url = "https://files.pythonhosted.org/packages/be/33/e90bc2b1374f7f36ef106f56620de5a783907e19ca857efe2277e31cac3e/charset_normalizer-3.5.2-cp315-cp315t-win_amd64.whl", hash = "sha256:a192e2c40070d92c3ccf777e3a5c4ff515573cd2bb7ed0c537fdadbbec5bbf21", size = 221322, upload-time = "2026-09-30T04:38:22.886Z" }, + { url = "https://files.pythonhosted.org/packages/66/89/dfa6dcb08c200b7830ab56439e8c1890f2971d51aafbb3937894a2e7fcfc/charset_normalizer-3.5.2-cp315-cp315t-win_arm64.whl", hash = "sha256:749e97e1b32313717a565abbe321bc2190bc8b35f1a67e4cdbc7c56c8d8ffe58", size = 209192, upload-time = "2026-09-30T04:38:24.648Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ab/176fbfd5b64939c55d652366aa5b9ef1d767af207a3aa6ebeb0d226c484d/charset_normalizer-3.5.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:4275811936e2f06feff5e598fb42a1b7ae852da8e39605211892b56b81a34efd", size = 331815, upload-time = "2026-09-30T04:38:26.216Z" }, + { url = "https://files.pythonhosted.org/packages/7e/84/371eac6b30bdbcbf2d632a1a01809103459216fcaae61b8b8d922c1bfb8a/charset_normalizer-3.5.2-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1c50fe28bbc2ced33386f298650d91218076c05420e6cbd790b913adc41659e7", size = 253276, upload-time = "2026-09-30T04:38:28.032Z" }, + { url = "https://files.pythonhosted.org/packages/43/6f/c4fbae58febff71709c51bc7e18fdfa55341dc382704740f9f0cbf03817b/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d19fbd981a488e22cd04883659ca6b08f50b5974f9fd7c95655ef6a043e5893f", size = 241239, upload-time = "2026-09-30T04:38:29.732Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/458c3f42164a07d0c5210798e9e704b39e540a6793b05aba67f3a35243a9/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0fed1d06615f022ee3b13caf5e8b180cfea32bb2c5aded8a9d44277afc040f93", size = 231121, upload-time = "2026-09-30T04:38:31.462Z" }, + { url = "https://files.pythonhosted.org/packages/09/54/ab9e89367076f6331bb6c65c4bf14a5361fa5191cb6561bf534f18504e1b/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:838dcc90063569a0448120554591a1d6c4a4ffe11babf048908793154ab86ade", size = 260350, upload-time = "2026-09-30T04:38:33.239Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c1/061431ecc688d9d76602502cb57cc01e691e682c18f1beb45f9673b5bbd2/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2ce45c6627b22c47e390bc91a41c3d13032192e699fa0bea96e9671b373d69b0", size = 255430, upload-time = "2026-09-30T04:38:34.865Z" }, + { url = "https://files.pythonhosted.org/packages/8d/1f/20c8949f0676f7ab811abdeb7f4d7f1cbc6e61ff20bef08b44edeb092bc8/charset_normalizer-3.5.2-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0774bf9bf620249fee3e0b8b9fd3065de213be30f3aa94ce2494b3b638949e26", size = 250612, upload-time = "2026-09-30T04:38:36.649Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9e/46f2fa4c431fc98c4ae76a8cb5bdca54e0341e3cfc3fcfd8e82740250818/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1db38f4c5496827c1a501846d64d14c3b80c7e6714e406cd7dc36a9899fa1011", size = 242083, upload-time = "2026-09-30T04:38:38.26Z" }, + { url = "https://files.pythonhosted.org/packages/bd/39/559be29a0c0f086e0bba6922babd38916cc5e0b58ced4de13ee01ea05508/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:304d8e4d493af723536393eee0c689eb7813f4a474c8b479dee63f1fdd98f621", size = 232738, upload-time = "2026-09-30T04:38:39.81Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6c/387b0e4f756a282831c1d9fc6aeb6c51ca4507ca202767c8de15ce9b12e2/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:9b7f416ff0978e2f2249330527f0ad6fa02f4932e6199692d3b52da2048c19e4", size = 260703, upload-time = "2026-09-30T04:38:41.346Z" }, + { url = "https://files.pythonhosted.org/packages/96/92/1fdf015f09ef449f50d3ac4b67c90887c9c318b727daa95cc4f866e6521d/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:01077390b03f7988f11d700a2194e69b119741a86b1a638b1db88891e3eced8e", size = 247622, upload-time = "2026-09-30T04:38:42.937Z" }, + { url = "https://files.pythonhosted.org/packages/dc/3c/8e7b8a5671ad5d433669fb2a76f1a0164df2d9b1718b0206bc2a16d840cc/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:7e841fb9010836c992c9f12fcbd43a831de93a5f726fc1ccd8ca1d0268c5014c", size = 257500, upload-time = "2026-09-30T04:38:44.604Z" }, + { url = "https://files.pythonhosted.org/packages/b4/f0/45b579df5cabc1d5d53ea1cc35e8437d3ca768c0acccc7041517cb6fbb32/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9cae88599c7219005d879f98e5ed53341e9a122af585e1091200358a3003d2a0", size = 255100, upload-time = "2026-09-30T04:38:46.289Z" }, + { url = "https://files.pythonhosted.org/packages/31/68/fdec18a343f5fb3f310588dd478b09ac4799e0b187dbade3a8cd776f03ef/charset_normalizer-3.5.2-cp37-abi3-win32.whl", hash = "sha256:01b0c0d2262a9e28e8484a278c7e1b5d650e3ac8cf2683d2967e25899f208bdf", size = 174499, upload-time = "2026-09-30T04:38:47.999Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8a/b618149cc5207943a0242068d7a27897f56a62947b5a039085f2a22029f8/charset_normalizer-3.5.2-cp37-abi3-win_amd64.whl", hash = "sha256:9f56f72050826f63dcee7a7f55b0a77168cb3bfc553fd405e7f8f9ece75a4036", size = 200092, upload-time = "2026-09-30T04:38:49.707Z" }, + { url = "https://files.pythonhosted.org/packages/03/cf/4c66866fa9e2b1c78e3c911516d1de497a677b7ac60f1eceda74ce777ca3/charset_normalizer-3.5.2-cp37-abi3-win_arm64.whl", hash = "sha256:40ab6bffa02ae10a0581e6c198be7d2d8ca5c2a0c64e4ed3465d766df457573e", size = 294363, upload-time = "2026-09-30T04:38:51.312Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ad/d07d7862a62ffa6d79d68074d14823243dd235a77c45262acbf6adeb28bf/charset_normalizer-3.5.2-py3-none-any.whl", hash = "sha256:b6b751274acb69d77b3323d6b7dbaa3c7fdfc1eb829b7eb61d262f32e1af9685", size = 68872, upload-time = "2026-09-30T04:39:21.828Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { 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 = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "idna" +version = "3.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/08/8eea9d4b8302028f3abb2c0813953f7aec26d33b7a8960ed760e65ff29fa/idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44", size = 216463, upload-time = "2026-09-17T14:11:04.752Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/e1/2069291243c926a2ff1cd706c7f3eeb9b62144bf60f77c9fb9ff2fb26bd3/iniconfig-2.3.1.tar.gz", hash = "sha256:67f4b9c50da0dedf52af349e7749a80a9057a5031199791b906c3bb3ae878960", size = 21209, upload-time = "2026-10-06T22:48:38.076Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/43/4ca9e49d27a1fcf6bece6f6aec0ea46bb9112489b93d4b688fb415457bdb/iniconfig-2.3.1-py3-none-any.whl", hash = "sha256:9121e2c1fdb355232495be3194c8dfe87ccc2d5dee45947b78e68f499790d7a7", size = 7552, upload-time = "2026-10-06T22:48:36.959Z" }, +] + +[[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.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "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/95/b0/c7453d0b6e2073c3264468b106ee1563750cecc910965e67357e3698c83e/numpy-2.5.4.tar.gz", hash = "sha256:9a94cf751c9ad8ebaa835bcd3d40dacf8534ad086b88c38029b65123c7999d2a", size = 20866315, upload-time = "2026-10-10T20:05:31.422Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/97/ba2074e92b7befea137e77ea8471e768bbd87c339b7e8c9f5a931949f977/numpy-2.5.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c6342f54c67093cae5c0227eb0eb772fdb79f2a2c37a6eb278b9909ee06aa356", size = 17001609, upload-time = "2026-10-10T20:02:40.843Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a9/bac826765e971d8e16e2064e9ac7525fd69b40ac17c905033a7f5442023f/numpy-2.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b11e8fda06a7d69f15ebf542660b74466c2e51094800c1fb794f47ad4faeef17", size = 12015718, upload-time = "2026-10-10T20:02:43.45Z" }, + { url = "https://files.pythonhosted.org/packages/31/2f/5ea3570fcb8ccd0882bea99436a513b2c85dad8f774a2057849130a8fb99/numpy-2.5.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9cb18a327b49c5c337f972b03682f6a49855525faaf3c0d3e9c96cd0fd8880a8", size = 5451717, upload-time = "2026-10-10T20:02:46.169Z" }, + { url = "https://files.pythonhosted.org/packages/34/f2/b4fc1bafca03868220b5eaf729d2f21ebd7d7b151c0f9e144fe212bbca35/numpy-2.5.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:aec3fc4b32ff82421274f5d205c559c51c840c8df66a78efd7f3612dd005a26a", size = 6789926, upload-time = "2026-10-10T20:02:48.139Z" }, + { url = "https://files.pythonhosted.org/packages/dc/96/8319e2457ae4333c62c815c7006b869a4f60985c1e01024c2f8c6c040fe5/numpy-2.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe4d21ab149f15e4e6043dfb0de87e6e5f34ac176cde83060e9802981fca2ac2", size = 15695312, upload-time = "2026-10-10T20:02:50.115Z" }, + { url = "https://files.pythonhosted.org/packages/43/a3/c799c62e19c337e6d3770b08e475887fb30ce8477d3c09efca6b2f0228a6/numpy-2.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fbde6962867ee75b48b0ee29b2b9372ec5d617799dbaf38e82dc0596f2f7738a", size = 16727283, upload-time = "2026-10-10T20:02:53.186Z" }, + { url = "https://files.pythonhosted.org/packages/39/6b/3604e53fb00314d0dc1b94ec9125a1484f649c0a17480b1f0f0c7a9d6250/numpy-2.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:381a7a3d2e65e64c0ec302795ab9dc12bb1e73f150904699c153716177eebdaf", size = 17047890, upload-time = "2026-10-10T20:02:56.038Z" }, + { url = "https://files.pythonhosted.org/packages/4a/7a/e8b58a5289a0d464c52885de47c35a935cdd70c03a4c3ab94a5126416dd0/numpy-2.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b89d0aaae2fe498c648f4c4795c084db535af5bd98ef942b2a3681fb74ce8645", size = 18485839, upload-time = "2026-10-10T20:02:59.018Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c9/47094f597015009f310b8c900def59065ef1ff5a6fe7b51fc65ec58ec2c6/numpy-2.5.4-cp312-cp312-win32.whl", hash = "sha256:9968ab7e49b93ac6e1c3b2239732183152c9150f16308d30b66a372cffe3483c", size = 6138936, upload-time = "2026-10-10T20:03:01.626Z" }, + { url = "https://files.pythonhosted.org/packages/12/33/fefe62073dc8acfd0f2b9ed7c003af2f50aa61555e113e6db02b8f79f145/numpy-2.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7b1b6353e36a7e50de2973a38d705c88ee93adcf120673cee7f45a4a3fa223a", size = 12573091, upload-time = "2026-10-10T20:03:04.349Z" }, + { url = "https://files.pythonhosted.org/packages/1a/07/161270b0c2eec56e4c905f6d6d22e1b836887b2cb189d3f5820aa588e9dd/numpy-2.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:aa1cce2ff3f8d953de38b76bf44602caeb69f101430208f64a10067f7cb4b1d3", size = 10521630, upload-time = "2026-10-10T20:03:06.767Z" }, + { url = "https://files.pythonhosted.org/packages/67/14/1c3ee0118a8fce08565a5d8482631608426a33af10a01077fada5dc7c119/numpy-2.5.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2377da2dd3ba2c1200956acbab2a358c83b8e1f8531191672d1cd6ad83250d53", size = 16997729, upload-time = "2026-10-10T20:03:09.291Z" }, + { url = "https://files.pythonhosted.org/packages/83/8c/b0ea9477fb1f0d4484bbc5cba21678cc9969704d8d7f3f158d1db35f8e14/numpy-2.5.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7415db95818b39ec475a5eea54d9e3b6bc83e3912158e46da3438cdce399804d", size = 12009826, upload-time = "2026-10-10T20:03:11.946Z" }, + { url = "https://files.pythonhosted.org/packages/e2/84/6a3d75b3ba3dfe84ac0053450753d1e6d250a8bf80f66474cc46d1fb643f/numpy-2.5.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6d6a71b9d9a97c03633aa12565ef2825ffa036cc1d99cfd50dacf0f128af4fe2", size = 5445803, upload-time = "2026-10-10T20:03:14.329Z" }, + { url = "https://files.pythonhosted.org/packages/61/18/bb993f267ca20b376e07092a16793a5b31ed3138751e9ba480011a14d742/numpy-2.5.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d8200f16437b289a5bb927c6e184eccc3e8389bc0070fea4cd5b9e13c1757959", size = 6786220, upload-time = "2026-10-10T20:03:16.602Z" }, + { url = "https://files.pythonhosted.org/packages/db/b6/135bb0953b61dc21c6cafa14b424ae666944e4899cf140e00c2b322a1a45/numpy-2.5.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2e71b04c6cad90026e544501bbe0ab9290fa8a4d845e7e8c0d124fb429c988", size = 15689178, upload-time = "2026-10-10T20:03:18.721Z" }, + { url = "https://files.pythonhosted.org/packages/da/24/3bd070f3269dc609d8f26b2643f62ef91bb415841c0b294805aaf7fe06da/numpy-2.5.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6ffa07666f8da0eef81d149934a626d0d95fbd6838432a33e66245423a9062c0", size = 16718044, upload-time = "2026-10-10T20:03:21.386Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8e/9d15bd356b0a019c965312b1a3c6a727cac4cae5bc40045fbc12ce4cff9c/numpy-2.5.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2fa3328f784fc8277fc48026f6cad516f5c561c5d8e2e39b3c9e0c8f23223b34", size = 17048364, upload-time = "2026-10-10T20:03:24.468Z" }, + { url = "https://files.pythonhosted.org/packages/dc/fe/9d5b560db964f15871885f2250795d15945f8699e17ef90c0c2ff4c875b2/numpy-2.5.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b86966fbe4ad7de710422175572bcdc75fdedadfb54bc6fab7deabccddd7780b", size = 18474904, upload-time = "2026-10-10T20:03:27.895Z" }, + { url = "https://files.pythonhosted.org/packages/e9/98/d27552990f1bd611ef3e7466adadc78312ea2df63b83aad47fdc3d3ca8df/numpy-2.5.4-cp313-cp313-win32.whl", hash = "sha256:5258bc06526964be5face2fc6f756857a3f24f21ec3e72ca131337a75b165d6c", size = 6134537, upload-time = "2026-10-10T20:03:30.511Z" }, + { url = "https://files.pythonhosted.org/packages/90/8c/140a40398a66b4471211be1affdb6ed24c486d581bd28d07b7f2fcb69540/numpy-2.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:8b4d2fd2d34e5f8c9235ee787de5631a37a28402b15cb80814df973d2be54129", size = 12566113, upload-time = "2026-10-10T20:03:32.612Z" }, + { url = "https://files.pythonhosted.org/packages/34/52/01d205e5e8ccb27b2b0b141e801f22b830198c979111b0fa44771438d9a9/numpy-2.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:bc39ac66a7a9a3fbd6134fda43136b60ffde99c8f4501e64e0d2b24da137babf", size = 10519523, upload-time = "2026-10-10T20:03:35.163Z" }, + { url = "https://files.pythonhosted.org/packages/99/ba/005cb5edd580d2f84d7ca3206b92dc17d4388e56e6f87ffe8f2762f83139/numpy-2.5.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c668b2f0d651605b58892644b0e302c7157f7159544227758c896982ef384b18", size = 17005499, upload-time = "2026-10-10T20:03:37.961Z" }, + { url = "https://files.pythonhosted.org/packages/f3/49/fee7587c33ee35f7977f9051d7f2023d4e7246d62710c80f20c2361ea232/numpy-2.5.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ffa6ce09a1c6a08e9667dd9c97aa0b14184e8d18f2a14b78b2a2328c9147f076", size = 12019666, upload-time = "2026-10-10T20:03:40.606Z" }, + { url = "https://files.pythonhosted.org/packages/d5/b2/c6ce165acffceb15a82c07b9cc77d391f86b3f379ba62911908ae5d34b91/numpy-2.5.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:956555e0603a4d38019ae6925711cb9dc43195c076a928accf7ea5d50bddfe53", size = 5455617, upload-time = "2026-10-10T20:03:43.138Z" }, + { url = "https://files.pythonhosted.org/packages/77/7f/dd85ce260a669a89be06842cf355d7353a33e6cfbc590fb8ebb947d88dc9/numpy-2.5.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2c2c4afffdeb7920e445028dd71eb932cac3e704792e964bc2a232426d4f1255", size = 6791932, upload-time = "2026-10-10T20:03:44.874Z" }, + { url = "https://files.pythonhosted.org/packages/63/d6/34b0a2b0741386a63025a65a2c09caaaaaad6d0ca95b66cd65c30dd7fcb5/numpy-2.5.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4054173604cd8658796053f1f3bc0befb68ec1c0762c57fdad61e199256a8617", size = 15710899, upload-time = "2026-10-10T20:03:46.839Z" }, + { url = "https://files.pythonhosted.org/packages/16/d5/928078d2b28f26829b138b4a6c3980045022fb409f570657a224ae60ef4e/numpy-2.5.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d549420b8858885cea8838a727842249218b9c1da24dd517e25c9c7a948310a3", size = 16721710, upload-time = "2026-10-10T20:03:49.489Z" }, + { url = "https://files.pythonhosted.org/packages/f9/cf/673fd1b8f4cd78eb6320e87ec4c90ac19c095644259e3749853a405c70f4/numpy-2.5.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:823874a507a84af050493b622affde94b6f7c3a0dc22cb2801381bc03b871c00", size = 17066182, upload-time = "2026-10-10T20:03:52.25Z" }, + { url = "https://files.pythonhosted.org/packages/f3/92/a77b5061b1b3e2643928c37976d79ee173e1b171ed158b7a3c61056b41bc/numpy-2.5.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4e263278bfb5ee6409db8aedbc4cc32973b1b82bc1e8d3c668551d04d83a7e37", size = 18480315, upload-time = "2026-10-10T20:03:55.39Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1d/1486ef3d3fb2279fd93c4c43c1bbbf1ca389a19816696684409f71babaab/numpy-2.5.4-cp314-cp314-win32.whl", hash = "sha256:cfd73180400042a7c532d30c5e287bdd03c59ff9ee1b4c0316af0539e29dfe23", size = 6185739, upload-time = "2026-10-10T20:03:58.186Z" }, + { url = "https://files.pythonhosted.org/packages/52/9a/e1e512ebc948d5b9dd33b08736760f0ebbed2848fd4eda1f553088a6dcee/numpy-2.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:2ca144f15135b6212a5c47b1e2aeca6e412f102f95a2d5d88d8aec77eb255de3", size = 12703552, upload-time = "2026-10-10T20:04:00.28Z" }, + { url = "https://files.pythonhosted.org/packages/2c/05/de709a982d7bbcd688a3fad71f002e9ff80c2db39e03ee726609b610f1d1/numpy-2.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:468397ba3c64427474706e5c9123fe266395496714dc684294eac75cd4930d1e", size = 10803901, upload-time = "2026-10-10T20:04:02.659Z" }, + { url = "https://files.pythonhosted.org/packages/13/34/083570ada3bb2a30fbe5d77c8c6fef9141144a15d33e6f793a67e9749ab8/numpy-2.5.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1ef3aa6d7e29bb13677323114280b05acc57607fa2300e66432d665d5418a162", size = 12138695, upload-time = "2026-10-10T20:04:05.012Z" }, + { url = "https://files.pythonhosted.org/packages/94/06/1f9c24db48eef0c2d1207e3b11fffb0478e39dfd8c1e1be7476936885eed/numpy-2.5.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:98b053943e5a0474ec0da309d2cb9d3f18ea57f8a2067c2ab7b5f763d1068380", size = 5574615, upload-time = "2026-10-10T20:04:07.316Z" }, + { url = "https://files.pythonhosted.org/packages/da/0f/593fba2e1560e949123bc7d2fc48b5893d56e58cd4bd5a273d2fbf60b220/numpy-2.5.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:b64a85f40e154983960a4167d4c1d57a50c7f109b3d3264a3a984154e90a8454", size = 6889383, upload-time = "2026-10-10T20:04:09.918Z" }, + { url = "https://files.pythonhosted.org/packages/eb/9f/b799dfdce4e05e80ed4bc815c71ff343a11533b2c0ffc221cae8538cda63/numpy-2.5.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a813ed7719bf45463c51779e6a98d0385fe905e48447526938a4b8337333d551", size = 15753763, upload-time = "2026-10-10T20:04:12.278Z" }, + { url = "https://files.pythonhosted.org/packages/34/88/16c5f12f86f5ad2817c4d103205131fc6c8acb3d1878af05a1a4f23ec859/numpy-2.5.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9b80cdf5cedba0e90d93fa5f9a333c4d65bd545cd669b71bb97ce2b703c9d73", size = 16757212, upload-time = "2026-10-10T20:04:14.799Z" }, + { url = "https://files.pythonhosted.org/packages/ff/4f/a1fe40e18a898e6a5089f4f0d891f0a493eb0574d5b34458f0fbe5aa3e5c/numpy-2.5.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2199ed071f460487c8db2c0e5c0b564494190edb4772fe80f9aad88b2604def5", size = 17116471, upload-time = "2026-10-10T20:04:17.58Z" }, + { url = "https://files.pythonhosted.org/packages/aa/46/e923a11c78e65c1722e7aaad817c06bd591324174b9d28ce5d31eee4d432/numpy-2.5.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:64f9c9878c1938476365e11ccfb6b770f3b9e5f045ccddc514235041e6959365", size = 18524063, upload-time = "2026-10-10T20:04:20.365Z" }, + { url = "https://files.pythonhosted.org/packages/5a/fa/84ab064514440c1f64a1b21088f2c82756defdd05e07c75ab233899565b2/numpy-2.5.4-cp314-cp314t-win32.whl", hash = "sha256:64d1c8ac28a4077cf987e0a71a7a0ef7e2df70722f07f0baa42dbb7eb6938647", size = 6340926, upload-time = "2026-10-10T20:04:22.865Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7e/6cd886876f435b10685db9b9f7eeb70356f99e052116f4e5f11c5792c714/numpy-2.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:067374eb538c34c745436365cf7b0112595c1d326f21ce4ff340f61230239fbb", size = 12901584, upload-time = "2026-10-10T20:04:24.99Z" }, + { url = "https://files.pythonhosted.org/packages/38/1b/3c1684f6a06f7307f2335fca6e486cb162847fb97e91d65f8eb5cabad213/numpy-2.5.4-cp314-cp314t-win_arm64.whl", hash = "sha256:e94aef2c639da4a960ad0db8e06471208d8589974953d78b61d345b4eb99e394", size = 10891152, upload-time = "2026-10-10T20:04:27.52Z" }, + { url = "https://files.pythonhosted.org/packages/08/f4/3224deff3af2bef6bc0b175369698d8cb348f3d91d9bb0286cd5c9eae9e0/numpy-2.5.4-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:8dddfbee2e68d26d0d7d7d9cb247b1fd4409241cce32d815a11d97ec2cfde179", size = 17003231, upload-time = "2026-10-10T20:04:30.021Z" }, + { url = "https://files.pythonhosted.org/packages/be/75/fee0b8c6d94b44b2fdfae74f6a4ad5a138739589a8aebaec28ce4e713ed5/numpy-2.5.4-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:81e3420b27048b65eb14c3acf0c174a8cb0e023277716110347d2dcb26026dad", size = 12018300, upload-time = "2026-10-10T20:04:32.519Z" }, + { url = "https://files.pythonhosted.org/packages/47/c0/d0b335a499a04b65f532c3f034346ef390f81299060f928492dabc1e0272/numpy-2.5.4-cp315-cp315-macosx_14_0_arm64.whl", hash = "sha256:0b4724a19de67bea8cfc4970798efa78bcbbe2ac2613cfac16721a42d44de2a5", size = 5454250, upload-time = "2026-10-10T20:04:34.943Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0e/461b3783c03d668052e6a21b01b673db6ffcb7831fd32d9aa5368c1cd426/numpy-2.5.4-cp315-cp315-macosx_14_0_x86_64.whl", hash = "sha256:2132418bf8dd124a427ca9e6a1daf9ee1a87185344c95119ceae868b99466da1", size = 6789644, upload-time = "2026-10-10T20:04:37.258Z" }, + { url = "https://files.pythonhosted.org/packages/b3/02/5dad269b02166965a7b4ca14adaddd75dbee0de42435bfecf561b84ba5a6/numpy-2.5.4-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:325518d4245b9e331387702aa58c2ce1dc4cdcbb41dfb4ccd5dcbc7e08db1266", size = 15704353, upload-time = "2026-10-10T20:04:39.616Z" }, + { url = "https://files.pythonhosted.org/packages/93/3a/01360c8036822ed9f7aa32189a77d1476567ec1e8e1383522389e4faac45/numpy-2.5.4-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56733449d2544178beaa4545cee357370440cf056c197f9c7bfb19dbfdd0e86d", size = 16718648, upload-time = "2026-10-10T20:04:42.383Z" }, + { url = "https://files.pythonhosted.org/packages/7d/5c/b863a2c093c4d6f21a597fcaf24ead0835c09ab16a8312d5a5a8868af683/numpy-2.5.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:5ec3753760c1a6d8bb91200666e545c3a9728e6269dfb5d6ce02340996698aa3", size = 17059053, upload-time = "2026-10-10T20:04:44.976Z" }, + { url = "https://files.pythonhosted.org/packages/0a/60/ced4f57f9a1258a0af74f17cb0b0c2700b5c67cd6678823c803b263e4df3/numpy-2.5.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:b1185012870173de7ae33d370bd45b1cf5baee747ea4b97036b65f4e93016877", size = 18477406, upload-time = "2026-10-10T20:04:47.863Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bd/0ef22dafaafcc7d4bb3ca26b8d2afbd55dedad8eaba99a8c864e1997456f/numpy-2.5.4-cp315-cp315-win32.whl", hash = "sha256:298eca75243f2cbbfdb460560b9fb2a1792a33cf2ab4286efd43d92e8d3df508", size = 6185133, upload-time = "2026-10-10T20:04:50.467Z" }, + { url = "https://files.pythonhosted.org/packages/50/bc/d2651b155ecc608a77e6f4d15495c11f14f19bb98f8bf0c5b0d38f86dda1/numpy-2.5.4-cp315-cp315-win_amd64.whl", hash = "sha256:332f3378fe077dd850e677ec01bdcc4f22368fb5d50ef10b2c79230b1bf5a592", size = 12703085, upload-time = "2026-10-10T20:04:52.63Z" }, + { url = "https://files.pythonhosted.org/packages/dc/d2/45e404f8abb26fb9eda12b94012936873e827b1be76f2ee7890be128312e/numpy-2.5.4-cp315-cp315-win_arm64.whl", hash = "sha256:d4cccbbc78717966f764cd3af4fb70276fa01fc7a2688af11c78901fa5c04f05", size = 10801451, upload-time = "2026-10-10T20:04:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/c6/c3/2ae14e09cfdb67dc187a342e15308a21c15bf4d2071f8079e6aee5fe56dc/numpy-2.5.4-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:950ea81d57ef070665581b6e1b5f6a029306423cd1739c5b95fe78aa30db6b9d", size = 17097121, upload-time = "2026-10-10T20:04:58.403Z" }, + { url = "https://files.pythonhosted.org/packages/f5/cf/305ae624ef8a039414317224abe9ec9c2fe7ea3c2e1cf204d43ff6b2ffb9/numpy-2.5.4-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:c05ede731b03fb1b7591faca9389ade3267d2bddf1ad8882bb3f2cc5e101694f", size = 12135439, upload-time = "2026-10-10T20:05:01.65Z" }, + { url = "https://files.pythonhosted.org/packages/a9/a8/f75c63813aef95827bb2c0d13b12803016853056e8792c280058cdbfe783/numpy-2.5.4-cp315-cp315t-macosx_14_0_arm64.whl", hash = "sha256:5fbf7141bbfd63aea22f435c9062a032b9ea0082fe9845dad7f021d3f1234e71", size = 5571451, upload-time = "2026-10-10T20:05:04.135Z" }, + { url = "https://files.pythonhosted.org/packages/6f/0f/f17763f983868b5c49b4101ebd7e00760bd1769478a6bb6a8de6e085bbac/numpy-2.5.4-cp315-cp315t-macosx_14_0_x86_64.whl", hash = "sha256:3573cd22564692a5b899ec344e5d5b9cc4576f2985b96f22af3564ed54f2710f", size = 6883356, upload-time = "2026-10-10T20:05:06.249Z" }, + { url = "https://files.pythonhosted.org/packages/67/a7/8af04c5a79e047996cfa38854dcfbececdd0343a7c933a46fdd03ef6f5da/numpy-2.5.4-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c109eac9cd439193678f69d70733c1108487546ca8eafc107b510ae10c1aecd", size = 15750991, upload-time = "2026-10-10T20:05:08.376Z" }, + { url = "https://files.pythonhosted.org/packages/57/7a/648254290d0c504faa8f2d07aa206660c728802c781a6f3fc68ab7cb5d71/numpy-2.5.4-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:80d6ef6e8620eb2c2b4c4caad50b5935d6db3cde2d51581b55dcc79e14016d1d", size = 16757675, upload-time = "2026-10-10T20:05:11.393Z" }, + { url = "https://files.pythonhosted.org/packages/b8/fe/4a8c3cdb0c70400cfe4c5bec42d3099a5673802a95064614b33e07b82aa1/numpy-2.5.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:77045a4b175bbf5316ec08003880804336c78f92281a1b72222b274ea85ec5ac", size = 17113846, upload-time = "2026-10-10T20:05:14.49Z" }, + { url = "https://files.pythonhosted.org/packages/1b/7e/619692bb67778702c0e9eb2d468568a7573f4e269386ea61aed01ee4e557/numpy-2.5.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:0f02a46e49cfb6c73bdb7aea1c0d3461dbae9aba613542b65f657cd3d17b9fab", size = 18522915, upload-time = "2026-10-10T20:05:17.33Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b5/4da41c328788f575838f97a098fe8ca691ebc6f6fd73ad4a262ee40b184d/numpy-2.5.4-cp315-cp315t-win32.whl", hash = "sha256:ad62a416ddcf863bf44bba76fbf6b53366ab0692e294f51cae4b5fbe0d246788", size = 6335804, upload-time = "2026-10-10T20:05:19.921Z" }, + { url = "https://files.pythonhosted.org/packages/98/94/6482ddfa3d312490cb9358f375bf2ad56427dbea8769187158e94d653753/numpy-2.5.4-cp315-cp315t-win_amd64.whl", hash = "sha256:38f47be9f74ab870d2633b5456ae519c43758a8d1fd05342f0ce4ecc034396ee", size = 12890095, upload-time = "2026-10-10T20:05:21.875Z" }, + { url = "https://files.pythonhosted.org/packages/48/7f/c2d1b436b6e7cfebac140c2579a298344b85f2991a2ce5c3615cefb29400/numpy-2.5.4-cp315-cp315t-win_arm64.whl", hash = "sha256:7a14a461d9340f1b46b8648578aed9cdb8b3b018a8fac6c1dde2c9192a01a87f", size = 10883718, upload-time = "2026-10-10T20:05:28.547Z" }, +] + +[[package]] +name = "packaging" +version = "26.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, +] + +[[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.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "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.4", 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/e2/17/d7b106e05bfa642e8694451e7d3d759c6a241c5386a5d962e4f66c047e06/pandas-3.0.6.tar.gz", hash = "sha256:66b07ef7315a31bfe1089cd3d71a7de781c9dca986762d0b4fe7c0ef17465d10", size = 4667686, upload-time = "2026-09-17T23:23:18.345Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/48/88e8d250d28efa8163294f6809a71683c7ee67f63ac7c33021c0503b3547/pandas-3.0.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:085e3786ae6b2e82b406266bce36690f72b9dc1421903ba9296b2981a9fcf586", size = 10391798, upload-time = "2026-09-17T23:20:20.96Z" }, + { url = "https://files.pythonhosted.org/packages/55/a6/39db5d41f3eb5d7846626312cb30e0cea48e988fc47a3725698bc931ad3c/pandas-3.0.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d7564d86a94c2eb8ab290b07f63ddaae5c032fa53897c29a2ff2197d43aee8af", size = 10023718, upload-time = "2026-09-17T23:20:25.094Z" }, + { url = "https://files.pythonhosted.org/packages/54/b7/707e966129f77ee8a39d41a41bbd989b790b3fef581c6e26b821315cba6b/pandas-3.0.6-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7c0afdcaf6661d795fcefc2f647ddd1136f62cdc153fba177c685d97a87808", size = 10612800, upload-time = "2026-09-17T23:20:27.99Z" }, + { url = "https://files.pythonhosted.org/packages/63/be/dfb6cc9329d0bbe76dadda8a1113d026bb996ec76c509221368dc68349f4/pandas-3.0.6-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47121f9571503f724c9b93e297ab6254ac99c77adf5e9ed085ea419fd585c258", size = 11108900, upload-time = "2026-09-17T23:20:30.65Z" }, + { url = "https://files.pythonhosted.org/packages/1a/6f/3d58f15bbe972f3d7bfa13ee06d731785a76fe8d232c92b2655a8de14127/pandas-3.0.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:994a79608263fe1c14cc48ffa7300e2b834b7d1cb406ffe96a08828cb0cdd79b", size = 11630226, upload-time = "2026-09-17T23:20:33.582Z" }, + { url = "https://files.pythonhosted.org/packages/58/54/9b494de4a3dd92fc6eb19187db1b21afb50fdc21962f32ea1aca9f270cb2/pandas-3.0.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a3a22e07fe75347eaacc75b0e85297947af4fba6b4aae23916bd8b6828d0bba3", size = 12146942, upload-time = "2026-09-17T23:20:36.792Z" }, + { url = "https://files.pythonhosted.org/packages/d3/dc/d2df02854aec5d47659acfb2be352eecc691845b2f86e99c84f1010a8671/pandas-3.0.6-cp311-cp311-win_amd64.whl", hash = "sha256:2e5fa32ff162dfdbc280157d664f44d23049ae414725af9676df339c501d82cd", size = 9859246, upload-time = "2026-09-17T23:20:43.976Z" }, + { url = "https://files.pythonhosted.org/packages/79/1e/2a30df0d7dede5c195300a1820b0bc21cea3aa24e4c8b6c4431565ecc79a/pandas-3.0.6-cp311-cp311-win_arm64.whl", hash = "sha256:5e75072773c1b2f7cb63faa3a6f562aede11f3976f68ed34cb538bc091a28171", size = 9106923, upload-time = "2026-09-17T23:20:46.611Z" }, + { url = "https://files.pythonhosted.org/packages/77/4c/597d588c055d4373cff19cbbc32d4dd046c7be8fadee957585b5ba9e5b24/pandas-3.0.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7dac2d65e9087e8e7b5a45fe15c4920911a221df061ab629943ce016489145c7", size = 10397411, upload-time = "2026-09-17T23:20:49.465Z" }, + { url = "https://files.pythonhosted.org/packages/18/8f/48907c7c707b61a8e5018c32e1a3f70623209bfb59020a2f6196159d3aa7/pandas-3.0.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9dab635a549e58a053c7b0fa054dc0bd7be22f0ed9a720f4a85d5fb993276172", size = 10051279, upload-time = "2026-09-17T23:20:52.409Z" }, + { url = "https://files.pythonhosted.org/packages/67/fa/613d867c3d9554a61bafdec6f79565c8a3e73235feb52cc4a72ad2e0fa6a/pandas-3.0.6-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3dccb584123b399c07562ac4d62543e90ede49ddf8ce3c13ffc64cbe828c281", size = 10303429, upload-time = "2026-09-17T23:20:55.597Z" }, + { url = "https://files.pythonhosted.org/packages/cb/67/0c0f18e38d7f2d2af8c24b3315bc4046e73bbdd4a5540405506671ad0c0d/pandas-3.0.6-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0704044b676496b8350e023b09f174a26772456c974a2b11c36bebb558c9490d", size = 10788193, upload-time = "2026-09-17T23:20:58.617Z" }, + { url = "https://files.pythonhosted.org/packages/39/53/1b57f3162501fe36687e4870e1b918a6458ca7386b173af75663ace95857/pandas-3.0.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e7c1905ef02c3d6d43d9dbd5b6ccb4da4870a0b0c821bbc103fbdb6f3ad2707b", size = 11323501, upload-time = "2026-09-17T23:21:01.911Z" }, + { url = "https://files.pythonhosted.org/packages/f2/d2/b1182e8d39100369d7f13f4a125a3fb6b096fef112c46d0566c25781ff68/pandas-3.0.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:569e114072b24fc4970c12e2b4bab252671668a40b324318903380cab0254c0c", size = 11838173, upload-time = "2026-09-17T23:21:04.85Z" }, + { url = "https://files.pythonhosted.org/packages/c7/33/5b717af24d2f27995e51e0875a269dddd373045216e34cf62e3aa764eaa1/pandas-3.0.6-cp312-cp312-pyemscripten_2024_0_wasm32.whl", hash = "sha256:2a8fc94be2ee5f1d86f97aacd8cc566f81680b6498e76f3007421bb5d98151bf", size = 7118499, upload-time = "2026-09-17T23:21:07.661Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2a/14b3b17cd75cef4a1ee1af4234eb41cc1afe4103b98c2d80e8916abfd42b/pandas-3.0.6-cp312-cp312-win_amd64.whl", hash = "sha256:3ef908d28590b3f42d7070e7ad8f9b34b442b260b7f3c1afb57e0040c58cdb1b", size = 9661439, upload-time = "2026-09-17T23:21:10.959Z" }, + { url = "https://files.pythonhosted.org/packages/3b/11/3d580a604a1e35d69f6676847bd12db7d14344bc677b717f4413e79c5d0d/pandas-3.0.6-cp312-cp312-win_arm64.whl", hash = "sha256:f4e7c52eb108d752e7592268108fd3e98efd76d83a3125cdd06c621c2e44359b", size = 8959806, upload-time = "2026-09-17T23:21:13.851Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1c/143605a1f6443ad50ebda78a31e5a3a10147fec2590e931584aaa5ff0a09/pandas-3.0.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ae8073aed8e21d1a7fe263dcdc6840743549722a6738198a0a46000fa9476f2", size = 10418900, upload-time = "2026-09-17T23:21:16.594Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ca/87f8548f73d452aab35e4a90f8b39ae303295e0f2ef0b4055c44d6b3f1be/pandas-3.0.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:60d81f9e1799b36f3739e7fff44d1fbb2e8fd5a271b3863e03de9715fccda0fa", size = 10064785, upload-time = "2026-09-17T23:21:19.677Z" }, + { url = "https://files.pythonhosted.org/packages/43/1a/d951442e5607c6e3b2462eff8f420797d428aa74b87c6ecfe4f48553626e/pandas-3.0.6-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:097090508a1dd335013d39106fc10b20f4fd4a171638e47b77d55798ed9dab6c", size = 10245290, upload-time = "2026-09-17T23:21:22.797Z" }, + { url = "https://files.pythonhosted.org/packages/50/fa/96d50e1e6cd0b08b5e2b7c838f65ae644940f75a124063380b5ef73b6866/pandas-3.0.6-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e92d9fa834c7d877130027cddc0cad8dcff97c1f6cca26bd6310f847228b658", size = 10757657, upload-time = "2026-09-17T23:21:25.673Z" }, + { url = "https://files.pythonhosted.org/packages/7b/12/f82d13a2cb703e1a8acee7e01fdc2b898d9cd0c00f07d1dfce63af43e350/pandas-3.0.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b27c8d890e4aa2171437ae2a39de1d215e674158e4865c4023a8b31c932513b2", size = 11249114, upload-time = "2026-09-17T23:21:28.898Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ce/8aef2e561a2f2c8b38c913c67373c65ba6748174e763d27c80271b24bd17/pandas-3.0.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8029ec0f1f89e4f985929ce1f6626dabf3140d61a4e9c1215afdab34eaf9a5d", size = 11820511, upload-time = "2026-09-17T23:21:32.11Z" }, + { url = "https://files.pythonhosted.org/packages/c0/bd/63cb67e6903ef6d9c2871916dbcbc09d254da0fe8b870cf62e16b21945f2/pandas-3.0.6-cp313-cp313-win_amd64.whl", hash = "sha256:f3ce8a6968045481e91a3990e797e348ce13db45ee164a7095bbc824e26c09dd", size = 9638092, upload-time = "2026-09-17T23:21:34.883Z" }, + { url = "https://files.pythonhosted.org/packages/75/2e/e7b35b712edb068d382ddc8b2bea8a04974100515ba2daa22b478b265842/pandas-3.0.6-cp313-cp313-win_arm64.whl", hash = "sha256:cc39303913e2ea129915670de5d1c9fbd647f543bb72e5543bac8baa94e9e42f", size = 8952032, upload-time = "2026-09-17T23:21:37.729Z" }, + { url = "https://files.pythonhosted.org/packages/75/55/1a8875395b05ccd572cbca0b9255dcd2db6e6508e632a558c1a6884b39ad/pandas-3.0.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ee913a91669056c1de1a6b733fbfeab711de9e54e3bee2dfa5fe79d9457247d1", size = 10492213, upload-time = "2026-09-17T23:21:40.746Z" }, + { url = "https://files.pythonhosted.org/packages/35/61/47ae13476995cc8a40cd609e93e7cf11f273d8692925c2903cb6d38aa0d1/pandas-3.0.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ff51a4459ed036e93d1eb1bb5e6e7b28685d3cb6b7c12b91c05b31024e234729", size = 10156618, upload-time = "2026-09-17T23:21:44.142Z" }, + { url = "https://files.pythonhosted.org/packages/bc/f2/cc5f2adb8d6e86a85d9fb5128f8cf205a61189336f70d1f7faf0d1b53ec9/pandas-3.0.6-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:654aae059295dbba6ecd2328ca12712a2cf1676214c8699f1c29213f7ccf9c34", size = 10375479, upload-time = "2026-09-17T23:21:47.159Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ba/ffdcb19be4ff6bfe7d969e7cef2c567c633df5a3a1cc1053394ad053bca8/pandas-3.0.6-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f51d7f651c8054c5e82a69265c98082e795d1442df7ca6edc3a545d61214b1", size = 10783244, upload-time = "2026-09-17T23:21:50.367Z" }, + { url = "https://files.pythonhosted.org/packages/77/5b/e150075b2c6eb69fae896f2d9239bc6ed07db97735971d53d66de6553460/pandas-3.0.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:22172a92e7ee678ec0140c7af4fc9366b55413834a1cd86af78b3caa0b0574de", size = 11382223, upload-time = "2026-09-17T23:21:53.355Z" }, + { url = "https://files.pythonhosted.org/packages/d6/8a/b441c587dc7355bf6e1f68a91b4f76a6c29740f0c23be3acc5d4ebbeea6d/pandas-3.0.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:583be68728a31d0d750d5b8d9e00f02b153df0d4655f858bde93cb84cfc4227c", size = 11854881, upload-time = "2026-09-17T23:21:56.342Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e9/f43410fada510b43fec09993c08f552086c3d247d3ee801a678f3cb10ea5/pandas-3.0.6-cp314-cp314-win_amd64.whl", hash = "sha256:77ccbe5057aece6fc172b9b77f19c04335af6882bc2e10c8f3ee4e6bfb3da553", size = 9791672, upload-time = "2026-09-17T23:21:59.332Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9e/db14c059c21f9baa1907d436f8bf30e0c76c6288225c5e8b79a08ba8b2c5/pandas-3.0.6-cp314-cp314-win_arm64.whl", hash = "sha256:fb625f426b375bcc96e3a04c5d5d266cd7be6ae5d6866e0e703382ab5164068c", size = 9121246, upload-time = "2026-09-17T23:22:02.123Z" }, + { url = "https://files.pythonhosted.org/packages/67/ba/bad0f8dac020ab38a8637fddab01a57a82da7a496a6e6f19590aad53ab62/pandas-3.0.6-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9e492cd4bdba6778de4fe0df7f4590c012161ebcf9902dce01b01dc683105514", size = 10920612, upload-time = "2026-09-17T23:22:05.404Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a9/b500982e9aac6d52a58da4ad3f11e14168a315b06906b3f397c427878065/pandas-3.0.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d7dcd21238cbb4828ff148481ba01cac8946dc5121457b5aeba28636f8f99a60", size = 10570213, upload-time = "2026-09-17T23:22:08.44Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/e6ecd0073c98be8f840ac3125b955272835d7d9fd69f5944383b164deb5e/pandas-3.0.6-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ff482fa91fa2bafd92e8fe66ce3645c851824310f295c1f0a2f96e928fc4541", size = 10252356, upload-time = "2026-09-17T23:22:11.302Z" }, + { url = "https://files.pythonhosted.org/packages/04/f5/001e230a7a7803590d9275a1a3f7e1bb605e3a495cfe5e8d3a532090621b/pandas-3.0.6-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:db7ec631f26223beee8e5c9e0b8f23c24d8197bbd1d982421d4e3188bea51965", size = 10655952, upload-time = "2026-09-17T23:22:14.283Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ab/bab587148a3852c96aae26c4b5f9e04ce2221801ad94e166b4fbf969ede0/pandas-3.0.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd75ed0c840f709fc2ae26ddd9534ac77ca1a48ac0cce521a74acaa85f3340a7", size = 11274177, upload-time = "2026-09-17T23:22:17.352Z" }, + { url = "https://files.pythonhosted.org/packages/f3/32/74b48d87df2b80892d713c149abfe36d5db4de41b4eccb042a2bc07dafc1/pandas-3.0.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ef738d71d1059245b6bb03e312be06d8b3821326a83486c1ad03b9aba3710e44", size = 11719041, upload-time = "2026-09-17T23:22:20.227Z" }, + { url = "https://files.pythonhosted.org/packages/f6/c6/d64b72d64d7eb0fad9fe424d34138e45dee70ddbea1360dcd0adf30e28f6/pandas-3.0.6-cp314-cp314t-win_amd64.whl", hash = "sha256:429d9df32731ab01383ed98f2baa7a60368090d1a94fc06019a12062510e8630", size = 10191388, upload-time = "2026-09-17T23:22:23.524Z" }, + { url = "https://files.pythonhosted.org/packages/7a/30/5e5b2ccabeca73ae2b03fc82bca3eabb7466cf43737f05ac08d591665d47/pandas-3.0.6-cp314-cp314t-win_arm64.whl", hash = "sha256:a4dbd4dc65cbe645b92b8785d0f96dd7311010dc6606cf620e51b07b8788a12a", size = 9387796, upload-time = "2026-09-17T23:22:26.64Z" }, + { url = "https://files.pythonhosted.org/packages/b6/77/47c5fb0be8bdd00116814c2c40d9ec42dbeb943865ef95130fe58a898226/pandas-3.0.6-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:50c44cbf5820b6b91a5f74aae04972472aefadd3cd9fbd1010409d85528bd570", size = 10483960, upload-time = "2026-09-17T23:22:30.071Z" }, + { url = "https://files.pythonhosted.org/packages/09/08/a310cb2fefe6d2b4623ab2150da818d93d4e73e33b58e4d163bb74243c5a/pandas-3.0.6-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:eb6900de08ac85f93ac4948aa6b80842eba555875337b8359035ac9c43e92d34", size = 10149432, upload-time = "2026-09-17T23:22:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/54/36/6af478ec3a26d7754555cd62c3101c589c0931b1d85398e4fa5403910a1c/pandas-3.0.6-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e25e2e1adee99ddfada6f7206a79ae8e9c8a8861b0e3eaaba165006d3eef18e", size = 10360981, upload-time = "2026-09-17T23:22:35.621Z" }, + { url = "https://files.pythonhosted.org/packages/43/20/5ece0e9cb79a6473216620db6a90546f578001c2bd857b77c444b27acad1/pandas-3.0.6-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4ff44b2cb51cbd691c91f92c4ea6c71e34003f239ebd67c2e857dc898466b49c", size = 10776585, upload-time = "2026-09-17T23:22:38.427Z" }, + { url = "https://files.pythonhosted.org/packages/2f/b6/cd3038f31ade5e8d2b4e1c9549d4b31e4d598469562f47142b2ad9171c0a/pandas-3.0.6-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:5edd0a7abb0986ecce1ac81f56d99b6763f86aa6946dceb6c661224f90af5a19", size = 11385249, upload-time = "2026-09-17T23:22:41.18Z" }, + { url = "https://files.pythonhosted.org/packages/0a/87/05bb3003737f80375d7311774916a24d161e2e591abe8c672aed7813defc/pandas-3.0.6-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:1bcb3e9ed29e74a7439cedff9e2aefd3ea65de84d7de9ccb6c194192541bd60e", size = 11848595, upload-time = "2026-09-17T23:22:44.207Z" }, + { url = "https://files.pythonhosted.org/packages/c0/30/1c0d46acf236d19ef975e9cdd5d1e0dd54924b03f0ed8287096ad4a18152/pandas-3.0.6-cp315-cp315-win_amd64.whl", hash = "sha256:253e12cb9081b0afbac607920f6142975966bc315135e09de275fdbaa415d2de", size = 9783810, upload-time = "2026-09-17T23:22:47.097Z" }, + { url = "https://files.pythonhosted.org/packages/87/03/df3304a9c2833c4810e7f1c887b04105b24731f9deef8b5d5d04522a375b/pandas-3.0.6-cp315-cp315-win_arm64.whl", hash = "sha256:97274c9adf6255bb48c620cd6959805efa7f09ea2167f0e0ae006a448cd2fca7", size = 9116820, upload-time = "2026-09-17T23:22:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/47/25/d5f6cce5efa17c38e4f752a875b4a26d66cfadd529cb8c81672f0376d6a6/pandas-3.0.6-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:265f562fdd1079f69f3de96dd425c3405224038c0af4f920c54bd240ee2c4640", size = 10894614, upload-time = "2026-09-17T23:22:53.223Z" }, + { url = "https://files.pythonhosted.org/packages/ea/8b/e1876bfdc1df06bafc022d33202b5663bd86c81df2a6140aacafd0344669/pandas-3.0.6-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:c6e4aae3e9bea26c6c9a20d88d96c86ec4a99b4db5fd516bcb4e829ab2c0ee36", size = 10546973, upload-time = "2026-09-17T23:22:56.155Z" }, + { url = "https://files.pythonhosted.org/packages/e9/27/e0a27a5a5c27f7db66657b44def9e93121fd0ad4f0808355b9898fcbf204/pandas-3.0.6-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a77a1a44e4d88f1c6a2a64d3eb12efec8420875722e14279800b173a7c7c2804", size = 10239717, upload-time = "2026-09-17T23:22:59.603Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4f/4eadb7d86a921c1e8bc70916cfe601ed667c4169118d17d69958611c21f8/pandas-3.0.6-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86fa853a12e0b70927e2b1ee00d56d2224ec9cbb4b9d58348b5ad52d2f21150e", size = 10641275, upload-time = "2026-09-17T23:23:02.81Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d1/eba72e9d905e84e79aefeefdcc6bc1abe15d9077973566643f4211636966/pandas-3.0.6-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:c826e9babb7790142c399f58599d8de679bea059d7b39c5b6efa2096fac37266", size = 11262919, upload-time = "2026-09-17T23:23:06.038Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8a/1c5bd2642b450e374b6191189f47c49538fe41f82348a66de6f647e6ab59/pandas-3.0.6-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:8fe77b408d82e2615674dfed62533b95e18a03610573877422aada4f625d4947", size = 11709148, upload-time = "2026-09-17T23:23:09.082Z" }, + { url = "https://files.pythonhosted.org/packages/73/3d/1b142bd0d0f1326a5d98c91c955b923cd1e06b5eecb428cd9d8817fa01c0/pandas-3.0.6-cp315-cp315t-win_amd64.whl", hash = "sha256:83e91d15738d7783c050197cef2f2cf82fc6353dae9865aa87ed1fa16aa4d55a", size = 10161518, upload-time = "2026-09-17T23:23:12.365Z" }, + { url = "https://files.pythonhosted.org/packages/0b/a3/6419c14da2adc1f09a6a183b8f91d7494d325b287f4ca984ac04f663638a/pandas-3.0.6-cp315-cp315t-win_arm64.whl", hash = "sha256:963ca21199097a84c7827c4678b04e30833084fbf8ef44fde3fa7180a29f8fa0", size = 9364529, upload-time = "2026-09-17T23:23:15.274Z" }, +] + +[[package]] +name = "pluggy" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/db/7fc19e6f2dc92a966727031389fc2e08b558f0f25eb7403c1119ad4713cd/pluggy-1.7.0.tar.gz", hash = "sha256:d1eaa46ebb595891b860ab086b4d09c8588af65ebd4361b8e8f4bb8920b90ba8", size = 123304, upload-time = "2026-10-15T09:50:58.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/9e/2b38731e0fc536806f16490e1a12d7f0dc2a1235aa8cc07bcc75416a7daa/pluggy-1.7.0-py3-none-any.whl", hash = "sha256:7dd7b0d8832ba3cb632c306926ded123429211b83641b35dc5c41ad2d34f9bec", size = 27082, upload-time = "2026-10-15T09:50:56.808Z" }, +] + +[[package]] +name = "pse-sdk" +version = "1.5.1" +source = { editable = "." } +dependencies = [ + { name = "requests" }, +] + +[package.optional-dependencies] +pandas = [ + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "pandas", version = "3.0.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "pandas", version = "3.0.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pytest" }, + { name = "responses" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "pandas", marker = "extra == 'pandas'", specifier = ">=1.5" }, + { name = "requests", specifier = ">=2.28" }, +] +provides-extras = ["pandas"] + +[package.metadata.requires-dev] +dev = [ + { name = "pandas", specifier = ">=1.5" }, + { name = "pytest" }, + { name = "responses" }, + { name = "ruff" }, +] + +[[package]] +name = "pygments" +version = "2.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, +] + +[[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 = "pytz" +version = "2026.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/14/21/d83d6ef28c4c912c4bb4d1dcf591f7b8c6bde87b9c66f9f454677314e16d/pytz-2026.5.tar.gz", hash = "sha256:fa23724b9c486543b9ff54a327ee7569ac83ade54bb9afd0fc18676620401c86", size = 318572, upload-time = "2026-10-04T02:37:58.719Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/ef/c66110d46fb800dda0bf33164182dfadabe26a90e4476844d502a23dca8e/pytz-2026.5-py2.py3-none-any.whl", hash = "sha256:e658af3757f9e26a9d25dd2aff38335acd92bc9104f890a894b2c1ba28311b03", size = 506342, upload-time = "2026-10-04T02:37:56.814Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "responses" +version = "0.26.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, + { name = "requests" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/47/f216a33221db8eff328987661cf18371afee89c62a62b434b963d6b509c9/responses-0.26.3.tar.gz", hash = "sha256:b0c11ca8131b8b227b8d5108e6ed39772222bd5aab030ed430e8f99057c4c409", size = 86335, upload-time = "2026-08-26T19:17:24.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/86/ca7958de70cb0752350575e98229368a3a2f746a2942034b3364e17312bb/responses-0.26.3-py3-none-any.whl", hash = "sha256:74474f799334ac4f37d93b6437ecc3bb1bb5c77a8d31780a338643be2dce0af8", size = 36289, upload-time = "2026-08-26T19:17:23.176Z" }, +] + +[[package]] +name = "ruff" +version = "0.17.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e7/e6d5749983b020e6cb1055abec0a6f361a8e94f0184cdcf56d4760995f02/ruff-0.17.1.tar.gz", hash = "sha256:5bb796c5112e9fb9527f2ef2faa0a130d6312d19de92bcfaddd70dbb2d41020f", size = 5323349, upload-time = "2026-10-15T15:23:18.64Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/78/90c46ca6250709f7c37e397b60d06061adee8d0f7d13959ed6a8e49b14fd/ruff-0.17.1-py3-none-linux_armv6l.whl", hash = "sha256:1796c41eafec66f4635125dd1e074a7aa27b8d20c187d484360acf11d247fc4a", size = 10147528, upload-time = "2026-10-15T15:22:33.667Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d2/a2e28bc35b083ae51fa07dfba5280a667c8b01ecaee7802fc8849dcd5c46/ruff-0.17.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8eadb09a788010be29c895418bdfa513e4e1c65e64b94e5b19e4cd381f6cc7fb", size = 10225974, upload-time = "2026-10-15T15:22:36.328Z" }, + { url = "https://files.pythonhosted.org/packages/8e/71/7a9029a81c8206ee01a98f6cfeeb56e6c095af86857581a37045c46d7e94/ruff-0.17.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:29b454f18ca3eeaf37fbf140841dcf27d36109e29dee9aae8acecb68c26c6f74", size = 9270361, upload-time = "2026-10-15T15:22:38.651Z" }, + { url = "https://files.pythonhosted.org/packages/c0/8c/e075e9c917cb0575297880c96768304e881240171c870cbd8d9b7b54bec2/ruff-0.17.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:872e03dd231744d6c5ec5c8af6be52b71323fc6ee096e4c8b34d9f844ee3b978", size = 9938300, upload-time = "2026-10-15T15:22:41.284Z" }, + { url = "https://files.pythonhosted.org/packages/87/27/0b4bb926a49de2383f161272b4d863571a4c7ecdbf595a6bc58813a13367/ruff-0.17.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4c0d6cb5f8fbac77eec9af49f4fc0eaf856b05545e372b0694d24b4e76b1c640", size = 9948464, upload-time = "2026-10-15T15:22:43.681Z" }, + { url = "https://files.pythonhosted.org/packages/14/27/518d57e0e02aecb8ea4acac6b57b51a53173d25fa734952bfdd7d531e88e/ruff-0.17.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78beced4a3c94dfedbee4b999b95b6a27b35820fdc0afbacc6d37a8da26c1cd1", size = 10638639, upload-time = "2026-10-15T15:22:46.081Z" }, + { url = "https://files.pythonhosted.org/packages/01/82/d89a4e498ade59e6cee52d67f8da569019372212b87468c1fdeae884601c/ruff-0.17.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7feefc63921202894f0e158b0e32f45a5950d70a7fb693ccb9264816c25b1c05", size = 11337238, upload-time = "2026-10-15T15:22:48.894Z" }, + { url = "https://files.pythonhosted.org/packages/a2/7c/3b0fdef9352656317c977086fe1d802902406e4f2a0d8578da90c0e6d1c5/ruff-0.17.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:684154861a819bf1f433669d2a0ce9c3e54677c65b8ebb866cc2a7ec86345785", size = 10896327, upload-time = "2026-10-15T15:22:51.45Z" }, + { url = "https://files.pythonhosted.org/packages/62/86/5566290219708ce2928f3a29c4f843a1185b341ec6111c4631a33c5ceced/ruff-0.17.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6ef98e51de8ef38082f90653ceeb84944bd7673783eea13e1647c5b9b064189", size = 10350811, upload-time = "2026-10-15T15:22:54.401Z" }, + { url = "https://files.pythonhosted.org/packages/ad/d2/ca8462dabb3c413990d49703ba306c30c862195a75448d080fa8fcce9775/ruff-0.17.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:8f9aa5b5bd5fa1937803ea609c8f153b8de39bf3e0e149897ae34b0c5b52f467", size = 10569994, upload-time = "2026-10-15T15:22:57.142Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ad/dd1864c1f1db17c8cfb297ce70b352388e6977620f66f7182c52855407df/ruff-0.17.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:80e53b0e5faa3c5667f035daaa032893ae42b7cfe708c886d39ed131e54688ab", size = 10059672, upload-time = "2026-10-15T15:23:00.084Z" }, + { url = "https://files.pythonhosted.org/packages/65/45/3f56cf1d2168c2165cdd5ac6f9668aee66a4cfdd4caf2b2cc5ccb2b1d568/ruff-0.17.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:056c5d5fa0ac790051ae165d7035c7bf956d02de2db557f2e9b31d877a505fca", size = 9947803, upload-time = "2026-10-15T15:23:03.132Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4b/5457cb2be4c1d1ea5e919558131a0f197a4a454a0530d9e30820e8206e4c/ruff-0.17.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bd8f5a55b10f371642eacd0e8665a42a8b97da15c9a7187c79e697b77b5e0ce4", size = 10351807, upload-time = "2026-10-15T15:23:05.628Z" }, + { url = "https://files.pythonhosted.org/packages/49/fb/db15b4f8fa24a1e36b99e1401f42ef6df16ee299a48670d5d262b771e7d7/ruff-0.17.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ce05e6f3973611d9550a600e04bb531061e368baba31610eb57c1509a8f21a0a", size = 10765716, upload-time = "2026-10-15T15:23:08.092Z" }, + { url = "https://files.pythonhosted.org/packages/0a/e8/1e186578076aff7a80735f625005fde678412ab609cd323ed182eb7d6b87/ruff-0.17.1-py3-none-win32.whl", hash = "sha256:1de0030a8aa78c65c5fecb84b2e5c390ac684c0452875a02afc7c083d49a496a", size = 9895093, upload-time = "2026-10-15T15:23:10.582Z" }, + { url = "https://files.pythonhosted.org/packages/6a/2e/da3d9589bae8e8b58f3a48bd5bc0b980232fb14a83bf02c3be7660f87c1b/ruff-0.17.1-py3-none-win_amd64.whl", hash = "sha256:5df76f16580fab58d0f6c98a3ac254a4abceb7b2489b8b678335869cff77b3d7", size = 10097955, upload-time = "2026-10-15T15:23:13.309Z" }, + { url = "https://files.pythonhosted.org/packages/df/c9/0f2a4dea06a5e12c1191ee3d96b1e1233797d0d5e18f97a2b909a95a9836/ruff-0.17.1-py3-none-win_arm64.whl", hash = "sha256:42400b9a6ff8515ff27796f07f92e0195be011c86e81e381606f58ea5502fef4", size = 9853542, upload-time = "2026-10-15T15:23:16.05Z" }, +] + +[[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 = "tomli" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/78/9ad63712633ed3ab5cc1a648d863d7e7da371e9425e209555a0fe711b695/tomli-2.5.0.tar.gz", hash = "sha256:264507556cd8b8c8e7c6ee037cdf443a463f03f4c958e57195e3d369711b8ff6", size = 17662, upload-time = "2026-10-07T12:23:37.892Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/ab99b60ee52acd949684febabc3005d0045d0f66bebd9cdebd67372d26dd/tomli-2.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c4dc1c1781f2f716de763d1e9a7b34c6a894e167e291c7c5d16c72f7a9538545", size = 163901, upload-time = "2026-10-07T12:22:15.601Z" }, + { url = "https://files.pythonhosted.org/packages/bc/00/ee01b7ed4579180fff07142d290257f25ba786f23f3ec6005f620933c2f5/tomli-2.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eff8babca5a7999bc137acbc7482a8b7e17ffca5075ab41f5d770ab408c7bfef", size = 163756, upload-time = "2026-10-07T12:22:16.957Z" }, + { url = "https://files.pythonhosted.org/packages/72/c2/4efebf65372f6583185f79799312109dddb61102d47e5c33dcfd1a297aca/tomli-2.5.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:86665cee9c4835b7a7f1e8ec2c719b5258d4dc782887aded5a8ae7352a96843b", size = 268038, upload-time = "2026-10-07T12:22:18.135Z" }, + { url = "https://files.pythonhosted.org/packages/53/07/5850468e925d898abb36038666f9c333a94d2a223e802a8ba5b6d319d23f/tomli-2.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e369fd63331746182360977b1892bfc215476a30d61612d732425311639f56", size = 276422, upload-time = "2026-10-07T12:22:19.567Z" }, + { url = "https://files.pythonhosted.org/packages/b4/87/f293984cdcf83c054196d4fd3dad44fc68ae55b4b8c44bc76cef360c3150/tomli-2.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7ad1ea345759240d6463efa0ed1c704402752e49aa21476620738d74d72d8aa1", size = 272616, upload-time = "2026-10-07T12:22:20.794Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ce/db582886b3c1219d3fec93ebd669332482e5aee7a91e0f7838d84f2d1759/tomli-2.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:96243987194634bd411066ce40c952e108f86af04db533ecd8ac3ff2a85b1885", size = 276593, upload-time = "2026-10-07T12:22:22.12Z" }, + { url = "https://files.pythonhosted.org/packages/bf/72/7619b87dea4261fc27dd7b54c4461c129c1f7d9bb7ba3aec89c797a431b8/tomli-2.5.0-cp311-cp311-win32.whl", hash = "sha256:610b27d99f28ec5f191c7064a48f3ddb179a1fe6ca73d571483ae859f57b605e", size = 101830, upload-time = "2026-10-07T12:22:23.651Z" }, + { url = "https://files.pythonhosted.org/packages/1e/74/220106da34502304b6751a2a9b8a9fbca6c3fd47e737a2e2e3da7c61c9db/tomli-2.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:c804ae44fe7b4bab5da295e4f980a1ff04670bca9d23fe0a4e887e08ebd741a8", size = 112742, upload-time = "2026-10-07T12:22:24.972Z" }, + { url = "https://files.pythonhosted.org/packages/27/99/7d9c8b41837a7773613e169504147375c157a290167aa59ad74a085f521f/tomli-2.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:cfac177ebd6236003846ea339981f71457cb6eb748f23381eb257e45092e3980", size = 109332, upload-time = "2026-10-07T12:22:26.117Z" }, + { url = "https://files.pythonhosted.org/packages/52/ed/7baa86f87493646a594de388c7c1c40a39dd0461f7e9c0359cbeefc91fe8/tomli-2.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f4a40d03fb9f63424f0979855bdeaf44dd7696b8d59501822c10ed30ba532df", size = 164854, upload-time = "2026-10-07T12:22:27.444Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b1/44c0341f2224397855723c7a8a39f718ea6fcbcc3dacc66e5aeca0f334e3/tomli-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9ebf8d19b17bd0daeb7b7dec81a946a439b753942fd0210d6e96c532249eea6b", size = 164074, upload-time = "2026-10-07T12:22:28.679Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e2d5b7d3fba47adedb23de616c16d428ea076c79a3d8e1d95d649ffe197e/tomli-2.5.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf0b5e8e0f68ebb494356e577c06c139161efd8d3b9050f93b39b7c26cc54ff0", size = 274274, upload-time = "2026-10-07T12:22:29.804Z" }, + { url = "https://files.pythonhosted.org/packages/43/90/6090e706ff27a6f89f4a40578e3324b95c3cd8c4150868aabf33a8f414c3/tomli-2.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cf74416bdc94ae458b14e37286c1073081850ac8459a00d0c5efef5d44294c6", size = 286435, upload-time = "2026-10-07T12:22:31.297Z" }, + { url = "https://files.pythonhosted.org/packages/0a/9e/a2c40768df16c408f22430afb0a73e9d7e5f79c950884954649d1146b74d/tomli-2.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:61ea1ebe1e55a34ea8199cc8dbff398d35027b82271c8ac4802fd3a1fd5b1bcc", size = 278119, upload-time = "2026-10-07T12:22:32.601Z" }, + { url = "https://files.pythonhosted.org/packages/12/25/3c0cb485b98e9cfac495629b1c93c87ccf0b72fbe9d2689fd8fe62c6d5a3/tomli-2.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed53f7e89bb04f6d9e8e7799112360b0c4d5cbff067de0814c98c37c39b920f7", size = 286177, upload-time = "2026-10-07T12:22:33.745Z" }, + { url = "https://files.pythonhosted.org/packages/77/8b/0144c65f0e37e51c18d04ae15c21b19431c165002d0131fe9aa8b0b8b1e8/tomli-2.5.0-cp312-cp312-win32.whl", hash = "sha256:e7ad033e27a516a233bea839cdb77b80146facb3b4f40bf02cd0cac165cdd5c2", size = 102760, upload-time = "2026-10-07T12:22:34.887Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/5d6d8f42fc9a05fce69354e00ff256484192f5f2fc9a2165718fa0de61ec/tomli-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:bd05de8c1698f8413dd7d869492693a0bf2211543b787ac78cd5e7536af1a6d7", size = 112722, upload-time = "2026-10-07T12:22:36.162Z" }, + { url = "https://files.pythonhosted.org/packages/30/65/df18032218db0fb9b769fb23c8039a051f15c811993995ea04c350273a32/tomli-2.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:069435bd5480429b98c5e5afb02ab21c219b6f0064680671c6dc0d46817346ea", size = 109534, upload-time = "2026-10-07T12:22:37.296Z" }, + { url = "https://files.pythonhosted.org/packages/42/e5/51736d70da209350969e15aca5c5ab6e2ce1ea87a0a892a6c13aec172a86/tomli-2.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:943276cf269e0071948d9ff697159c1735e623c1151d88abb09b74659ef0cbea", size = 163328, upload-time = "2026-10-07T12:22:38.373Z" }, + { url = "https://files.pythonhosted.org/packages/ec/55/086f80dab4ab497602644274e6dea7ec5dd0b4e262e443a8ad3bb7edee2d/tomli-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:463b16086865b97facd8d0b3fb4cb7c544e3f58d2a69dc3113d6db9653fdb043", size = 162246, upload-time = "2026-10-07T12:22:39.673Z" }, + { url = "https://files.pythonhosted.org/packages/aa/eb/3ecc94459f3635c92321f4e7bde571323fdb2267c50e19e3188a281eae3b/tomli-2.5.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1245a6638fc4bb0a60af38a7d45413db34a13842027c77597c712c998c62fdf0", size = 272655, upload-time = "2026-10-07T12:22:41.08Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d7/494fd1f0c37a621f1ad9975c2efadb523e8101f144ed6edb2e7fe64738f2/tomli-2.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5d8bac3d603c97e6854424e5b2b5b741bdbde387e09f162fb0446812b4a8362b", size = 283595, upload-time = "2026-10-07T12:22:42.222Z" }, + { url = "https://files.pythonhosted.org/packages/70/51/bb8d62b1317e6640866f6949b2d5855e5300f2c99d46de1cd245570bba65/tomli-2.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:21e4cae4114aba25aa0d4f85cdf486d290fb35c0954d7bba536248da64d43066", size = 276253, upload-time = "2026-10-07T12:22:43.625Z" }, + { url = "https://files.pythonhosted.org/packages/66/f4/f46bd7f0763cd47de2db697dca9257c6a4adfd1a93b018cc75c8190ed5a8/tomli-2.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bbaefc84548d754be821bba7c4141c4787dda182f9e77f2f87b71213529efa7b", size = 283582, upload-time = "2026-10-07T12:22:44.983Z" }, + { url = "https://files.pythonhosted.org/packages/ac/03/70f2bcb2923a6db37818d917e124270a7f4cfd38ea576f5aa753a91c0ef5/tomli-2.5.0-cp313-cp313-win32.whl", hash = "sha256:abdbf6313b8d9efe157edeb7ab6eae4de064b1300ad31abf73755154b30abe68", size = 102628, upload-time = "2026-10-07T12:22:46.508Z" }, + { url = "https://files.pythonhosted.org/packages/dc/98/d52024bb5b0ff68b4f0d276d867f634c84a67319a7e9f6b7708a37742333/tomli-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd4dc129784e0c5335bd4e61dfcc4487499a013419e655cf2da1d091b7e0efdc", size = 113301, upload-time = "2026-10-07T12:22:47.647Z" }, + { url = "https://files.pythonhosted.org/packages/6f/f2/540db3a70572a8c23a28aba3e9c358ce0ffffbafc990905c1343aa265b31/tomli-2.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:69491c143d2fe063046e0301e62a810bed338fa4d1ce0fd870c27dc1e09b0d84", size = 109744, upload-time = "2026-10-07T12:22:48.925Z" }, + { url = "https://files.pythonhosted.org/packages/e4/49/caf6b307766eb9567664a8707e9d6be5fcc0e8903f18781c6677a60d80c7/tomli-2.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d3182ee2d887e507bd67319a0a61105d1dd33facc111329559a233b772c1a105", size = 162899, upload-time = "2026-10-07T12:22:50.088Z" }, + { url = "https://files.pythonhosted.org/packages/d3/c8/68cfce773a2733a49c74f99d627fb461bd990756860099eac25617889585/tomli-2.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:521345fd1f19d45b8df87657aaa38b6f2ca3800059fadf428e7ebf479a383646", size = 162080, upload-time = "2026-10-07T12:22:51.558Z" }, + { url = "https://files.pythonhosted.org/packages/7e/b2/e5bb8651fdad593f670501a7d718b1a7f73f064d44dea15e04c04dfef45d/tomli-2.5.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e95c7614e705bfe2b04b27aa124adec59752d15813df37e2156747cab3a006b", size = 273380, upload-time = "2026-10-07T12:22:52.918Z" }, + { url = "https://files.pythonhosted.org/packages/8d/d2/9e2d7f8b1dfe0e2b34c245986ebd55c4c553ea4ce6c47c443b332673253f/tomli-2.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ac2027d37c3afbdf4bdd377f2676f6f1d2122a5be1f1137b49dced590b37e75", size = 283228, upload-time = "2026-10-07T12:22:54.173Z" }, + { url = "https://files.pythonhosted.org/packages/ba/df/ec7b876b7b1a2718bd74a3743c076fff565b04029ba33e8f61fac262739f/tomli-2.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c414be4ed9d3cac80c42e348fa5a956117d1a48227f48026e31f59cb4a7671eb", size = 277189, upload-time = "2026-10-07T12:22:55.342Z" }, + { url = "https://files.pythonhosted.org/packages/7d/7b/e192d9eed0b9cb80da799f4d77052297fb9a2c3cc9b19f571f56ea88add6/tomli-2.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9b03d7dc168353b4132965bde20feceabaa470e570c6f59660dfae59b1f9eeb3", size = 283632, upload-time = "2026-10-07T12:22:56.735Z" }, + { url = "https://files.pythonhosted.org/packages/84/50/ff94454e75461d75623e47401ed323d65c10aab8fe9033242c20cd2fdf32/tomli-2.5.0-cp314-cp314-win32.whl", hash = "sha256:6f041843c4d3a37245c0c056fd955b186bf8b1fb85690cbe40b81230891dc34b", size = 103535, upload-time = "2026-10-07T12:22:58.084Z" }, + { url = "https://files.pythonhosted.org/packages/54/0b/bdacf05f963bd6026ebf6eeb0beda847d1d60e03e440725c64a4e08a0afd/tomli-2.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:f4b653094e18f9031102d3a1da5c729c8f222d85225b18037dac621695e46e1a", size = 114621, upload-time = "2026-10-07T12:22:59.2Z" }, + { url = "https://files.pythonhosted.org/packages/61/99/53f438fa6ae4f9d4ed0ddde3e7242b3bdc34b48c8f9948b72b9e9b127676/tomli-2.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:3f89d10c1ff6a38d992c27fc8a4816af71a909e08a40ec66934240b1e74347c3", size = 111572, upload-time = "2026-10-07T12:23:00.479Z" }, + { url = "https://files.pythonhosted.org/packages/b9/20/1f88f19427d380a40e90a770e087489eaafe4aeee070ae88ed2bbec00acd/tomli-2.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:e9e15b4a6c7dd6b85b5fbab29488a73f1f70de516942308daa266bf0e0aeb0d4", size = 171814, upload-time = "2026-10-07T12:23:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/d0/56/cbe5079c9f9a54b9b3e27fc82f08f3cb36edee75561679f53d2380c801d6/tomli-2.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e12bbcd32897272fb05929110362ae9ff4c1b9bb26bd9e971e71dcd3275b4c3d", size = 171324, upload-time = "2026-10-07T12:23:03.18Z" }, + { url = "https://files.pythonhosted.org/packages/2b/30/1d53fd3b0f1cb3ba542e345ec32c26aefdddc4e829e4f3429af8a4f27782/tomli-2.5.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20aa36de8f2cf87237143bc1fa1aae8d6612c09118f4da21c6a684db5dd1f6f9", size = 297441, upload-time = "2026-10-07T12:23:04.345Z" }, + { url = "https://files.pythonhosted.org/packages/66/d9/0800acb6a111686f764c1b91ef15cc42a20a66a46013bb42220f1d2c61c1/tomli-2.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22185fad8a1e622f064e78008018a0dd3323550dcb479cb7a1d296888d74024f", size = 307476, upload-time = "2026-10-07T12:23:05.671Z" }, + { url = "https://files.pythonhosted.org/packages/e8/63/30a8f3cd51b5bec37f04744bad0b0dc6160df84aad4f27b0e9283d66f221/tomli-2.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:984012f71908165449a951de2050d52f276bfe3aa5d5f570f63ddad814370374", size = 296113, upload-time = "2026-10-07T12:23:07.202Z" }, + { url = "https://files.pythonhosted.org/packages/ab/18/0b9ffc597e69c5a1e20a7823cb60d54b39a9f54e91edcb8574f022186758/tomli-2.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f79203b3965b4000e91808aaa7c040206093f2b8bf86f455982f2274c9ccf442", size = 307725, upload-time = "2026-10-07T12:23:08.508Z" }, + { url = "https://files.pythonhosted.org/packages/ab/c7/18f8baae0b5607a60e8e19b4a7fedee43a8ff6458e3896dcbbadeeac9c22/tomli-2.5.0-cp314-cp314t-win32.whl", hash = "sha256:91294a9fb94a75542f6e46e4a2ae709bd8d9b51134098cae5cf3bea5478b6d03", size = 108546, upload-time = "2026-10-07T12:23:09.956Z" }, + { url = "https://files.pythonhosted.org/packages/72/34/4cca9739254130627bde87500b3f2b512154fe2f278efa7e2a5e10ad4bcb/tomli-2.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f15e3e0b835a6d68b10c86bf80a3149780498d6911c93c3ffd1861d19f9200f1", size = 117814, upload-time = "2026-10-07T12:23:11.486Z" }, + { url = "https://files.pythonhosted.org/packages/7d/fb/afa530d47dd80a78fce43beac6bc6e00f84558eafcffbc6f37b21e80d056/tomli-2.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:6664b7ae7af7294256c53960a6103077f4914cec8ff98479c352f622c6f6b2f0", size = 115188, upload-time = "2026-10-07T12:23:12.728Z" }, + { url = "https://files.pythonhosted.org/packages/66/98/316fdc00f8c0939e6fe50461dd343c162d3ad51d1286eb25b7db54361d50/tomli-2.5.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:a525685c2f97da40762b8695eb7aa0af4c8344ca1905c73e4e29cb04d34607dc", size = 162775, upload-time = "2026-10-07T12:23:13.941Z" }, + { url = "https://files.pythonhosted.org/packages/c5/22/7b10fa5bb01c9539f53f69b619361b19350acc73657772ea7ac70ba309a8/tomli-2.5.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:9dbb18c1cfb2f6517942fc9314437f66aa06d94436ffb1f06102ef3572f35276", size = 161406, upload-time = "2026-10-07T12:23:15.215Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e7/1a069d86dfd20f1f84f71c63faed9f83c1d890bc06c27d82dc7d888fb573/tomli-2.5.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:752e8b1aa6a4367ef8bf6a1a1e005540f7ed055ba36d7193796812ca5404eb52", size = 273855, upload-time = "2026-10-07T12:23:16.471Z" }, + { url = "https://files.pythonhosted.org/packages/ae/83/d1ef43d1687d092ab9c235455c76e6e709483b346b056f086095c7c263a5/tomli-2.5.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c47300f9bf791808f77d82747691c4bb09cb14bdf3060cca99b42cdc4361d5a7", size = 284910, upload-time = "2026-10-07T12:23:18.166Z" }, + { url = "https://files.pythonhosted.org/packages/cc/05/f4d9cf7de61822ece0c3873f30d291e324911c71a378b8bfe5ced13fd9f5/tomli-2.5.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:19b0dd8749f4ea2f112c5fcfb3c5248390c899d7e2e173f1d91abee1fa0ff391", size = 277723, upload-time = "2026-10-07T12:23:19.355Z" }, + { url = "https://files.pythonhosted.org/packages/42/28/78262493141fa543151cf005760c3cb01d09fc28a11f993c05109902cb8c/tomli-2.5.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:57b1c3b01fab802e2899bc3d168dca320e14165e2fd9fd584760fb4ca5826859", size = 285115, upload-time = "2026-10-07T12:23:20.698Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b9/e1dab9a30bcb677b5cc5cee810609cfd64f24306a3055767dd3fda00b1e0/tomli-2.5.0-cp315-cp315-win32.whl", hash = "sha256:667e521b37a6c5ccaa044202c235b530f90177ffe2cd4a64ecc213c7dd535feb", size = 103475, upload-time = "2026-10-07T12:23:21.941Z" }, + { url = "https://files.pythonhosted.org/packages/4c/bd/31a3790c11d6ea95fcf5e6022ac0f8d0543c9b61120b730fc481bd43d3b4/tomli-2.5.0-cp315-cp315-win_amd64.whl", hash = "sha256:d747252933c8a65ef6bd8da0fbb7ce28a90eb6119d8cd00772cd528aa07b68d5", size = 114589, upload-time = "2026-10-07T12:23:23.098Z" }, + { url = "https://files.pythonhosted.org/packages/47/a2/4f6310fa699364f0e3af7ee3af88dddd9af066d33e716a0265bbe2b3ea84/tomli-2.5.0-cp315-cp315-win_arm64.whl", hash = "sha256:75dbcde8751b0a960aa3de173aa5e894d590755c6d7758b7e774c06f1dc3cbdd", size = 111493, upload-time = "2026-10-07T12:23:24.233Z" }, + { url = "https://files.pythonhosted.org/packages/68/14/00853f0b396d8971107ae1921bb5b322fdee1650d2f16bf06c20adb532e5/tomli-2.5.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:2419c2a189551987b59d80e63ec355671283336f41c6b9b89462df679c7d0c57", size = 171380, upload-time = "2026-10-07T12:23:25.512Z" }, + { url = "https://files.pythonhosted.org/packages/89/ad/fa6949321dadee46b27363974fb197b94c911c3b0f7a5fd26d7dc18fc2a0/tomli-2.5.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:0dc598040da8d42cf20f0be588ed7004f46db12a0ac6c32e03a59dccedaaadcd", size = 170553, upload-time = "2026-10-07T12:23:26.855Z" }, + { url = "https://files.pythonhosted.org/packages/53/aa/3056c919eb3e084df3752b2cf5f865dcc04af0b27dba2f66d7b28af4633a/tomli-2.5.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49096930c8d886c9bbdab62d2d0d17ce823ddeea522309a190b36245d5b49e01", size = 294428, upload-time = "2026-10-07T12:23:28.132Z" }, + { url = "https://files.pythonhosted.org/packages/96/b2/faeeb5d8769ea3832021d73e892c8391eae7b4b4f8b55a789127bd8b18a9/tomli-2.5.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8ade5023067f99fe72b88accd30d0ea05a158e9e32a11f124e731ea9695313f", size = 304909, upload-time = "2026-10-07T12:23:29.381Z" }, + { url = "https://files.pythonhosted.org/packages/f6/52/f094c09e73fb654b621716d019acb5d29bdfd1be01df80c281d552bda48d/tomli-2.5.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:b69564772b5c8f22ea5f498dff08cfa825045b4d4c4400529000bdf818aa3b2a", size = 293220, upload-time = "2026-10-07T12:23:30.608Z" }, + { url = "https://files.pythonhosted.org/packages/86/f5/0c30541078ca4b505ce3bd76ed931facbfec524dd018535d691d1af0a6d2/tomli-2.5.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:8ff3a2ca028c7eee0c777f9a092038d0a594a9fa04e215f929a22c329e2cb142", size = 305705, upload-time = "2026-10-07T12:23:32.181Z" }, + { url = "https://files.pythonhosted.org/packages/05/74/590e7d19d6a118fc5cc5704ff358e21d95b8573f6b9443b1519f29ca8825/tomli-2.5.0-cp315-cp315t-win32.whl", hash = "sha256:62fc1bc8eb03e3a9cadfca713d65614ed8e09d974a283295ffe3a831976b4dc5", size = 108432, upload-time = "2026-10-07T12:23:33.496Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b8/63a75cfb27a17c38550e44025d3a6e7be64516fd8608a3b75703bf37d81b/tomli-2.5.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f3fcbc57b1791fa6cbe5d8434179d51de12be1a4811469529f47f6e7487a2571", size = 117281, upload-time = "2026-10-07T12:23:34.648Z" }, + { url = "https://files.pythonhosted.org/packages/72/01/e8c1debb2173973372934c68fc8e46170ab60ef23ed4592dff4dec6e8993/tomli-2.5.0-cp315-cp315t-win_arm64.whl", hash = "sha256:d2ba24db8a9376921b5e87b4762b9adb0f3f1deaea68f2b8b0bb2c11efb9c3e7", size = 115069, upload-time = "2026-10-07T12:23:35.77Z" }, + { url = "https://files.pythonhosted.org/packages/60/3f/3e3f8fd0919249b0200c80fbc4f9a1e70be19f9883da71dfb7f8b9ab8aca/tomli-2.5.0-py3-none-any.whl", hash = "sha256:32a7b79ac57a2e83670ce329ccf675798bc5a2094783a63676866b70503f2e2b", size = 14765, upload-time = "2026-10-07T12:23:36.875Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "tzdata" +version = "2026.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/68/f1b440335057bfce71b6e50a9d09445aa2ecbd08359a337976627b8409e7/tzdata-2026.5.tar.gz", hash = "sha256:8cc73c0a0bfca7dbfa59235d60b2eff82231dee33f53d206db1acd9173cfc0a7", size = 200404, upload-time = "2026-10-03T09:23:14.143Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/21/1e5995a1c920cce14e4bffae20c665ec10e7ed03ab25e006cd741092b718/tzdata-2026.5-py2.py3-none-any.whl", hash = "sha256:b683bd1b6659ddcd810ff02ad09ba821d4bf1065072805063eb35c49617905ac", size = 347996, upload-time = "2026-10-03T09:23:12.535Z" }, +] + +[[package]] +name = "urllib3" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" }, +] From a155a7c11f491612bbdcb284b915a1794bcbebcd Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 14:50:10 +0200 Subject: [PATCH 06/51] refactor: sort __all__ (ruff RUF022) --- src/pse_sdk/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pse_sdk/__init__.py b/src/pse_sdk/__init__.py index 2bbea15..0347f71 100644 --- a/src/pse_sdk/__init__.py +++ b/src/pse_sdk/__init__.py @@ -4,4 +4,4 @@ from pse_sdk._format import OutputFormat __version__ = "1.5.1" -__all__ = ["PSEClient", "OutputFormat"] +__all__ = ["OutputFormat", "PSEClient"] From 7fe81ec664a7e73ca30c619482ad57b70598082f Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 14:50:10 +0200 Subject: [PATCH 07/51] docs: switch CONTRIBUTING to uv workflow --- CONTRIBUTING.md | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 88d2dca..ed913f7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,12 +9,10 @@ cd pse-python-sdk ``` -2. **Set up a virtual environment** and install dev dependencies: +2. **Install [uv](https://docs.astral.sh/uv/getting-started/installation/)** and sync the environment (creates `.venv` with the package and dev dependencies, pinned by `uv.lock`): ```bash - python -m venv .venv - source .venv/bin/activate # Windows: .venv\Scripts\activate - pip install -e ".[dev]" + uv sync ``` 3. **Checkout `develop`** — all feature work starts here: @@ -43,21 +41,35 @@ Short-lived branches for specific work: ## Development Workflow -Run these from the project root with your venv activated: +Run these from the project root: ```bash # Run tests -pytest +uv run pytest # Lint -ruff check src/ +uv run ruff check src/ # Format -ruff format src/ +uv run ruff format src/ ``` Fix any lint/test failures before opening a PR. +## Dependencies + +Dev dependencies live in the `dev` dependency group in `pyproject.toml`; exact versions are pinned in `uv.lock`. + +```bash +# Add a dev dependency +uv add --dev + +# Upgrade a single locked package +uv lock --upgrade-package +``` + +Commit `uv.lock` together with any `pyproject.toml` dependency change. + ## Commit Convention Use [Conventional Commits](https://www.conventionalcommits.org/): From 231eef9c2d564e70b00b5f64e8707a54740a36bc Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 14:57:21 +0200 Subject: [PATCH 08/51] refactor: render registry with repr and ruff format --- scripts/gen_registry.py | 95 ++--- src/pse_sdk/_endpoint_spec.py | 18 + src/pse_sdk/_registry.py | 715 +++++++--------------------------- 3 files changed, 193 insertions(+), 635 deletions(-) create mode 100644 src/pse_sdk/_endpoint_spec.py diff --git a/scripts/gen_registry.py b/scripts/gen_registry.py index 048772c..4233a94 100644 --- a/scripts/gen_registry.py +++ b/scripts/gen_registry.py @@ -2,15 +2,16 @@ Usage:: - python scripts/gen_registry.py # regenerate - python scripts/gen_registry.py --check # exit 1 if out of date - python scripts/gen_registry.py --spec openapi.json # use a local spec file + uv run scripts/gen_registry.py # regenerate + uv run scripts/gen_registry.py --check # exit 1 if out of date + uv run scripts/gen_registry.py --spec openapi.json # use a local spec file """ from __future__ import annotations import argparse import json +import subprocess import sys import urllib.request from pathlib import Path @@ -29,24 +30,17 @@ ) DATE_COLUMN = "business_date" -HEADER = '''\ +TEMPLATE = """\ # Generated by scripts/gen_registry.py from {url} # (spec version {version}). Do not edit by hand. -from dataclasses import dataclass from typing import Literal -ColumnType = Literal["string", "number", "integer", "boolean"] +from pse_sdk._endpoint_spec import EndpointSpec +Endpoint = Literal[{names}] -@dataclass(frozen=True) -class EndpointSpec: - """Schema of one PSE API endpoint.""" - - columns: dict[str, ColumnType] # all columns in API order -> JSON type - date_column: str | None # column for date-range filtering, None = no filter - meta_columns: tuple[str, ...] # dtime/period/business_date/publication_ts (+ _utc) - keys: tuple[str, ...] # primary key columns -''' +REGISTRY: dict[Endpoint, EndpointSpec] = {{{entries}}} +""" def load_spec(source: str) -> dict: @@ -88,44 +82,37 @@ def build_registry(spec: dict) -> dict[str, dict]: def render(registry: dict[str, dict], spec_version: str) -> str: - """Render *registry* as the source code of ``_registry.py``.""" - lines = [HEADER.format(url=SPEC_URL, version=spec_version), ""] - - lines.append("Endpoint = Literal[") - lines += [f" {_quote(name)}," for name in registry] - lines += ["]", "", "REGISTRY: dict[Endpoint, EndpointSpec] = {"] - - for name, entry in registry.items(): - date_column = _quote(entry["date_column"]) if entry["date_column"] else "None" - lines.append(f" {_quote(name)}: EndpointSpec(") - lines.append(" columns={") - lines += [ - f" {_quote(col)}: {_quote(type_)}," - for col, type_ in entry["columns"].items() - ] - lines.append(" },") - lines.append(f" date_column={date_column},") - lines += _render_tuple("meta_columns", entry["meta_columns"]) - lines += _render_tuple("keys", entry["keys"]) - lines.append(" ),") - - lines.append("}") - return "\n".join(lines) + "\n" - - -def _render_tuple(field: str, items: tuple[str, ...]) -> list[str]: - if not items: - return [f" {field}=(),"] - return [ - f" {field}=(", - *(f" {_quote(item)}," for item in items), - " ),", - ] - - -def _quote(value: str) -> str: - """Return *value* as a double-quoted Python string literal.""" - return json.dumps(value) + """Render *registry* as the source code of ``_registry.py``. + + Values are written with ``repr`` and laid out by ``ruff format``; trailing + commas keep one endpoint per line. + """ + source = TEMPLATE.format( + url=SPEC_URL, + version=spec_version, + names="".join(f"{name!r}," for name in registry), + entries="".join( + f"{name!r}: EndpointSpec({_kwargs(entry)})," + for name, entry in registry.items() + ), + ) + return _ruff_format(source) + + +def _kwargs(entry: dict) -> str: + return ", ".join(f"{field}={value!r}" for field, value in entry.items()) + + +def _ruff_format(source: str) -> str: + """Format *source* with the ruff pinned in ``uv.lock``, ignoring config.""" + result = subprocess.run( + [sys.executable, "-m", "ruff", "format", "--isolated", "-"], + input=source, + capture_output=True, + encoding="utf-8", + check=True, + ) + return result.stdout def main(argv: list[str] | None = None) -> int: @@ -151,7 +138,7 @@ def main(argv: list[str] | None = None) -> int: if args.check: if not OUTPUT.exists() or OUTPUT.read_text(encoding="utf-8") != source: print( - f"{OUTPUT.name} is out of date. Run: python scripts/gen_registry.py", + f"{OUTPUT.name} is out of date. Run: uv run scripts/gen_registry.py", file=sys.stderr, ) return 1 diff --git a/src/pse_sdk/_endpoint_spec.py b/src/pse_sdk/_endpoint_spec.py new file mode 100644 index 0000000..3045646 --- /dev/null +++ b/src/pse_sdk/_endpoint_spec.py @@ -0,0 +1,18 @@ +"""Endpoint schema type used by the generated ``_registry.py``.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Literal + +ColumnType = Literal["string", "number", "integer", "boolean"] + + +@dataclass(frozen=True) +class EndpointSpec: + """Schema of one PSE API endpoint.""" + + columns: dict[str, ColumnType] # all columns in API order -> JSON type + date_column: str | None # column for date-range filtering, None = no filter + meta_columns: tuple[str, ...] # dtime/period/business_date/publication_ts (+ _utc) + keys: tuple[str, ...] # primary key columns diff --git a/src/pse_sdk/_registry.py b/src/pse_sdk/_registry.py index 89a6de7..1e645af 100644 --- a/src/pse_sdk/_registry.py +++ b/src/pse_sdk/_registry.py @@ -1,20 +1,8 @@ # Generated by scripts/gen_registry.py from https://api.raporty.pse.pl/api/openapi # (spec version 2.1.5). Do not edit by hand. -from dataclasses import dataclass from typing import Literal -ColumnType = Literal["string", "number", "integer", "boolean"] - - -@dataclass(frozen=True) -class EndpointSpec: - """Schema of one PSE API endpoint.""" - - columns: dict[str, ColumnType] # all columns in API order -> JSON type - date_column: str | None # column for date-range filtering, None = no filter - meta_columns: tuple[str, ...] # dtime/period/business_date/publication_ts (+ _utc) - keys: tuple[str, ...] # primary key columns - +from pse_sdk._endpoint_spec import EndpointSpec Endpoint = Literal[ "afrr-status", @@ -122,16 +110,8 @@ class EndpointSpec: "publication_ts_utc": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "event_time_utc", - "afrr_status", - "reason_code", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("event_time_utc", "afrr_status", "reason_code"), ), "cmbp-tp": EndpointSpec( columns={ @@ -158,9 +138,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), "cmbu-tu": EndpointSpec( columns={ @@ -188,9 +166,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), "codn": EndpointSpec( columns={ @@ -202,16 +178,8 @@ class EndpointSpec: "publication_ts": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "period_from_date", - "period_to_date", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "period_from_date", "period_to_date"), ), "cor": EndpointSpec( columns={ @@ -235,9 +203,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), "cor-q-1": EndpointSpec( columns={ @@ -250,16 +216,8 @@ class EndpointSpec: "publication_ts": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "period_to_date", - "period_from_date", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "period_to_date", "period_from_date"), ), "cordmax": EndpointSpec( columns={ @@ -270,16 +228,8 @@ class EndpointSpec: "publication_ts_utc": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "cordmax_fcst", - "cordmax_cost", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "cordmax_fcst", "cordmax_cost"), ), "crb-rozl": EndpointSpec( columns={ @@ -307,9 +257,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), "csdac-pln": EndpointSpec( columns={ @@ -332,9 +280,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), "csire-kpi1": EndpointSpec( columns={ @@ -358,15 +304,8 @@ class EndpointSpec: "publication_ts_utc": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "row_no", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "row_no"), ), "csire-kpi10": EndpointSpec( columns={ @@ -385,15 +324,8 @@ class EndpointSpec: "publication_ts_utc": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "row_no", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "row_no"), ), "csire-kpi2": EndpointSpec( columns={ @@ -412,15 +344,8 @@ class EndpointSpec: "publication_ts_utc": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "row_no", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "row_no"), ), "csire-kpi3": EndpointSpec( columns={ @@ -439,15 +364,8 @@ class EndpointSpec: "publication_ts_utc": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "row_no", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "row_no"), ), "csire-kpi4": EndpointSpec( columns={ @@ -471,15 +389,8 @@ class EndpointSpec: "publication_ts_utc": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "row_no", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "row_no"), ), "csire-kpi5": EndpointSpec( columns={ @@ -503,15 +414,8 @@ class EndpointSpec: "publication_ts_utc": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "row_no", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "row_no"), ), "csire-kpi6": EndpointSpec( columns={ @@ -530,15 +434,8 @@ class EndpointSpec: "publication_ts_utc": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "row_no", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "row_no"), ), "csire-kpi7": EndpointSpec( columns={ @@ -557,15 +454,8 @@ class EndpointSpec: "publication_ts_utc": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "row_no", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "row_no"), ), "csire-kpi8": EndpointSpec( columns={ @@ -589,15 +479,8 @@ class EndpointSpec: "publication_ts_utc": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "row_no", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "row_no"), ), "csire-kpi9": EndpointSpec( columns={ @@ -621,15 +504,8 @@ class EndpointSpec: "publication_ts_utc": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "row_no", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "row_no"), ), "eb-rozl": EndpointSpec( columns={ @@ -655,9 +531,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), "en-rozl": EndpointSpec( columns={ @@ -682,9 +556,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), "energy-prices": EndpointSpec( columns={ @@ -715,9 +587,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), "gen-jw": EndpointSpec( columns={ @@ -744,11 +614,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - "resource_code", - "operating_mode", - ), + keys=("dtime_utc", "resource_code", "operating_mode"), ), "his-bil-mocy": EndpointSpec( columns={ @@ -797,16 +663,8 @@ class EndpointSpec: "publication_ts": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "peak_hour", - "peak", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "peak_hour", "peak"), ), "his-gen-pal": EndpointSpec( columns={ @@ -831,11 +689,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "business_date", - "dtime_utc", - "alias_entsoe", - ), + keys=("business_date", "dtime_utc", "alias_entsoe"), ), "his-gen-pal-sire": EndpointSpec( columns={ @@ -861,12 +715,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "business_date", - "dtime_utc", - "alias_sire", - "alias_entsoe", - ), + keys=("business_date", "dtime_utc", "alias_sire", "alias_entsoe"), ), "his-wlk-cal": EndpointSpec( columns={ @@ -905,9 +754,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), "it-oeb-rbb": EndpointSpec( columns={ @@ -927,15 +774,8 @@ class EndpointSpec: "publication_ts_utc": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "iteration_no", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "iteration_no"), ), "it-oeb-rbn": EndpointSpec( columns={ @@ -952,15 +792,8 @@ class EndpointSpec: "publication_ts": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "iteration_no", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "iteration_no"), ), "it-omb-rbb": EndpointSpec( columns={ @@ -980,15 +813,8 @@ class EndpointSpec: "publication_ts_utc": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "iteration_no", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "iteration_no"), ), "it-omb-rbn": EndpointSpec( columns={ @@ -1005,15 +831,8 @@ class EndpointSpec: "publication_ts": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "iteration_no", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "iteration_no"), ), "it-opmb-rmb": EndpointSpec( columns={ @@ -1030,15 +849,8 @@ class EndpointSpec: "publication_ts": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "iteration_no", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "iteration_no"), ), "it-ot-rbn": EndpointSpec( columns={ @@ -1055,15 +867,8 @@ class EndpointSpec: "publication_ts": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "iteration_no", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "iteration_no"), ), "it-pp-rbb": EndpointSpec( columns={ @@ -1083,15 +888,8 @@ class EndpointSpec: "publication_ts_utc": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "iteration_no", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "iteration_no"), ), "it-pp-rbn": EndpointSpec( columns={ @@ -1108,15 +906,8 @@ class EndpointSpec: "publication_ts": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "iteration_no", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "iteration_no"), ), "it-use-rbb": EndpointSpec( columns={ @@ -1137,15 +928,8 @@ class EndpointSpec: "publication_ts_utc": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "iteration_no", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "iteration_no"), ), "it-use-rbn": EndpointSpec( columns={ @@ -1162,15 +946,8 @@ class EndpointSpec: "publication_ts": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "iteration_no", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "iteration_no"), ), "kmb-kro-rozl": EndpointSpec( columns={ @@ -1194,9 +971,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), "krb-rozl": EndpointSpec( columns={ @@ -1221,9 +996,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), "kse-load": EndpointSpec( columns={ @@ -1247,9 +1020,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), "lolp": EndpointSpec( columns={ @@ -1292,10 +1063,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "business_date", - "dtime_utc", - ), + keys=("business_date", "dtime_utc"), ), "mbp-tp": EndpointSpec( columns={ @@ -1322,9 +1090,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), "mbu-tu": EndpointSpec( columns={ @@ -1352,9 +1118,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), "oeb-bpkdbo": EndpointSpec( columns={ @@ -1381,13 +1145,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - "activ_direction", - "ofp", - "ofc", - "cdo", - ), + keys=("dtime_utc", "activ_direction", "ofp", "ofc", "cdo"), ), "ogr-b": EndpointSpec( columns={ @@ -1424,13 +1182,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "node", - "business_date", - "to_dtime_utc", - "from_dtime_utc", - "period", - ), + keys=("node", "business_date", "to_dtime_utc", "from_dtime_utc", "period"), ), "ogr-b-head": EndpointSpec( columns={ @@ -1441,12 +1193,8 @@ class EndpointSpec: "is_active": "boolean", }, date_column="business_date", - meta_columns=( - "business_date", - ), - keys=( - "business_date", - ), + meta_columns=("business_date",), + keys=("business_date",), ), "ogr-d1": EndpointSpec( columns={ @@ -1470,31 +1218,14 @@ class EndpointSpec: "publication_ts_utc": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "node", - "business_date", - "to_dtime", - "from_dtime", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("node", "business_date", "to_dtime", "from_dtime"), ), "ogr-d1-head": EndpointSpec( - columns={ - "title": "string", - "period": "string", - "remarks": "string", - }, + columns={"title": "string", "period": "string", "remarks": "string"}, date_column=None, - meta_columns=( - "period", - ), - keys=( - "period", - ), + meta_columns=("period",), + keys=("period",), ), "ogr-d2": EndpointSpec( columns={ @@ -1518,31 +1249,14 @@ class EndpointSpec: "publication_ts_utc": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "node", - "business_date", - "to_dtime", - "from_dtime", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("node", "business_date", "to_dtime", "from_dtime"), ), "ogr-d2-head": EndpointSpec( - columns={ - "title": "string", - "period": "string", - "remarks": "string", - }, + columns={"title": "string", "period": "string", "remarks": "string"}, date_column=None, - meta_columns=( - "period", - ), - keys=( - "period", - ), + meta_columns=("period",), + keys=("period",), ), "ogr-d39": EndpointSpec( columns={ @@ -1566,17 +1280,8 @@ class EndpointSpec: "publication_ts_utc": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "node", - "business_date", - "to_dtime", - "from_dtime", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("node", "business_date", "to_dtime", "from_dtime"), ), "ogr-d39-head": EndpointSpec( columns={ @@ -1587,12 +1292,8 @@ class EndpointSpec: "remarks": "string", }, date_column=None, - meta_columns=( - "period", - ), - keys=( - "period", - ), + meta_columns=("period",), + keys=("period",), ), "ogr-m": EndpointSpec( columns={ @@ -1616,17 +1317,8 @@ class EndpointSpec: "publication_ts_utc": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "node", - "business_date", - "to_dtime", - "from_dtime", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("node", "business_date", "to_dtime", "from_dtime"), ), "ogr-m-head": EndpointSpec( columns={ @@ -1637,12 +1329,8 @@ class EndpointSpec: "remarks": "string", }, date_column=None, - meta_columns=( - "period", - ), - keys=( - "period", - ), + meta_columns=("period",), + keys=("period",), ), "ogr-oper": EndpointSpec( columns={ @@ -1666,31 +1354,14 @@ class EndpointSpec: "publication_ts_utc": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "node", - "business_date", - "to_dtime", - "from_dtime", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("node", "business_date", "to_dtime", "from_dtime"), ), "ogr-oper-head": EndpointSpec( - columns={ - "title": "string", - "period": "string", - "remarks": "string", - }, + columns={"title": "string", "period": "string", "remarks": "string"}, date_column=None, - meta_columns=( - "period", - ), - keys=( - "period", - ), + meta_columns=("period",), + keys=("period",), ), "ogr-r": EndpointSpec( columns={ @@ -1723,13 +1394,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "node", - "business_date", - "to_dtime", - "from_dtime", - "period", - ), + keys=("node", "business_date", "to_dtime", "from_dtime", "period"), ), "ogr-r-head": EndpointSpec( columns={ @@ -1740,12 +1405,8 @@ class EndpointSpec: "remarks": "string", }, date_column=None, - meta_columns=( - "period", - ), - keys=( - "period", - ), + meta_columns=("period",), + keys=("period",), ), "ogr-rmb": EndpointSpec( columns={ @@ -1765,15 +1426,8 @@ class EndpointSpec: "publication_ts_utc": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "from_dtime", - "node", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("from_dtime", "node"), ), "pdgobpkd": EndpointSpec( columns={ @@ -1825,10 +1479,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - "business_date", - ), + keys=("dtime_utc", "business_date"), ), "pdgopkd": EndpointSpec( columns={ @@ -1880,10 +1531,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - "business_date", - ), + keys=("dtime_utc", "business_date"), ), "pdgsz": EndpointSpec( columns={ @@ -1906,11 +1554,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - "valid_from_ts", - "valid_to_ts", - ), + keys=("dtime_utc", "valid_from_ts", "valid_to_ts"), ), "pdwkseub": EndpointSpec( columns={ @@ -1937,10 +1581,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - "resource_code", - ), + keys=("dtime_utc", "resource_code"), ), "pk5l-wp": EndpointSpec( columns={ @@ -1974,9 +1615,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "plan_dtime_utc", - ), + keys=("plan_dtime_utc",), ), "poeb-rbb": EndpointSpec( columns={ @@ -2001,13 +1640,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "business_date", - "dtime_utc", - "ofcg", - "ofcd", - "ofp", - ), + keys=("business_date", "dtime_utc", "ofcg", "ofcd", "ofp"), ), "poeb-rbn": EndpointSpec( columns={ @@ -2032,13 +1665,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "business_date", - "dtime_utc", - "ofcg", - "ofcd", - "ofp", - ), + keys=("business_date", "dtime_utc", "ofcg", "ofcd", "ofp"), ), "pomb-rbn": EndpointSpec( columns={ @@ -2097,13 +1724,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - "pom", - "com", - "comrr", - "reserve_type", - ), + keys=("dtime_utc", "pom", "com", "comrr", "reserve_type"), ), "poze-redoze": EndpointSpec( columns={ @@ -2129,10 +1750,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "business_date", - "period", - ), + keys=("business_date", "period"), ), "price-cost": EndpointSpec( columns={ @@ -2160,9 +1778,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), "price-fcst": EndpointSpec( columns={ @@ -2190,9 +1806,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), "przeplywy-mocy": EndpointSpec( columns={ @@ -2216,10 +1830,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - "section_code", - ), + keys=("dtime_utc", "section_code"), ), "pwm-dost-op": EndpointSpec( columns={ @@ -2254,10 +1865,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "business_date", - "dtime_utc", - ), + keys=("business_date", "dtime_utc"), ), "pwm-igcc-in": EndpointSpec( columns={ @@ -2285,10 +1893,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "business_date", - "dtime_utc", - ), + keys=("business_date", "dtime_utc"), ), "pwm-rdb": EndpointSpec( columns={ @@ -2322,10 +1927,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "business_date", - "dtime_utc", - ), + keys=("business_date", "dtime_utc"), ), "pwm-rdn": EndpointSpec( columns={ @@ -2361,10 +1963,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "business_date", - "dtime_utc", - ), + keys=("business_date", "dtime_utc"), ), "pwm-rdn-rdb": EndpointSpec( columns={ @@ -2400,10 +1999,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "business_date", - "dtime_utc", - ), + keys=("business_date", "dtime_utc"), ), "rcco2": EndpointSpec( columns={ @@ -2414,15 +2010,8 @@ class EndpointSpec: "business_date": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "rcco2_pln", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "rcco2_pln"), ), "rce-pln": EndpointSpec( columns={ @@ -2445,9 +2034,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), "ro-prog": EndpointSpec( columns={ @@ -2470,9 +2057,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), "ro-rozl": EndpointSpec( columns={ @@ -2495,9 +2080,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), "sk": EndpointSpec( columns={ @@ -2522,9 +2105,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), "specwezlow": EndpointSpec( columns={ @@ -2540,15 +2121,8 @@ class EndpointSpec: "publication_ts": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "business_date", - "regional_dispatch_center", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("business_date", "regional_dispatch_center"), ), "unav-pk5l": EndpointSpec( columns={ @@ -2570,17 +2144,8 @@ class EndpointSpec: "publication_ts_utc": "string", }, date_column="business_date", - meta_columns=( - "business_date", - "publication_ts", - "publication_ts_utc", - ), - keys=( - "power_plant", - "unit_code", - "start_dtime_utc", - "end_dtime_utc", - ), + meta_columns=("business_date", "publication_ts", "publication_ts_utc"), + keys=("power_plant", "unit_code", "start_dtime_utc", "end_dtime_utc"), ), "use-sprz-rbb": EndpointSpec( columns={ @@ -2599,9 +2164,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), "use-sprz-rbn": EndpointSpec( columns={ @@ -2620,9 +2183,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), "wmb": EndpointSpec( columns={ @@ -2652,9 +2213,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), "wsp-wrm": EndpointSpec( columns={ @@ -2682,9 +2241,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), "zeb-rozl": EndpointSpec( columns={ @@ -2709,9 +2266,7 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime", - ), + keys=("dtime",), ), "zmb": EndpointSpec( columns={ @@ -2741,8 +2296,6 @@ class EndpointSpec: "publication_ts", "publication_ts_utc", ), - keys=( - "dtime_utc", - ), + keys=("dtime_utc",), ), } From 44734187c9b36ecc14b9c0d740b1507d22e4a572 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 14:57:21 +0200 Subject: [PATCH 09/51] docs: use uv for registry regeneration --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index de240bc..71e92a4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,10 +62,10 @@ Fix any lint/test failures before opening a PR. ```bash # Regenerate from the live spec -python scripts/gen_registry.py +uv run scripts/gen_registry.py # Check that the committed registry is up to date -python scripts/gen_registry.py --check +uv run scripts/gen_registry.py --check ``` ## Dependencies From 7ea646b4ae4c1aef4fc2b6b65574f401cfce29c0 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 15:05:37 +0200 Subject: [PATCH 10/51] refactor: use string.Template and extract check helper in gen_registry --- scripts/gen_registry.py | 49 +++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/scripts/gen_registry.py b/scripts/gen_registry.py index 4233a94..6de786f 100644 --- a/scripts/gen_registry.py +++ b/scripts/gen_registry.py @@ -11,6 +11,7 @@ import argparse import json +import string import subprocess import sys import urllib.request @@ -30,17 +31,17 @@ ) DATE_COLUMN = "business_date" -TEMPLATE = """\ -# Generated by scripts/gen_registry.py from {url} -# (spec version {version}). Do not edit by hand. +TEMPLATE = string.Template("""\ +# Generated by scripts/gen_registry.py from $url +# (spec version $version). Do not edit by hand. from typing import Literal from pse_sdk._endpoint_spec import EndpointSpec -Endpoint = Literal[{names}] +Endpoint = Literal[$names] -REGISTRY: dict[Endpoint, EndpointSpec] = {{{entries}}} -""" +REGISTRY: dict[Endpoint, EndpointSpec] = {$entries} +""") def load_spec(source: str) -> dict: @@ -87,14 +88,12 @@ def render(registry: dict[str, dict], spec_version: str) -> str: Values are written with ``repr`` and laid out by ``ruff format``; trailing commas keep one endpoint per line. """ - source = TEMPLATE.format( - url=SPEC_URL, - version=spec_version, - names="".join(f"{name!r}," for name in registry), - entries="".join( - f"{name!r}: EndpointSpec({_kwargs(entry)})," - for name, entry in registry.items() - ), + names = "".join(f"{name!r}," for name in registry) + entries = "".join( + f"{name!r}: EndpointSpec({_kwargs(entry)})," for name, entry in registry.items() + ) + source = TEMPLATE.substitute( + url=SPEC_URL, version=spec_version, names=names, entries=entries ) return _ruff_format(source) @@ -115,6 +114,19 @@ def _ruff_format(source: str) -> str: return result.stdout +def _check(source: str) -> int: + """Return 1 if the committed registry differs from *source*, else 0.""" + current = OUTPUT.read_text(encoding="utf-8") if OUTPUT.exists() else None + if current != source: + print( + f"{OUTPUT.name} is out of date. Run: uv run scripts/gen_registry.py", + file=sys.stderr, + ) + return 1 + print(f"{OUTPUT.name} is up to date.") + return 0 + + def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter @@ -136,14 +148,7 @@ def main(argv: list[str] | None = None) -> int: source = render(registry, spec["info"]["version"]) if args.check: - if not OUTPUT.exists() or OUTPUT.read_text(encoding="utf-8") != source: - print( - f"{OUTPUT.name} is out of date. Run: uv run scripts/gen_registry.py", - file=sys.stderr, - ) - return 1 - print(f"{OUTPUT.name} is up to date.") - return 0 + return _check(source) OUTPUT.write_text(source, encoding="utf-8", newline="\n") print(f"Wrote {len(registry)} endpoints to {OUTPUT}") From a274c02a73545d26adf70f78d827d72c9698dbbd Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 15:37:34 +0200 Subject: [PATCH 11/51] feat: add registry-driven query helpers --- src/pse_sdk/_query.py | 34 +++++++++++++++++++ tests/test_query.py | 76 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 src/pse_sdk/_query.py create mode 100644 tests/test_query.py diff --git a/src/pse_sdk/_query.py b/src/pse_sdk/_query.py new file mode 100644 index 0000000..90ec5e9 --- /dev/null +++ b/src/pse_sdk/_query.py @@ -0,0 +1,34 @@ +"""Build OData query parts for an endpoint from the generated registry.""" + +from __future__ import annotations + +from pse_sdk._endpoint_spec import EndpointSpec +from pse_sdk._registry import REGISTRY + + +def get_spec(endpoint: str) -> EndpointSpec: + """Return the registry entry for *endpoint*. + + Raises: + ValueError: If *endpoint* is not in the registry. + """ + try: + return REGISTRY[endpoint] + except KeyError: + raise ValueError(f"Unknown endpoint {endpoint!r}") from None + + +def build_select(spec: EndpointSpec, columns: list[str] | None) -> list[str] | None: + """Return the ``$select`` columns for *columns* of *spec*. + + ``None`` selects all columns. Otherwise the meta and key columns are + prepended so every row stays identifiable; duplicates are dropped. + """ + if columns is None: + return None + return list(dict.fromkeys([*spec.meta_columns, *spec.keys, *columns])) + + +def date_clauses(column: str, start: str, end: str) -> list[str]: + """Return ``$filter`` clauses limiting *column* to ``[start, end]``.""" + return [f"{column} ge '{start}'", f"{column} le '{end}'"] diff --git a/tests/test_query.py b/tests/test_query.py new file mode 100644 index 0000000..a69b3ad --- /dev/null +++ b/tests/test_query.py @@ -0,0 +1,76 @@ +import pytest + +from pse_sdk._endpoint_spec import EndpointSpec +from pse_sdk._query import build_select, date_clauses, get_spec +from pse_sdk._registry import REGISTRY + +SPEC = EndpointSpec( + columns={ + "node": "string", + "value": "number", + "other": "number", + "dtime": "string", + "dtime_utc": "string", + "business_date": "string", + }, + date_column="business_date", + meta_columns=("dtime", "dtime_utc", "business_date"), + keys=("dtime_utc", "node"), +) + + +class TestGetSpec: + def test_known_endpoint(self): + assert get_spec("price-cost") is REGISTRY["price-cost"] + + def test_unknown_endpoint_raises(self): + with pytest.raises(ValueError, match="Unknown endpoint 'nope'"): + get_spec("nope") + + +class TestBuildSelect: + def test_none_selects_all(self): + assert build_select(SPEC, None) is None + + def test_meta_then_keys_then_columns(self): + assert build_select(SPEC, ["value", "other"]) == [ + "dtime", + "dtime_utc", + "business_date", + "node", + "value", + "other", + ] + + def test_duplicates_dropped(self): + assert build_select(SPEC, ["node", "business_date", "value", "value"]) == [ + "dtime", + "dtime_utc", + "business_date", + "node", + "value", + ] + + def test_empty_columns_selects_meta_and_keys(self): + assert build_select(SPEC, []) == ["dtime", "dtime_utc", "business_date", "node"] + + def test_unknown_column_passed_through(self): + assert build_select(SPEC, ["typo"])[-1] == "typo" + + def test_columns_not_mutated(self): + columns = ["value"] + build_select(SPEC, columns) + assert columns == ["value"] + + +@pytest.mark.parametrize("name", REGISTRY) +def test_registry_select_only_existing_columns(name): + spec = REGISTRY[name] + assert set(build_select(spec, [])) <= set(spec.columns) + + +def test_date_clauses(): + assert date_clauses("business_date", "2024-01-01", "2024-01-30") == [ + "business_date ge '2024-01-01'", + "business_date le '2024-01-30'", + ] From b0573d98cae7bfa595fe3b120665973a742dd51f Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 15:37:42 +0200 Subject: [PATCH 12/51] feat: add PSEClient.fetch() --- src/pse_sdk/_client.py | 72 +++++++++++++++++++++++++++++++++++++-- tests/test_client.py | 77 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+), 2 deletions(-) diff --git a/src/pse_sdk/_client.py b/src/pse_sdk/_client.py index ca86574..1a46b88 100644 --- a/src/pse_sdk/_client.py +++ b/src/pse_sdk/_client.py @@ -1,7 +1,11 @@ from __future__ import annotations +from pse_sdk._dates import normalize_date_range, split_date_range from pse_sdk._endpoints import make_endpoint +from pse_sdk._format import OutputFormat, convert, validate_format from pse_sdk._http import HTTPClient +from pse_sdk._query import build_select, date_clauses, get_spec +from pse_sdk._registry import Endpoint class PSEClient: @@ -14,8 +18,14 @@ class PSEClient: from pse_sdk import PSEClient pse = PSEClient() - records = pse.get_cen("2024-01-01", "2024-01-31") - df = pse.get_price_cost("2024-01-01", "2024-01-31", format="dataframe") + records = pse.fetch("price-cost", "2024-01-01", "2024-01-31") + df = pse.fetch( + "price-cost", + "2024-01-01", + "2024-01-31", + columns=["cen_cost", "ckoeb_cost"], + format="dataframe", + ) """ _BASE_URL = "https://api.raporty.pse.pl/api" @@ -131,3 +141,61 @@ def __init__(self, timeout: int = 30) -> None: self.get_pv = make_endpoint( client=self.client, path="his-wlk-cal", columns=["pv"] ) + + def fetch( + self, + endpoint: Endpoint, + start: str, + end: str, + *, + columns: list[str] | None = None, + format: OutputFormat = "dict", + ): + """Query *endpoint* for a date range. + + Args: + endpoint: Endpoint name, e.g. ``"price-cost"``. + start: Begin of range (``YYYY-MM-DD``, ``YYYY/MM/DD``, or ``YYYYMMDD``). + end: End of range (``YYYY-MM-DD``, ``YYYY/MM/DD``, or ``YYYYMMDD``). + columns: Data columns to return. The endpoint's meta and key + columns are always included. ``None`` (default) returns all + columns. + format: Output format — ``"dict"`` (default) or ``"dataframe"``. + + Raises: + TypeError: If *start* or *end* is not a string. + ValueError: If *endpoint* is unknown or has no date column, + *format* is not supported, a date string cannot be parsed, + or *start* is after *end*. + """ + select = build_select(get_spec(endpoint), columns) + return self._fetch(endpoint, start, end, select=select, format=format) + + def _fetch( + self, + endpoint: str, + start: str, + end: str, + *, + select: list[str] | None, + format: OutputFormat, + ): + """Fetch *select* columns of *endpoint* in 30-day chunks of the range.""" + validate_format(format) + spec = get_spec(endpoint) + if spec.date_column is None: + raise ValueError( + f"Endpoint {endpoint!r} has no date column and is not supported yet" + ) + start_str, end_str = normalize_date_range(start, end) + + records: list[dict] = [] + for chunk_start, chunk_end in split_date_range(start_str, end_str): + filter_expr = " and ".join( + date_clauses(spec.date_column, chunk_start, chunk_end) + ) + records.extend( + self.client.fetch_all(endpoint, select=select, filter_expr=filter_expr) + ) + + return convert(records, format) diff --git a/tests/test_client.py b/tests/test_client.py index 25dac25..f91a8a1 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,4 +1,8 @@ +import pandas as pd +import pytest + from pse_sdk._client import PSEClient +from pse_sdk._registry import REGISTRY EXPECTED_ENDPOINTS = [ ("get_cen", "price-cost", ["cen_cost"]), @@ -76,3 +80,76 @@ def test_endpoint_columns(self, mock_client): PSEClient() columns = [call.kwargs["columns"] for call in mock_make.call_args_list] assert columns == [cols for _, _, cols in EXPECTED_ENDPOINTS] + + +@pytest.fixture() +def pse(mock_client): + client = PSEClient() + client.client.fetch_all.return_value = [] + return client + + +class TestFetch: + def test_select_meta_keys_and_columns(self, pse): + pse.fetch("price-cost", "2024-01-01", "2024-01-15", columns=["cen_cost", "ckoeb_cost"]) + assert pse.client.fetch_all.call_count == 1 + assert pse.client.fetch_all.call_args.args == ("price-cost",) + assert pse.client.fetch_all.call_args.kwargs["select"] == [ + *REGISTRY["price-cost"].meta_columns, + "cen_cost", + "ckoeb_cost", + ] + + def test_select_includes_keys(self, pse): + pse.fetch("gen-jw", "2024-01-01", "2024-01-15", columns=[]) + select = pse.client.fetch_all.call_args.kwargs["select"] + assert {"resource_code", "operating_mode"} <= set(select) + + def test_columns_none_selects_all(self, pse): + pse.fetch("price-cost", "2024-01-01", "2024-01-15") + assert pse.client.fetch_all.call_args.kwargs["select"] is None + + def test_filter_on_date_column(self, pse): + pse.fetch("price-cost", "2024-01-01", "2024-01-30") + filter_expr = pse.client.fetch_all.call_args.kwargs["filter_expr"] + assert filter_expr == "business_date ge '2024-01-01' and business_date le '2024-01-30'" + + def test_long_range_chunked(self, pse): + pse.fetch("price-cost", "2024-01-01", "2024-02-29") + filters = [c.kwargs["filter_expr"] for c in pse.client.fetch_all.call_args_list] + assert filters == [ + "business_date ge '2024-01-01' and business_date le '2024-01-30'", + "business_date ge '2024-01-31' and business_date le '2024-02-29'", + ] + + def test_chunked_results_combined(self, pse): + pse.client.fetch_all.side_effect = [[{"a": 1}], [{"a": 2}]] + result = pse.fetch("price-cost", "2024-01-01", "2024-02-29") + assert result == [{"a": 1}, {"a": 2}] + + def test_dataframe_format(self, pse): + pse.client.fetch_all.side_effect = [[{"a": 1}], [{"a": 2}]] + result = pse.fetch("price-cost", "2024-01-01", "2024-02-29", format="dataframe") + assert isinstance(result, pd.DataFrame) + assert len(result) == 2 + + def test_invalid_format_raises_before_request(self, pse): + with pytest.raises(ValueError, match="format must be"): + pse.fetch("price-cost", "2024-01-01", "2024-01-30", format="df") + pse.client.fetch_all.assert_not_called() + + def test_no_date_column_raises_before_request(self, pse): + with pytest.raises(ValueError, match="'ogr-d1-head' has no date column"): + pse.fetch("ogr-d1-head", "2024-01-01", "2024-01-30") + pse.client.fetch_all.assert_not_called() + + def test_unknown_endpoint_raises_before_request(self, pse): + with pytest.raises(ValueError, match="Unknown endpoint 'nope'"): + pse.fetch("nope", "2024-01-01", "2024-01-30") + pse.client.fetch_all.assert_not_called() + + def test_invalid_date_range_raises_before_request(self, pse): + with pytest.raises(ValueError, match="must not be after"): + pse.fetch("price-cost", "2024-02-01", "2024-01-01") + pse.client.fetch_all.assert_not_called() + From 7ec236a6add90580d3e0fc2cc0263f7ae42742fe Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 15:37:45 +0200 Subject: [PATCH 13/51] feat: route get_* methods through fetch() --- src/pse_sdk/_client.py | 64 +++++++++++----------- src/pse_sdk/_endpoints.py | 29 ++++------ tests/test_client.py | 21 +++++++ tests/test_endpoints.py | 112 ++++++++++++-------------------------- 4 files changed, 98 insertions(+), 128 deletions(-) diff --git a/src/pse_sdk/_client.py b/src/pse_sdk/_client.py index 1a46b88..fb0a5dc 100644 --- a/src/pse_sdk/_client.py +++ b/src/pse_sdk/_client.py @@ -40,106 +40,106 @@ def __init__(self, timeout: int = 30) -> None: # price-cost self.get_cen = make_endpoint( - client=self.client, path="price-cost", columns=["cen_cost"] + fetch=self._fetch, path="price-cost", columns=["cen_cost"] ) self.get_ckoeb = make_endpoint( - client=self.client, path="price-cost", columns=["ckoeb_cost"] + fetch=self._fetch, path="price-cost", columns=["ckoeb_cost"] ) self.get_ceb_sr = make_endpoint( - client=self.client, path="price-cost", columns=["ceb_sr_cost"] + fetch=self._fetch, path="price-cost", columns=["ceb_sr_cost"] ) self.get_cor_pp = make_endpoint( - client=self.client, path="price-cost", columns=["cor_pp_cost"] + fetch=self._fetch, path="price-cost", columns=["cor_pp_cost"] ) self.get_imb_energy = make_endpoint( - client=self.client, path="price-cost", columns=["imb_energy"] + fetch=self._fetch, path="price-cost", columns=["imb_energy"] ) self.get_contracting = make_endpoint( - client=self.client, path="price-cost", columns=["contracting"] + fetch=self._fetch, path="price-cost", columns=["contracting"] ) # csdac-pln self.get_csdac = make_endpoint( - client=self.client, path="csdac-pln", columns=["csdac_pln"] + fetch=self._fetch, path="csdac-pln", columns=["csdac_pln"] ) # krb-rozl - self.get_kb = make_endpoint(client=self.client, path="krb-rozl", columns=["kb"]) - self.get_ko = make_endpoint(client=self.client, path="krb-rozl", columns=["ko"]) + self.get_kb = make_endpoint(fetch=self._fetch, path="krb-rozl", columns=["kb"]) + self.get_ko = make_endpoint(fetch=self._fetch, path="krb-rozl", columns=["ko"]) self.get_kcz = make_endpoint( - client=self.client, path="krb-rozl", columns=["kcz"] + fetch=self._fetch, path="krb-rozl", columns=["kcz"] ) # kmb-kro-rozl self.get_kmb = make_endpoint( - client=self.client, path="kmb-kro-rozl", columns=["kmb"] + fetch=self._fetch, path="kmb-kro-rozl", columns=["kmb"] ) self.get_kro = make_endpoint( - client=self.client, path="kmb-kro-rozl", columns=["kro"] + fetch=self._fetch, path="kmb-kro-rozl", columns=["kro"] ) # sk self.get_sk_d1_fcst = make_endpoint( - client=self.client, path="sk", columns=["sk_d1_fcst"] + fetch=self._fetch, path="sk", columns=["sk_d1_fcst"] ) self.get_sk_d_fcst = make_endpoint( - client=self.client, path="sk", columns=["sk_d_fcst"] + fetch=self._fetch, path="sk", columns=["sk_d_fcst"] ) self.get_sk_cost = make_endpoint( - client=self.client, path="sk", columns=["sk_cost"] + fetch=self._fetch, path="sk", columns=["sk_cost"] ) # his-wlk-cal self.get_demand = make_endpoint( - client=self.client, path="his-wlk-cal", columns=["demand"] + fetch=self._fetch, path="his-wlk-cal", columns=["demand"] ) self.get_swm_p = make_endpoint( - client=self.client, path="his-wlk-cal", columns=["swm_p"] + fetch=self._fetch, path="his-wlk-cal", columns=["swm_p"] ) self.get_swm_np = make_endpoint( - client=self.client, path="his-wlk-cal", columns=["swm_np"] + fetch=self._fetch, path="his-wlk-cal", columns=["swm_np"] ) self.get_jg = make_endpoint( - client=self.client, path="his-wlk-cal", columns=["jg"] + fetch=self._fetch, path="his-wlk-cal", columns=["jg"] ) self.get_jgw1 = make_endpoint( - client=self.client, path="his-wlk-cal", columns=["jgw1"] + fetch=self._fetch, path="his-wlk-cal", columns=["jgw1"] ) self.get_jgw2 = make_endpoint( - client=self.client, path="his-wlk-cal", columns=["jgw2"] + fetch=self._fetch, path="his-wlk-cal", columns=["jgw2"] ) self.get_jgm1 = make_endpoint( - client=self.client, path="his-wlk-cal", columns=["jgm1"] + fetch=self._fetch, path="his-wlk-cal", columns=["jgm1"] ) self.get_jgm2 = make_endpoint( - client=self.client, path="his-wlk-cal", columns=["jgm2"] + fetch=self._fetch, path="his-wlk-cal", columns=["jgm2"] ) self.get_jgz1 = make_endpoint( - client=self.client, path="his-wlk-cal", columns=["jgz1"] + fetch=self._fetch, path="his-wlk-cal", columns=["jgz1"] ) self.get_jgz2 = make_endpoint( - client=self.client, path="his-wlk-cal", columns=["jgz2"] + fetch=self._fetch, path="his-wlk-cal", columns=["jgz2"] ) self.get_jgz3 = make_endpoint( - client=self.client, path="his-wlk-cal", columns=["jgz3"] + fetch=self._fetch, path="his-wlk-cal", columns=["jgz3"] ) self.get_jga = make_endpoint( - client=self.client, path="his-wlk-cal", columns=["jga"] + fetch=self._fetch, path="his-wlk-cal", columns=["jga"] ) self.get_jgo = make_endpoint( - client=self.client, path="his-wlk-cal", columns=["jgo"] + fetch=self._fetch, path="his-wlk-cal", columns=["jgo"] ) self.get_jnwrb = make_endpoint( - client=self.client, path="his-wlk-cal", columns=["jnwrb"] + fetch=self._fetch, path="his-wlk-cal", columns=["jnwrb"] ) self.get_jgm = make_endpoint( - client=self.client, path="his-wlk-cal", columns=["jgm"] + fetch=self._fetch, path="his-wlk-cal", columns=["jgm"] ) self.get_wi = make_endpoint( - client=self.client, path="his-wlk-cal", columns=["wi"] + fetch=self._fetch, path="his-wlk-cal", columns=["wi"] ) self.get_pv = make_endpoint( - client=self.client, path="his-wlk-cal", columns=["pv"] + fetch=self._fetch, path="his-wlk-cal", columns=["pv"] ) def fetch( diff --git a/src/pse_sdk/_endpoints.py b/src/pse_sdk/_endpoints.py index c77d735..bc67264 100644 --- a/src/pse_sdk/_endpoints.py +++ b/src/pse_sdk/_endpoints.py @@ -1,17 +1,19 @@ from __future__ import annotations -from pse_sdk._dates import normalize_date_range, split_date_range -from pse_sdk._format import OutputFormat, convert -from pse_sdk._http import HTTPClient +from collections.abc import Callable + +from pse_sdk._format import OutputFormat COMMON_COLUMNS = ["dtime", "period", "business_date", "publication_ts"] -def make_endpoint(client: HTTPClient, path: str, columns: list[str] | None = None): - """Create a callable that fetches and formats endpoint data.""" +def make_endpoint( + fetch: Callable[..., list[dict]], path: str, columns: list[str] | None = None +): + """Create a callable that fetches *columns* of *path* via *fetch*.""" select = list(COMMON_COLUMNS) + columns if columns else None - def fetch(start: str, end: str, format: OutputFormat = "dict") -> list[dict]: + def get(start: str, end: str, format: OutputFormat = "dict") -> list[dict]: """Query PSE data for a date range. Args: @@ -24,17 +26,6 @@ def fetch(start: str, end: str, format: OutputFormat = "dict") -> list[dict]: ValueError: If a date string cannot be parsed, or *start* is after *end*. """ - start_str, end_str = normalize_date_range(start, end) - - records: list[dict] = [] - for chunk_start, chunk_end in split_date_range(start_str, end_str): - filter_expr = ( - f"business_date ge '{chunk_start}' and business_date le '{chunk_end}'" - ) - records.extend( - client.fetch_all(path, select=select, filter_expr=filter_expr) - ) - - return convert(records, format) + return fetch(path, start, end, select=select, format=format) - return fetch + return get diff --git a/tests/test_client.py b/tests/test_client.py index f91a8a1..2ee8a2c 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,7 +1,10 @@ +from unittest.mock import patch + import pandas as pd import pytest from pse_sdk._client import PSEClient +from pse_sdk._endpoints import COMMON_COLUMNS from pse_sdk._registry import REGISTRY EXPECTED_ENDPOINTS = [ @@ -153,3 +156,21 @@ def test_invalid_date_range_raises_before_request(self, pse): pse.fetch("price-cost", "2024-02-01", "2024-01-01") pse.client.fetch_all.assert_not_called() + +class TestGetMethods: + def test_keep_legacy_select(self): + with patch("pse_sdk._client.HTTPClient") as mock_http: + fetch_all = mock_http.return_value.fetch_all + fetch_all.return_value = [] + PSEClient().get_cen("2024-01-01", "2024-01-30") + assert fetch_all.call_args.args == ("price-cost",) + assert fetch_all.call_args.kwargs == { + "select": COMMON_COLUMNS + ["cen_cost"], + "filter_expr": "business_date ge '2024-01-01' and business_date le '2024-01-30'", + } + + def test_invalid_format_raises_before_request(self): + with patch("pse_sdk._client.HTTPClient") as mock_http: + with pytest.raises(ValueError, match="format must be"): + PSEClient().get_cen("2024-01-01", "2024-01-30", format="df") + mock_http.return_value.fetch_all.assert_not_called() diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py index 8cfb638..9d140e5 100644 --- a/tests/test_endpoints.py +++ b/tests/test_endpoints.py @@ -1,99 +1,57 @@ from unittest.mock import MagicMock -import pandas as pd -import pytest - from pse_sdk._endpoints import COMMON_COLUMNS, make_endpoint class TestMakeEndpoint: def test_returns_callable(self): - client = MagicMock() - result = make_endpoint(client, "price-cost") + result = make_endpoint(MagicMock(), "price-cost") assert callable(result) def test_select_with_columns(self): - client = MagicMock() - client.fetch_all.return_value = [] - fn = make_endpoint(client, "price-cost", columns=["cen_cost"]) + fetch = MagicMock() + fn = make_endpoint(fetch, "price-cost", columns=["cen_cost"]) fn("2024-01-01", "2024-01-30") - call_kwargs = client.fetch_all.call_args - assert call_kwargs.kwargs["select"] == COMMON_COLUMNS + ["cen_cost"] + assert fetch.call_args.kwargs["select"] == COMMON_COLUMNS + ["cen_cost"] def test_select_none_without_columns(self): - client = MagicMock() - client.fetch_all.return_value = [] - fn = make_endpoint(client, "price-cost") + fetch = MagicMock() + fn = make_endpoint(fetch, "price-cost") fn("2024-01-01", "2024-01-30") - call_kwargs = client.fetch_all.call_args - assert call_kwargs.kwargs["select"] is None + assert fetch.call_args.kwargs["select"] is None def test_common_columns_not_mutated(self): original = list(COMMON_COLUMNS) - client = MagicMock() - make_endpoint(client, "price-cost", columns=["extra"]) + make_endpoint(MagicMock(), "price-cost", columns=["extra"]) assert COMMON_COLUMNS == original -class TestEndpointFetch: - def test_filter_expr_built_correctly(self): - client = MagicMock() - client.fetch_all.return_value = [] - fn = make_endpoint(client, "price-cost", columns=["cen_cost"]) +class TestEndpointCall: + def test_forwards_arguments(self): + fetch = MagicMock() + fn = make_endpoint(fetch, "price-cost", columns=["cen_cost"]) + fn("2024-01-01", "2024-01-30", format="dataframe") + fetch.assert_called_once_with( + "price-cost", + "2024-01-01", + "2024-01-30", + select=COMMON_COLUMNS + ["cen_cost"], + format="dataframe", + ) + + def test_positional_format(self): + fetch = MagicMock() + fn = make_endpoint(fetch, "price-cost") + fn("2024-01-01", "2024-01-30", "dataframe") + assert fetch.call_args.kwargs["format"] == "dataframe" + + def test_default_format_dict(self): + fetch = MagicMock() + fn = make_endpoint(fetch, "price-cost") fn("2024-01-01", "2024-01-30") - filter_expr = client.fetch_all.call_args.kwargs["filter_expr"] - assert filter_expr == "business_date ge '2024-01-01' and business_date le '2024-01-30'" - - def test_dict_format_returns_list(self): - client = MagicMock() - client.fetch_all.return_value = [{"a": 1}] - fn = make_endpoint(client, "price-cost") - result = fn("2024-01-01", "2024-01-30", format="dict") - assert isinstance(result, list) - - def test_dataframe_format_returns_df(self): - client = MagicMock() - client.fetch_all.return_value = [{"a": 1}] - fn = make_endpoint(client, "price-cost") - result = fn("2024-01-01", "2024-01-30", format="dataframe") - assert isinstance(result, pd.DataFrame) - - def test_invalid_dates_propagate(self): - client = MagicMock() - fn = make_endpoint(client, "price-cost") - with pytest.raises(ValueError): - fn("2024-02-01", "2024-01-01") - - -class TestChunkedFetch: - def test_short_range_single_call(self): - client = MagicMock() - client.fetch_all.return_value = [{"a": 1}] - fn = make_endpoint(client, "price-cost", columns=["cen_cost"]) - fn("2024-01-01", "2024-01-15") - assert client.fetch_all.call_count == 1 - - def test_long_range_multiple_calls(self): - client = MagicMock() - client.fetch_all.return_value = [] - fn = make_endpoint(client, "price-cost", columns=["cen_cost"]) - fn("2024-01-01", "2024-02-29") - assert client.fetch_all.call_count == 2 - filters = [c.kwargs["filter_expr"] for c in client.fetch_all.call_args_list] - assert filters[0] == "business_date ge '2024-01-01' and business_date le '2024-01-30'" - assert filters[1] == "business_date ge '2024-01-31' and business_date le '2024-02-29'" - - def test_chunked_results_combined(self): - client = MagicMock() - client.fetch_all.side_effect = [[{"a": 1}], [{"a": 2}]] - fn = make_endpoint(client, "price-cost") - result = fn("2024-01-01", "2024-02-29") - assert result == [{"a": 1}, {"a": 2}] + assert fetch.call_args.kwargs["format"] == "dict" - def test_chunked_results_combined_dataframe(self): - client = MagicMock() - client.fetch_all.side_effect = [[{"a": 1}], [{"a": 2}]] - fn = make_endpoint(client, "price-cost") - result = fn("2024-01-01", "2024-02-29", format="dataframe") - assert isinstance(result, pd.DataFrame) - assert len(result) == 2 + def test_returns_fetch_result(self): + fetch = MagicMock(return_value=[{"a": 1}]) + fn = make_endpoint(fetch, "price-cost") + assert fn("2024-01-01", "2024-01-30") == [{"a": 1}] From 15b7ae3271fe81e991ea85ffcc0fcf4180aff231 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 15:37:45 +0200 Subject: [PATCH 14/51] docs: document fetch() in README and endpoints.md --- README.md | 17 +++++++++++++---- docs/endpoints.md | 29 +++++++++++++++++++++-------- tests/manual_test.py | 8 +++++++- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 6b85647..c9112ea 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,20 @@ from pse_sdk import PSEClient client = PSEClient() -# Returns list[dict] +# Returns list[dict] with all columns +records = client.fetch("price-cost", start="2024-01-01", end="2024-01-31") + +# Returns pd.DataFrame (requires pandas) with selected columns +df = client.fetch( + "price-cost", + start="2024-01-01", + end="2024-01-31", + columns=["cen_cost", "ckoeb_cost"], + format="dataframe", +) + +# Single-column shortcut records = client.get_cen(start="2024-01-01", end="2024-01-31") - -# Returns pd.DataFrame (requires pandas) -df = client.get_cen(start="2024-01-01", end="2024-01-31", format="dataframe") ``` ## Date Formats diff --git a/docs/endpoints.md b/docs/endpoints.md index 1b61467..0f0f614 100644 --- a/docs/endpoints.md +++ b/docs/endpoints.md @@ -1,19 +1,32 @@ # Endpoints -All endpoints share the same signature: +## `fetch()` + +Any endpoint can be queried by name: ```python -(start: str, end: str, format: OutputFormat = "dict") +fetch(endpoint: Endpoint, start: str, end: str, *, + columns: list[str] | None = None, format: OutputFormat = "dict") ``` -## Common Response Columns +- `columns=None` returns all columns. +- `columns=[...]` returns these columns plus the endpoint's meta columns + (`dtime`, `period`, `business_date`, `publication_ts` and their `*_utc` + variants, where present) and key columns (e.g. `resource_code` on `gen-jw`), + so every row stays identifiable. +- Rows are filtered on `business_date`. +- `ogr-*-head` endpoints have no date column and are not supported yet. + +## `get_*` Methods + +Single-column shortcuts with the signature: -Every endpoint returns these columns plus endpoint-specific columns: +```python +(start: str, end: str, format: OutputFormat = "dict") +``` -- `dtime` — timestamp -- `period` — period identifier -- `business_date` — business date -- `publication_ts` — publication timestamp +Each returns its column plus `dtime`, `period`, `business_date` and +`publication_ts`. ## Endpoints Coverage diff --git a/tests/manual_test.py b/tests/manual_test.py index d8410b3..b93b01b 100644 --- a/tests/manual_test.py +++ b/tests/manual_test.py @@ -2,6 +2,12 @@ client = PSEClient() -resp = client.get_demand(start="2026-01-01", end="2026-01-02", format="dataframe") +resp = client.fetch( + "his-wlk-cal", + start="2026-01-01", + end="2026-01-02", + columns=["demand", "pv"], + format="dataframe", +) print(resp) From 8604f029c62409090598620914fcd205b88228f6 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 16:13:51 +0200 Subject: [PATCH 15/51] feat!: remove get_* methods and make_endpoint --- src/pse_sdk/_client.py | 123 ++------------------------------------ src/pse_sdk/_endpoints.py | 31 ---------- tests/conftest.py | 11 ++-- tests/test_client.py | 87 +-------------------------- tests/test_endpoints.py | 57 ------------------ tests/test_registry.py | 11 ---- 6 files changed, 11 insertions(+), 309 deletions(-) delete mode 100644 src/pse_sdk/_endpoints.py delete mode 100644 tests/test_endpoints.py diff --git a/src/pse_sdk/_client.py b/src/pse_sdk/_client.py index fb0a5dc..11fb0e9 100644 --- a/src/pse_sdk/_client.py +++ b/src/pse_sdk/_client.py @@ -1,7 +1,6 @@ from __future__ import annotations from pse_sdk._dates import normalize_date_range, split_date_range -from pse_sdk._endpoints import make_endpoint from pse_sdk._format import OutputFormat, convert, validate_format from pse_sdk._http import HTTPClient from pse_sdk._query import build_select, date_clauses, get_spec @@ -38,110 +37,6 @@ def __init__(self, timeout: int = 30) -> None: timeout=timeout, ) - # price-cost - self.get_cen = make_endpoint( - fetch=self._fetch, path="price-cost", columns=["cen_cost"] - ) - self.get_ckoeb = make_endpoint( - fetch=self._fetch, path="price-cost", columns=["ckoeb_cost"] - ) - self.get_ceb_sr = make_endpoint( - fetch=self._fetch, path="price-cost", columns=["ceb_sr_cost"] - ) - self.get_cor_pp = make_endpoint( - fetch=self._fetch, path="price-cost", columns=["cor_pp_cost"] - ) - self.get_imb_energy = make_endpoint( - fetch=self._fetch, path="price-cost", columns=["imb_energy"] - ) - self.get_contracting = make_endpoint( - fetch=self._fetch, path="price-cost", columns=["contracting"] - ) - - # csdac-pln - self.get_csdac = make_endpoint( - fetch=self._fetch, path="csdac-pln", columns=["csdac_pln"] - ) - - # krb-rozl - self.get_kb = make_endpoint(fetch=self._fetch, path="krb-rozl", columns=["kb"]) - self.get_ko = make_endpoint(fetch=self._fetch, path="krb-rozl", columns=["ko"]) - self.get_kcz = make_endpoint( - fetch=self._fetch, path="krb-rozl", columns=["kcz"] - ) - - # kmb-kro-rozl - self.get_kmb = make_endpoint( - fetch=self._fetch, path="kmb-kro-rozl", columns=["kmb"] - ) - self.get_kro = make_endpoint( - fetch=self._fetch, path="kmb-kro-rozl", columns=["kro"] - ) - - # sk - self.get_sk_d1_fcst = make_endpoint( - fetch=self._fetch, path="sk", columns=["sk_d1_fcst"] - ) - self.get_sk_d_fcst = make_endpoint( - fetch=self._fetch, path="sk", columns=["sk_d_fcst"] - ) - self.get_sk_cost = make_endpoint( - fetch=self._fetch, path="sk", columns=["sk_cost"] - ) - - # his-wlk-cal - self.get_demand = make_endpoint( - fetch=self._fetch, path="his-wlk-cal", columns=["demand"] - ) - self.get_swm_p = make_endpoint( - fetch=self._fetch, path="his-wlk-cal", columns=["swm_p"] - ) - self.get_swm_np = make_endpoint( - fetch=self._fetch, path="his-wlk-cal", columns=["swm_np"] - ) - self.get_jg = make_endpoint( - fetch=self._fetch, path="his-wlk-cal", columns=["jg"] - ) - self.get_jgw1 = make_endpoint( - fetch=self._fetch, path="his-wlk-cal", columns=["jgw1"] - ) - self.get_jgw2 = make_endpoint( - fetch=self._fetch, path="his-wlk-cal", columns=["jgw2"] - ) - self.get_jgm1 = make_endpoint( - fetch=self._fetch, path="his-wlk-cal", columns=["jgm1"] - ) - self.get_jgm2 = make_endpoint( - fetch=self._fetch, path="his-wlk-cal", columns=["jgm2"] - ) - self.get_jgz1 = make_endpoint( - fetch=self._fetch, path="his-wlk-cal", columns=["jgz1"] - ) - self.get_jgz2 = make_endpoint( - fetch=self._fetch, path="his-wlk-cal", columns=["jgz2"] - ) - self.get_jgz3 = make_endpoint( - fetch=self._fetch, path="his-wlk-cal", columns=["jgz3"] - ) - self.get_jga = make_endpoint( - fetch=self._fetch, path="his-wlk-cal", columns=["jga"] - ) - self.get_jgo = make_endpoint( - fetch=self._fetch, path="his-wlk-cal", columns=["jgo"] - ) - self.get_jnwrb = make_endpoint( - fetch=self._fetch, path="his-wlk-cal", columns=["jnwrb"] - ) - self.get_jgm = make_endpoint( - fetch=self._fetch, path="his-wlk-cal", columns=["jgm"] - ) - self.get_wi = make_endpoint( - fetch=self._fetch, path="his-wlk-cal", columns=["wi"] - ) - self.get_pv = make_endpoint( - fetch=self._fetch, path="his-wlk-cal", columns=["pv"] - ) - def fetch( self, endpoint: Endpoint, @@ -153,6 +48,8 @@ def fetch( ): """Query *endpoint* for a date range. + The range is fetched in 30-day chunks. + Args: endpoint: Endpoint name, e.g. ``"price-cost"``. start: Begin of range (``YYYY-MM-DD``, ``YYYY/MM/DD``, or ``YYYYMMDD``). @@ -168,26 +65,14 @@ def fetch( *format* is not supported, a date string cannot be parsed, or *start* is after *end*. """ - select = build_select(get_spec(endpoint), columns) - return self._fetch(endpoint, start, end, select=select, format=format) - - def _fetch( - self, - endpoint: str, - start: str, - end: str, - *, - select: list[str] | None, - format: OutputFormat, - ): - """Fetch *select* columns of *endpoint* in 30-day chunks of the range.""" - validate_format(format) spec = get_spec(endpoint) + validate_format(format) if spec.date_column is None: raise ValueError( f"Endpoint {endpoint!r} has no date column and is not supported yet" ) start_str, end_str = normalize_date_range(start, end) + select = build_select(spec, columns) records: list[dict] = [] for chunk_start, chunk_end in split_date_range(start_str, end_str): diff --git a/src/pse_sdk/_endpoints.py b/src/pse_sdk/_endpoints.py deleted file mode 100644 index bc67264..0000000 --- a/src/pse_sdk/_endpoints.py +++ /dev/null @@ -1,31 +0,0 @@ -from __future__ import annotations - -from collections.abc import Callable - -from pse_sdk._format import OutputFormat - -COMMON_COLUMNS = ["dtime", "period", "business_date", "publication_ts"] - - -def make_endpoint( - fetch: Callable[..., list[dict]], path: str, columns: list[str] | None = None -): - """Create a callable that fetches *columns* of *path* via *fetch*.""" - select = list(COMMON_COLUMNS) + columns if columns else None - - def get(start: str, end: str, format: OutputFormat = "dict") -> list[dict]: - """Query PSE data for a date range. - - Args: - start: Begin of range (``YYYY-MM-DD``, ``YYYY/MM/DD``, or ``YYYYMMDD``). - end: End of range (``YYYY-MM-DD``, ``YYYY/MM/DD``, or ``YYYYMMDD``). - format: Output format — ``"dict"`` (default) or ``"dataframe"``. - - Raises: - TypeError: If *start* or *end* is not a string. - ValueError: If a date string cannot be parsed, or *start* is - after *end*. - """ - return fetch(path, start, end, select=select, format=format) - - return get diff --git a/tests/conftest.py b/tests/conftest.py index ecbb9ad..5f721e5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,12 +1,9 @@ -from unittest.mock import MagicMock, patch +from unittest.mock import patch import pytest @pytest.fixture() -def mock_client(): - with ( - patch("pse_sdk._client.HTTPClient") as mock_http, - patch("pse_sdk._client.make_endpoint", return_value=MagicMock()) as mock_make, - ): - yield mock_http, mock_make +def mock_http(): + with patch("pse_sdk._client.HTTPClient") as mock_http: + yield mock_http diff --git a/tests/test_client.py b/tests/test_client.py index 2ee8a2c..b9c47da 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,51 +1,12 @@ -from unittest.mock import patch - import pandas as pd import pytest from pse_sdk._client import PSEClient -from pse_sdk._endpoints import COMMON_COLUMNS from pse_sdk._registry import REGISTRY -EXPECTED_ENDPOINTS = [ - ("get_cen", "price-cost", ["cen_cost"]), - ("get_ckoeb", "price-cost", ["ckoeb_cost"]), - ("get_ceb_sr", "price-cost", ["ceb_sr_cost"]), - ("get_cor_pp", "price-cost", ["cor_pp_cost"]), - ("get_imb_energy", "price-cost", ["imb_energy"]), - ("get_contracting", "price-cost", ["contracting"]), - ("get_csdac", "csdac-pln", ["csdac_pln"]), - ("get_kb", "krb-rozl", ["kb"]), - ("get_ko", "krb-rozl", ["ko"]), - ("get_kcz", "krb-rozl", ["kcz"]), - ("get_kmb", "kmb-kro-rozl", ["kmb"]), - ("get_kro", "kmb-kro-rozl", ["kro"]), - ("get_sk_d1_fcst", "sk", ["sk_d1_fcst"]), - ("get_sk_d_fcst", "sk", ["sk_d_fcst"]), - ("get_sk_cost", "sk", ["sk_cost"]), - ("get_demand", "his-wlk-cal", ["demand"]), - ("get_swm_p", "his-wlk-cal", ["swm_p"]), - ("get_swm_np", "his-wlk-cal", ["swm_np"]), - ("get_jg", "his-wlk-cal", ["jg"]), - ("get_jgw1", "his-wlk-cal", ["jgw1"]), - ("get_jgw2", "his-wlk-cal", ["jgw2"]), - ("get_jgm1", "his-wlk-cal", ["jgm1"]), - ("get_jgm2", "his-wlk-cal", ["jgm2"]), - ("get_jgz1", "his-wlk-cal", ["jgz1"]), - ("get_jgz2", "his-wlk-cal", ["jgz2"]), - ("get_jgz3", "his-wlk-cal", ["jgz3"]), - ("get_jga", "his-wlk-cal", ["jga"]), - ("get_jgo", "his-wlk-cal", ["jgo"]), - ("get_jnwrb", "his-wlk-cal", ["jnwrb"]), - ("get_jgm", "his-wlk-cal", ["jgm"]), - ("get_wi", "his-wlk-cal", ["wi"]), - ("get_pv", "his-wlk-cal", ["pv"]), -] - class TestPSEClient: - def test_default_timeout(self, mock_client): - mock_http, mock_make = mock_client + def test_default_timeout(self, mock_http): PSEClient() mock_http.assert_called_once_with( base_url="https://api.raporty.pse.pl/api", @@ -53,8 +14,7 @@ def test_default_timeout(self, mock_client): timeout=30, ) - def test_custom_timeout(self, mock_client): - mock_http, mock_make = mock_client + def test_custom_timeout(self, mock_http): PSEClient(timeout=60) mock_http.assert_called_once_with( base_url="https://api.raporty.pse.pl/api", @@ -62,31 +22,9 @@ def test_custom_timeout(self, mock_client): timeout=60, ) - def test_all_endpoints_created(self, mock_client): - pse = PSEClient() - for name, _, _ in EXPECTED_ENDPOINTS: - assert callable(getattr(pse, name)) - - def test_make_endpoint_call_count(self, mock_client): - _, mock_make = mock_client - PSEClient() - assert mock_make.call_count == len(EXPECTED_ENDPOINTS) - - def test_endpoint_paths(self, mock_client): - _, mock_make = mock_client - PSEClient() - paths = [call.kwargs["path"] for call in mock_make.call_args_list] - assert paths == [path for _, path, _ in EXPECTED_ENDPOINTS] - - def test_endpoint_columns(self, mock_client): - _, mock_make = mock_client - PSEClient() - columns = [call.kwargs["columns"] for call in mock_make.call_args_list] - assert columns == [cols for _, _, cols in EXPECTED_ENDPOINTS] - @pytest.fixture() -def pse(mock_client): +def pse(mock_http): client = PSEClient() client.client.fetch_all.return_value = [] return client @@ -155,22 +93,3 @@ def test_invalid_date_range_raises_before_request(self, pse): with pytest.raises(ValueError, match="must not be after"): pse.fetch("price-cost", "2024-02-01", "2024-01-01") pse.client.fetch_all.assert_not_called() - - -class TestGetMethods: - def test_keep_legacy_select(self): - with patch("pse_sdk._client.HTTPClient") as mock_http: - fetch_all = mock_http.return_value.fetch_all - fetch_all.return_value = [] - PSEClient().get_cen("2024-01-01", "2024-01-30") - assert fetch_all.call_args.args == ("price-cost",) - assert fetch_all.call_args.kwargs == { - "select": COMMON_COLUMNS + ["cen_cost"], - "filter_expr": "business_date ge '2024-01-01' and business_date le '2024-01-30'", - } - - def test_invalid_format_raises_before_request(self): - with patch("pse_sdk._client.HTTPClient") as mock_http: - with pytest.raises(ValueError, match="format must be"): - PSEClient().get_cen("2024-01-01", "2024-01-30", format="df") - mock_http.return_value.fetch_all.assert_not_called() diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py deleted file mode 100644 index 9d140e5..0000000 --- a/tests/test_endpoints.py +++ /dev/null @@ -1,57 +0,0 @@ -from unittest.mock import MagicMock - -from pse_sdk._endpoints import COMMON_COLUMNS, make_endpoint - - -class TestMakeEndpoint: - def test_returns_callable(self): - result = make_endpoint(MagicMock(), "price-cost") - assert callable(result) - - def test_select_with_columns(self): - fetch = MagicMock() - fn = make_endpoint(fetch, "price-cost", columns=["cen_cost"]) - fn("2024-01-01", "2024-01-30") - assert fetch.call_args.kwargs["select"] == COMMON_COLUMNS + ["cen_cost"] - - def test_select_none_without_columns(self): - fetch = MagicMock() - fn = make_endpoint(fetch, "price-cost") - fn("2024-01-01", "2024-01-30") - assert fetch.call_args.kwargs["select"] is None - - def test_common_columns_not_mutated(self): - original = list(COMMON_COLUMNS) - make_endpoint(MagicMock(), "price-cost", columns=["extra"]) - assert COMMON_COLUMNS == original - - -class TestEndpointCall: - def test_forwards_arguments(self): - fetch = MagicMock() - fn = make_endpoint(fetch, "price-cost", columns=["cen_cost"]) - fn("2024-01-01", "2024-01-30", format="dataframe") - fetch.assert_called_once_with( - "price-cost", - "2024-01-01", - "2024-01-30", - select=COMMON_COLUMNS + ["cen_cost"], - format="dataframe", - ) - - def test_positional_format(self): - fetch = MagicMock() - fn = make_endpoint(fetch, "price-cost") - fn("2024-01-01", "2024-01-30", "dataframe") - assert fetch.call_args.kwargs["format"] == "dataframe" - - def test_default_format_dict(self): - fetch = MagicMock() - fn = make_endpoint(fetch, "price-cost") - fn("2024-01-01", "2024-01-30") - assert fetch.call_args.kwargs["format"] == "dict" - - def test_returns_fetch_result(self): - fetch = MagicMock(return_value=[{"a": 1}]) - fn = make_endpoint(fetch, "price-cost") - assert fn("2024-01-01", "2024-01-30") == [{"a": 1}] diff --git a/tests/test_registry.py b/tests/test_registry.py index 5303317..141a8a0 100644 --- a/tests/test_registry.py +++ b/tests/test_registry.py @@ -135,17 +135,6 @@ class TestCommittedRegistry: def test_endpoint_literal_matches_registry(self): assert typing.get_args(Endpoint) == tuple(REGISTRY) - def test_contains_client_endpoints(self): - used = { - "price-cost", - "csdac-pln", - "krb-rozl", - "kmb-kro-rozl", - "sk", - "his-wlk-cal", - } - assert used <= set(REGISTRY) - def test_price_cost(self): spec = REGISTRY["price-cost"] assert spec.columns["cen_cost"] == "number" From 42d4a00d033bcbec5723857a68938242ea22a21b Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 16:13:51 +0200 Subject: [PATCH 16/51] docs: drop get_* shortcut from README, add CHANGELOG entry --- CHANGELOG.md | 9 +++++++++ README.md | 3 --- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb33378..dd4a7f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Removed +- **Breaking:** all 32 `get_*` methods of `PSEClient` (`get_cen`, `get_csdac`, + `get_kb`, `get_sk_cost`, `get_demand`, ...) and the internal `make_endpoint` + helper. Use `fetch()` with the endpoint and column instead: + `get_cen(s, e)` → `fetch("price-cost", s, e, columns=["cen_cost"])`. + `fetch()` also returns the `*_utc` meta columns. + ## [1.5.1] - 2026-10-20 ### Added diff --git a/README.md b/README.md index c9112ea..523fb45 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,6 @@ df = client.fetch( columns=["cen_cost", "ckoeb_cost"], format="dataframe", ) - -# Single-column shortcut -records = client.get_cen(start="2024-01-01", end="2024-01-31") ``` ## Date Formats From e9c4016d7da63584ce242d349f348dad24d3af89 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 16:13:51 +0200 Subject: [PATCH 17/51] docs: reorder endpoints table columns, drop Method and Status --- docs/endpoints.md | 243 ++++++++++++++++++++++------------------------ 1 file changed, 116 insertions(+), 127 deletions(-) diff --git a/docs/endpoints.md b/docs/endpoints.md index 0f0f614..74b7505 100644 --- a/docs/endpoints.md +++ b/docs/endpoints.md @@ -17,134 +17,123 @@ fetch(endpoint: Endpoint, start: str, end: str, *, - Rows are filtered on `business_date`. - `ogr-*-head` endpoints have no date column and are not supported yet. -## `get_*` Methods - -Single-column shortcuts with the signature: - -```python -(start: str, end: str, format: OutputFormat = "dict") -``` - -Each returns its column plus `dtime`, `period`, `business_date` and -`publication_ts`. - ## Endpoints Coverage > API reference: `https://api.raporty.pse.pl/api/openapi` -| Endpoint | Description | Column | Method | Status | -|---|---|---|---|---| -| `price-cost` | CEN price | `cen_cost` | `get_cen()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `price-cost` | CKOEB price | `ckoeb_cost` | `get_ckoeb()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `price-cost` | CEBśr price | `ceb_sr_cost` | `get_ceb_sr()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `price-cost` | Operating reserve price (COR) | `cor_pp_cost` | `get_cor_pp()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `price-cost` | Imbalance energy | `imb_energy` | `get_imb_energy()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `price-cost` | KSE contracting status (`short` if < 0, else `long`) | `contracting` | `get_contracting()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `csdac-pln` | SDAC price (PLN) | `csdac_pln` | `get_csdac()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `his-wlk-cal` | KSE power demand | `demand` | `get_demand()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `his-wlk-cal` | National cross-border exchange balance (parallel) | `swm_p` | `get_swm_p()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `his-wlk-cal` | National cross-border exchange balance (non-parallel) | `swm_np` | `get_swm_np()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `his-wlk-cal` | Total generation of scheduling units in KSE (JGw, JGm, JGz, JGa) | `jg` | `get_jg()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `his-wlk-cal` | Total generation of generating scheduling units JGw (ZAK=1) | `jgw1` | `get_jgw1()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `his-wlk-cal` | Total generation of generating scheduling units JGw (ZAK=2) | `jgw2` | `get_jgw2()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `his-wlk-cal` | Total generation of energy storage scheduling units JGm (ZAK=1) | `jgm1` | `get_jgm1()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `his-wlk-cal` | Total generation of energy storage scheduling units JGm (ZAK=2) | `jgm2` | `get_jgm2()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `his-wlk-cal` | Total generation of wind and PV scheduling units JGz (ZAK=1) | `jgz1` | `get_jgz1()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `his-wlk-cal` | Total generation of wind and PV scheduling units JGz (ZAK=2) | `jgz2` | `get_jgz2()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `his-wlk-cal` | Total generation of wind and PV scheduling units JGz (ZAK=3) | `jgz3` | `get_jgz3()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `his-wlk-cal` | Total generation of aggregator scheduling units (JGa) | `jga` | `get_jga()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `his-wlk-cal` | Total consumption of receiving scheduling units (JGo) | `jgo` | `get_jgo()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `his-wlk-cal` | Total generation of units not actively participating in balancing market | `jnwrb` | `get_jnwrb()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `his-wlk-cal` | Total JGm charging power | `jgm` | `get_jgm()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `his-wlk-cal` | Total wind generation | `wi` | `get_wi()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `his-wlk-cal` | Total photovoltaic generation | `pv` | `get_pv()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `kmb-kro-rozl` | Balancing capacity cost (KMB) | `kmb` | `get_kmb()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `kmb-kro-rozl` | Operating reserve cost (KRO) | `kro` | `get_kro()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `krb-rozl` | KB cost | `kb` | `get_kb()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `krb-rozl` | KO cost | `ko` | `get_ko()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `krb-rozl` | KCZ cost | `kcz` | `get_kcz()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `sk` | Forecasted KSE contracting status determined on day d-1 | `sk_d1_fcst` | `get_sk_d1_fcst()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `sk` | Forecasted KSE contracting status determined on day d | `sk_d_fcst` | `get_sk_d_fcst()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `sk` | KSE contracting status | `sk_cost` | `get_sk_cost()` | ![stable](https://img.shields.io/badge/stable-brightgreen) | -| `afrr-status` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `cmbp-tp` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `cmbu-tu` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `codn` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `cor` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `cor-q-1` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `cordmax` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `crb-rozl` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `csire-kpi1` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `csire-kpi2` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `csire-kpi3` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `csire-kpi4` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `csire-kpi5` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `csire-kpi6` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `csire-kpi7` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `csire-kpi8` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `csire-kpi9` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `csire-kpi10` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `eb-rozl` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `en-rozl` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `energy-prices` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `gen-jw` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `his-bil-mocy` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `his-gen-pal` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `it-oeb-rbb` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `it-oeb-rbn` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `it-omb-rbb` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `it-omb-rbn` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `it-opmb-rmb` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `it-ot-rbn` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `it-pp-rbb` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `it-pp-rbn` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `it-use-rbb` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `it-use-rbn` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `kse-load` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `lolp` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `mbp-tp` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `mbu-tu` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `oeb-bpkdbo` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `ogr-b` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `ogr-b-head` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `ogr-d1` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `ogr-d1-head` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `ogr-d2` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `ogr-d2-head` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `ogr-d39` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `ogr-d39-head` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `ogr-m` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `ogr-m-head` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `ogr-oper` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `ogr-oper-head` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `ogr-r` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `ogr-r-head` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `ogr-rmb` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `pdgobpkd` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `pdgopkd` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `pdgsz` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `pdwkseub` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `pk5l-wp` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `poeb-rbb` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `poeb-rbn` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `pomb-rbn` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `popmb-rmb` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `poze-redoze` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `price-fcst` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `przeplywy-mocy` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `pwm-dost-op` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `pwm-igcc-in` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `pwm-rdb` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `pwm-rdn` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `pwm-rdn-rdb` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `rcco2` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `rce-pln` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `ro-prog` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `ro-rozl` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `specwezlow` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `unav-pk5l` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `use-sprz-rbb` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `use-sprz-rbn` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `wsp-wrm` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `zeb-rozl` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | -| `zmb` | | | | ![planned](https://img.shields.io/badge/planned-yellow) | +| Endpoint | Column | Description | +|---|---|---| +| `price-cost` | `cen_cost` | CEN price | +| `price-cost` | `ckoeb_cost` | CKOEB price | +| `price-cost` | `ceb_sr_cost` | CEBśr price | +| `price-cost` | `cor_pp_cost` | Operating reserve price (COR) | +| `price-cost` | `imb_energy` | Imbalance energy | +| `price-cost` | `contracting` | KSE contracting status (`short` if < 0, else `long`) | +| `csdac-pln` | `csdac_pln` | SDAC price (PLN) | +| `his-wlk-cal` | `demand` | KSE power demand | +| `his-wlk-cal` | `swm_p` | National cross-border exchange balance (parallel) | +| `his-wlk-cal` | `swm_np` | National cross-border exchange balance (non-parallel) | +| `his-wlk-cal` | `jg` | Total generation of scheduling units in KSE (JGw, JGm, JGz, JGa) | +| `his-wlk-cal` | `jgw1` | Total generation of generating scheduling units JGw (ZAK=1) | +| `his-wlk-cal` | `jgw2` | Total generation of generating scheduling units JGw (ZAK=2) | +| `his-wlk-cal` | `jgm1` | Total generation of energy storage scheduling units JGm (ZAK=1) | +| `his-wlk-cal` | `jgm2` | Total generation of energy storage scheduling units JGm (ZAK=2) | +| `his-wlk-cal` | `jgz1` | Total generation of wind and PV scheduling units JGz (ZAK=1) | +| `his-wlk-cal` | `jgz2` | Total generation of wind and PV scheduling units JGz (ZAK=2) | +| `his-wlk-cal` | `jgz3` | Total generation of wind and PV scheduling units JGz (ZAK=3) | +| `his-wlk-cal` | `jga` | Total generation of aggregator scheduling units (JGa) | +| `his-wlk-cal` | `jgo` | Total consumption of receiving scheduling units (JGo) | +| `his-wlk-cal` | `jnwrb` | Total generation of units not actively participating in balancing market | +| `his-wlk-cal` | `jgm` | Total JGm charging power | +| `his-wlk-cal` | `wi` | Total wind generation | +| `his-wlk-cal` | `pv` | Total photovoltaic generation | +| `kmb-kro-rozl` | `kmb` | Balancing capacity cost (KMB) | +| `kmb-kro-rozl` | `kro` | Operating reserve cost (KRO) | +| `krb-rozl` | `kb` | KB cost | +| `krb-rozl` | `ko` | KO cost | +| `krb-rozl` | `kcz` | KCZ cost | +| `sk` | `sk_d1_fcst` | Forecasted KSE contracting status determined on day d-1 | +| `sk` | `sk_d_fcst` | Forecasted KSE contracting status determined on day d | +| `sk` | `sk_cost` | KSE contracting status | +| `afrr-status` | | | +| `cmbp-tp` | | | +| `cmbu-tu` | | | +| `codn` | | | +| `cor` | | | +| `cor-q-1` | | | +| `cordmax` | | | +| `crb-rozl` | | | +| `csire-kpi1` | | | +| `csire-kpi2` | | | +| `csire-kpi3` | | | +| `csire-kpi4` | | | +| `csire-kpi5` | | | +| `csire-kpi6` | | | +| `csire-kpi7` | | | +| `csire-kpi8` | | | +| `csire-kpi9` | | | +| `csire-kpi10` | | | +| `eb-rozl` | | | +| `en-rozl` | | | +| `energy-prices` | | | +| `gen-jw` | | | +| `his-bil-mocy` | | | +| `his-gen-pal` | | | +| `it-oeb-rbb` | | | +| `it-oeb-rbn` | | | +| `it-omb-rbb` | | | +| `it-omb-rbn` | | | +| `it-opmb-rmb` | | | +| `it-ot-rbn` | | | +| `it-pp-rbb` | | | +| `it-pp-rbn` | | | +| `it-use-rbb` | | | +| `it-use-rbn` | | | +| `kse-load` | | | +| `lolp` | | | +| `mbp-tp` | | | +| `mbu-tu` | | | +| `oeb-bpkdbo` | | | +| `ogr-b` | | | +| `ogr-b-head` | | | +| `ogr-d1` | | | +| `ogr-d1-head` | | | +| `ogr-d2` | | | +| `ogr-d2-head` | | | +| `ogr-d39` | | | +| `ogr-d39-head` | | | +| `ogr-m` | | | +| `ogr-m-head` | | | +| `ogr-oper` | | | +| `ogr-oper-head` | | | +| `ogr-r` | | | +| `ogr-r-head` | | | +| `ogr-rmb` | | | +| `pdgobpkd` | | | +| `pdgopkd` | | | +| `pdgsz` | | | +| `pdwkseub` | | | +| `pk5l-wp` | | | +| `poeb-rbb` | | | +| `poeb-rbn` | | | +| `pomb-rbn` | | | +| `popmb-rmb` | | | +| `poze-redoze` | | | +| `price-fcst` | | | +| `przeplywy-mocy` | | | +| `pwm-dost-op` | | | +| `pwm-igcc-in` | | | +| `pwm-rdb` | | | +| `pwm-rdn` | | | +| `pwm-rdn-rdb` | | | +| `rcco2` | | | +| `rce-pln` | | | +| `ro-prog` | | | +| `ro-rozl` | | | +| `specwezlow` | | | +| `unav-pk5l` | | | +| `use-sprz-rbb` | | | +| `use-sprz-rbn` | | | +| `wsp-wrm` | | | +| `zeb-rozl` | | | +| `zmb` | | | From 32a3f8564417f92204034c392588e7671cb832e6 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 16:50:50 +0200 Subject: [PATCH 18/51] feat: generate docs/endpoints.md from registry --- docs/endpoints.md | 912 +++++++++++++++++++++++++++++++++++++------- scripts/gen_docs.py | 87 +++++ tests/test_docs.py | 70 ++++ 3 files changed, 935 insertions(+), 134 deletions(-) create mode 100644 scripts/gen_docs.py create mode 100644 tests/test_docs.py diff --git a/docs/endpoints.md b/docs/endpoints.md index 74b7505..ccb12c3 100644 --- a/docs/endpoints.md +++ b/docs/endpoints.md @@ -1,139 +1,783 @@ + # Endpoints -## `fetch()` - -Any endpoint can be queried by name: - -```python -fetch(endpoint: Endpoint, start: str, end: str, *, - columns: list[str] | None = None, format: OutputFormat = "dict") -``` - -- `columns=None` returns all columns. -- `columns=[...]` returns these columns plus the endpoint's meta columns - (`dtime`, `period`, `business_date`, `publication_ts` and their `*_utc` - variants, where present) and key columns (e.g. `resource_code` on `gen-jw`), - so every row stays identifiable. -- Rows are filtered on `business_date`. -- `ogr-*-head` endpoints have no date column and are not supported yet. - -## Endpoints Coverage - > API reference: `https://api.raporty.pse.pl/api/openapi` -| Endpoint | Column | Description | +Data columns of every endpoint, in API order. Meta columns (`dtime`, `period`, +`business_date`, `publication_ts` and their `*_utc` variants) are left out; +`fetch()` returns them with every request. + +| Endpoint | Column | Type | |---|---|---| -| `price-cost` | `cen_cost` | CEN price | -| `price-cost` | `ckoeb_cost` | CKOEB price | -| `price-cost` | `ceb_sr_cost` | CEBśr price | -| `price-cost` | `cor_pp_cost` | Operating reserve price (COR) | -| `price-cost` | `imb_energy` | Imbalance energy | -| `price-cost` | `contracting` | KSE contracting status (`short` if < 0, else `long`) | -| `csdac-pln` | `csdac_pln` | SDAC price (PLN) | -| `his-wlk-cal` | `demand` | KSE power demand | -| `his-wlk-cal` | `swm_p` | National cross-border exchange balance (parallel) | -| `his-wlk-cal` | `swm_np` | National cross-border exchange balance (non-parallel) | -| `his-wlk-cal` | `jg` | Total generation of scheduling units in KSE (JGw, JGm, JGz, JGa) | -| `his-wlk-cal` | `jgw1` | Total generation of generating scheduling units JGw (ZAK=1) | -| `his-wlk-cal` | `jgw2` | Total generation of generating scheduling units JGw (ZAK=2) | -| `his-wlk-cal` | `jgm1` | Total generation of energy storage scheduling units JGm (ZAK=1) | -| `his-wlk-cal` | `jgm2` | Total generation of energy storage scheduling units JGm (ZAK=2) | -| `his-wlk-cal` | `jgz1` | Total generation of wind and PV scheduling units JGz (ZAK=1) | -| `his-wlk-cal` | `jgz2` | Total generation of wind and PV scheduling units JGz (ZAK=2) | -| `his-wlk-cal` | `jgz3` | Total generation of wind and PV scheduling units JGz (ZAK=3) | -| `his-wlk-cal` | `jga` | Total generation of aggregator scheduling units (JGa) | -| `his-wlk-cal` | `jgo` | Total consumption of receiving scheduling units (JGo) | -| `his-wlk-cal` | `jnwrb` | Total generation of units not actively participating in balancing market | -| `his-wlk-cal` | `jgm` | Total JGm charging power | -| `his-wlk-cal` | `wi` | Total wind generation | -| `his-wlk-cal` | `pv` | Total photovoltaic generation | -| `kmb-kro-rozl` | `kmb` | Balancing capacity cost (KMB) | -| `kmb-kro-rozl` | `kro` | Operating reserve cost (KRO) | -| `krb-rozl` | `kb` | KB cost | -| `krb-rozl` | `ko` | KO cost | -| `krb-rozl` | `kcz` | KCZ cost | -| `sk` | `sk_d1_fcst` | Forecasted KSE contracting status determined on day d-1 | -| `sk` | `sk_d_fcst` | Forecasted KSE contracting status determined on day d | -| `sk` | `sk_cost` | KSE contracting status | -| `afrr-status` | | | -| `cmbp-tp` | | | -| `cmbu-tu` | | | -| `codn` | | | -| `cor` | | | -| `cor-q-1` | | | -| `cordmax` | | | -| `crb-rozl` | | | -| `csire-kpi1` | | | -| `csire-kpi2` | | | -| `csire-kpi3` | | | -| `csire-kpi4` | | | -| `csire-kpi5` | | | -| `csire-kpi6` | | | -| `csire-kpi7` | | | -| `csire-kpi8` | | | -| `csire-kpi9` | | | -| `csire-kpi10` | | | -| `eb-rozl` | | | -| `en-rozl` | | | -| `energy-prices` | | | -| `gen-jw` | | | -| `his-bil-mocy` | | | -| `his-gen-pal` | | | -| `it-oeb-rbb` | | | -| `it-oeb-rbn` | | | -| `it-omb-rbb` | | | -| `it-omb-rbn` | | | -| `it-opmb-rmb` | | | -| `it-ot-rbn` | | | -| `it-pp-rbb` | | | -| `it-pp-rbn` | | | -| `it-use-rbb` | | | -| `it-use-rbn` | | | -| `kse-load` | | | -| `lolp` | | | -| `mbp-tp` | | | -| `mbu-tu` | | | -| `oeb-bpkdbo` | | | -| `ogr-b` | | | -| `ogr-b-head` | | | -| `ogr-d1` | | | -| `ogr-d1-head` | | | -| `ogr-d2` | | | -| `ogr-d2-head` | | | -| `ogr-d39` | | | -| `ogr-d39-head` | | | -| `ogr-m` | | | -| `ogr-m-head` | | | -| `ogr-oper` | | | -| `ogr-oper-head` | | | -| `ogr-r` | | | -| `ogr-r-head` | | | -| `ogr-rmb` | | | -| `pdgobpkd` | | | -| `pdgopkd` | | | -| `pdgsz` | | | -| `pdwkseub` | | | -| `pk5l-wp` | | | -| `poeb-rbb` | | | -| `poeb-rbn` | | | -| `pomb-rbn` | | | -| `popmb-rmb` | | | -| `poze-redoze` | | | -| `price-fcst` | | | -| `przeplywy-mocy` | | | -| `pwm-dost-op` | | | -| `pwm-igcc-in` | | | -| `pwm-rdb` | | | -| `pwm-rdn` | | | -| `pwm-rdn-rdb` | | | -| `rcco2` | | | -| `rce-pln` | | | -| `ro-prog` | | | -| `ro-rozl` | | | -| `specwezlow` | | | -| `unav-pk5l` | | | -| `use-sprz-rbb` | | | -| `use-sprz-rbn` | | | -| `wsp-wrm` | | | -| `zeb-rozl` | | | -| `zmb` | | | +| `afrr-status` | `event_time` | string | +| `afrr-status` | `event_time_utc` | string | +| `afrr-status` | `afrr_status` | integer | +| `afrr-status` | `reason_code` | integer | +| `afrr-status` | `reason` | string | +| `cmbp-tp` | `onmb` | string | +| `cmbp-tp` | `fcr_g` | number | +| `cmbp-tp` | `fcr_d` | number | +| `cmbp-tp` | `afrr_g` | number | +| `cmbp-tp` | `afrr_d` | number | +| `cmbp-tp` | `mfrrd_g` | number | +| `cmbp-tp` | `mfrrd_d` | number | +| `cmbp-tp` | `rr_d` | number | +| `cmbp-tp` | `rr_g` | number | +| `cmbu-tu` | `fcr_g` | number | +| `cmbu-tu` | `fcr_d` | number | +| `cmbu-tu` | `afrr_g` | number | +| `cmbu-tu` | `afrr_d` | number | +| `cmbu-tu` | `mfrrd_g` | number | +| `cmbu-tu` | `mfrrd_d` | number | +| `codn` | `period_from_date` | string | +| `codn` | `period_to_date` | string | +| `codn` | `price` | number | +| `cor` | `cor_cost` | number | +| `cor` | `cor_fcst` | number | +| `cor-q-1` | `period_from_date` | string | +| `cor-q-1` | `period_to_date` | string | +| `cor-q-1` | `cor_excl` | number | +| `cor-q-1` | `cor_inc` | number | +| `cordmax` | `cordmax_fcst` | number | +| `cordmax` | `cordmax_cost` | number | +| `crb-rozl` | `cen_cost` | number | +| `crb-rozl` | `ckoeb_cost` | number | +| `crb-rozl` | `ceb_pp_cost` | number | +| `crb-rozl` | `ceb_sr_cost` | number | +| `crb-rozl` | `ceb_sr_afrrg_cost` | number | +| `crb-rozl` | `ceb_sr_afrrd_cost` | number | +| `csdac-pln` | `csdac_pln` | number | +| `csire-kpi1` | `row_no` | string | +| `csire-kpi1` | `eic_code` | string | +| `csire-kpi1` | `tax_id` | string | +| `csire-kpi1` | `name` | string | +| `csire-kpi1` | `date_1d` | string | +| `csire-kpi1` | `completeness_1d` | number | +| `csire-kpi1` | `date_from_5d` | string | +| `csire-kpi1` | `date_to_5d` | string | +| `csire-kpi1` | `completeness_5d` | number | +| `csire-kpi1` | `date_from_1m` | string | +| `csire-kpi1` | `date_to_1m` | string | +| `csire-kpi1` | `completeness_1m` | number | +| `csire-kpi1` | `date_from_6m` | string | +| `csire-kpi1` | `date_to_6m` | string | +| `csire-kpi1` | `completeness_6m` | number | +| `csire-kpi2` | `row_no` | string | +| `csire-kpi2` | `eic_code` | string | +| `csire-kpi2` | `tax_id` | string | +| `csire-kpi2` | `name` | string | +| `csire-kpi2` | `date_from_1m` | string | +| `csire-kpi2` | `date_to_1m` | string | +| `csire-kpi2` | `completeness_1m` | number | +| `csire-kpi2` | `date_from_6m` | string | +| `csire-kpi2` | `date_to_6m` | string | +| `csire-kpi2` | `completeness_6m` | number | +| `csire-kpi3` | `row_no` | string | +| `csire-kpi3` | `eic_code` | string | +| `csire-kpi3` | `tax_id` | string | +| `csire-kpi3` | `name` | string | +| `csire-kpi3` | `date_from_1m` | string | +| `csire-kpi3` | `date_to_1m` | string | +| `csire-kpi3` | `completeness_1m` | number | +| `csire-kpi3` | `date_from_6m` | string | +| `csire-kpi3` | `date_to_6m` | string | +| `csire-kpi3` | `completeness_6m` | number | +| `csire-kpi4` | `row_no` | string | +| `csire-kpi4` | `eic_code` | string | +| `csire-kpi4` | `tax_id` | string | +| `csire-kpi4` | `name` | string | +| `csire-kpi4` | `date_1d` | string | +| `csire-kpi4` | `completeness_1d` | number | +| `csire-kpi4` | `date_from_5d` | string | +| `csire-kpi4` | `date_to_5d` | string | +| `csire-kpi4` | `completeness_5d` | number | +| `csire-kpi4` | `date_from_1m` | string | +| `csire-kpi4` | `date_to_1m` | string | +| `csire-kpi4` | `completeness_1m` | number | +| `csire-kpi4` | `date_from_6m` | string | +| `csire-kpi4` | `date_to_6m` | string | +| `csire-kpi4` | `completeness_6m` | number | +| `csire-kpi5` | `row_no` | string | +| `csire-kpi5` | `eic_code` | string | +| `csire-kpi5` | `tax_id` | string | +| `csire-kpi5` | `name` | string | +| `csire-kpi5` | `date_1d` | string | +| `csire-kpi5` | `timeliness_1d` | number | +| `csire-kpi5` | `date_from_5d` | string | +| `csire-kpi5` | `date_to_5d` | string | +| `csire-kpi5` | `timeliness_5d` | number | +| `csire-kpi5` | `date_from_1m` | string | +| `csire-kpi5` | `date_to_1m` | string | +| `csire-kpi5` | `timeliness_1m` | number | +| `csire-kpi5` | `date_from_6m` | string | +| `csire-kpi5` | `date_to_6m` | string | +| `csire-kpi5` | `timeliness_6m` | number | +| `csire-kpi6` | `row_no` | string | +| `csire-kpi6` | `eic_code` | string | +| `csire-kpi6` | `tax_id` | string | +| `csire-kpi6` | `name` | string | +| `csire-kpi6` | `date_from_1m` | string | +| `csire-kpi6` | `date_to_1m` | string | +| `csire-kpi6` | `timeliness_1m` | number | +| `csire-kpi6` | `date_from_6m` | string | +| `csire-kpi6` | `date_to_6m` | string | +| `csire-kpi6` | `timeliness_6m` | number | +| `csire-kpi7` | `row_no` | string | +| `csire-kpi7` | `eic_code` | string | +| `csire-kpi7` | `tax_id` | string | +| `csire-kpi7` | `name` | string | +| `csire-kpi7` | `date_from_1m` | string | +| `csire-kpi7` | `date_to_1m` | string | +| `csire-kpi7` | `timeliness_1m` | number | +| `csire-kpi7` | `date_from_6m` | string | +| `csire-kpi7` | `date_to_6m` | string | +| `csire-kpi7` | `timeliness_6m` | number | +| `csire-kpi8` | `row_no` | string | +| `csire-kpi8` | `eic_code` | string | +| `csire-kpi8` | `tax_id` | string | +| `csire-kpi8` | `name` | string | +| `csire-kpi8` | `date_1d` | string | +| `csire-kpi8` | `timeliness_1d` | number | +| `csire-kpi8` | `date_from_5d` | string | +| `csire-kpi8` | `date_to_5d` | string | +| `csire-kpi8` | `timeliness_5d` | number | +| `csire-kpi8` | `date_from_1m` | string | +| `csire-kpi8` | `date_to_1m` | string | +| `csire-kpi8` | `timeliness_1m` | number | +| `csire-kpi8` | `date_from_6m` | string | +| `csire-kpi8` | `date_to_6m` | string | +| `csire-kpi8` | `timeliness_6m` | number | +| `csire-kpi9` | `row_no` | string | +| `csire-kpi9` | `eic_code` | string | +| `csire-kpi9` | `tax_id` | string | +| `csire-kpi9` | `name` | string | +| `csire-kpi9` | `date_1d` | string | +| `csire-kpi9` | `correctness_1d` | number | +| `csire-kpi9` | `date_from_5d` | string | +| `csire-kpi9` | `date_to_5d` | string | +| `csire-kpi9` | `correctness_5d` | number | +| `csire-kpi9` | `date_from_1m` | string | +| `csire-kpi9` | `date_to_1m` | string | +| `csire-kpi9` | `correctness_1m` | number | +| `csire-kpi9` | `date_from_6m` | string | +| `csire-kpi9` | `date_to_6m` | string | +| `csire-kpi9` | `correctness_6m` | number | +| `csire-kpi10` | `row_no` | string | +| `csire-kpi10` | `eic_code` | string | +| `csire-kpi10` | `tax_id` | string | +| `csire-kpi10` | `name` | string | +| `csire-kpi10` | `date_from_1m` | string | +| `csire-kpi10` | `date_to_1m` | string | +| `csire-kpi10` | `timeliness_1m` | number | +| `csire-kpi10` | `date_from_6m` | string | +| `csire-kpi10` | `date_to_6m` | string | +| `csire-kpi10` | `timeliness_6m` | number | +| `eb-rozl` | `eb_d_pp` | number | +| `eb-rozl` | `eb_w_pp` | number | +| `eb-rozl` | `eb_afrrg` | number | +| `eb-rozl` | `eb_afrrd` | number | +| `en-rozl` | `en_d` | number | +| `en-rozl` | `en_w` | number | +| `en-rozl` | `balance` | number | +| `energy-prices` | `cor_cost` | number | +| `energy-prices` | `ceb_pp_cost` | number | +| `energy-prices` | `ceb_sr_cost` | number | +| `energy-prices` | `cen_cost` | number | +| `energy-prices` | `csdac_pln` | number | +| `energy-prices` | `sk_cost` | number | +| `energy-prices` | `balance` | number | +| `energy-prices` | `balance_power` | number | +| `energy-prices` | `sk_cost_power` | number | +| `gen-jw` | `resource_code` | string | +| `gen-jw` | `power_plant` | string | +| `gen-jw` | `operating_mode` | string | +| `gen-jw` | `operating_mode_eng` | string | +| `gen-jw` | `value` | number | +| `his-bil-mocy` | `peak` | string | +| `his-bil-mocy` | `peak_hour` | string | +| `his-bil-mocy` | `demand` | number | +| `his-bil-mocy` | `swm` | number | +| `his-bil-mocy` | `pos_jg` | number | +| `his-bil-mocy` | `pos_prb` | number | +| `his-bil-mocy` | `pos_sum` | number | +| `his-bil-mocy` | `rk_jg` | number | +| `his-bil-mocy` | `rk_prb` | number | +| `his-bil-mocy` | `rs_jg` | number | +| `his-bil-mocy` | `rs_prb` | number | +| `his-bil-mocy` | `rb_jg` | number | +| `his-bil-mocy` | `rb_prb` | number | +| `his-bil-mocy` | `ra_jg` | number | +| `his-bil-mocy` | `ra_prb` | number | +| `his-bil-mocy` | `we_jg` | number | +| `his-bil-mocy` | `we_prb` | number | +| `his-bil-mocy` | `ciep_jg` | number | +| `his-bil-mocy` | `ciep_prb` | number | +| `his-bil-mocy` | `inw_jg` | number | +| `his-bil-mocy` | `inw_prb` | number | +| `his-bil-mocy` | `pdysp_jg` | number | +| `his-bil-mocy` | `pdysp_prb` | number | +| `his-bil-mocy` | `sub` | number | +| `his-bil-mocy` | `sub_jg` | number | +| `his-bil-mocy` | `sub_prb` | number | +| `his-bil-mocy` | `rez` | number | +| `his-bil-mocy` | `rez_jg` | number | +| `his-bil-mocy` | `rez_jgw` | number | +| `his-bil-mocy` | `rez_jgw_wir` | number | +| `his-bil-mocy` | `rez_jgw_zim` | number | +| `his-bil-mocy` | `rez_jgm` | number | +| `his-bil-mocy` | `rez_jgz` | number | +| `his-bil-mocy` | `rez_jga` | number | +| `his-bil-mocy` | `rez_prb` | number | +| `his-bil-mocy` | `obc_sum` | number | +| `his-bil-mocy` | `obc_jg` | number | +| `his-bil-mocy` | `obc_prb` | number | +| `his-bil-mocy` | `ub_sum` | number | +| `his-bil-mocy` | `pdysp_sum` | number | +| `his-gen-pal` | `date` | string | +| `his-gen-pal` | `alias_entsoe` | string | +| `his-gen-pal` | `value` | string | +| `his-gen-pal-sire` | `date` | string | +| `his-gen-pal-sire` | `alias_entsoe` | string | +| `his-gen-pal-sire` | `value` | string | +| `his-gen-pal-sire` | `alias_sire` | string | +| `his-wlk-cal` | `demand` | number | +| `his-wlk-cal` | `swm_p` | number | +| `his-wlk-cal` | `swm_np` | number | +| `his-wlk-cal` | `jg` | number | +| `his-wlk-cal` | `jgo` | string | +| `his-wlk-cal` | `jgw1` | number | +| `his-wlk-cal` | `jgw2` | number | +| `his-wlk-cal` | `jgm1` | number | +| `his-wlk-cal` | `jgm2` | number | +| `his-wlk-cal` | `jgz1` | number | +| `his-wlk-cal` | `jgz2` | number | +| `his-wlk-cal` | `jgz3` | number | +| `his-wlk-cal` | `jga` | number | +| `his-wlk-cal` | `jnwrb` | number | +| `his-wlk-cal` | `jgm` | number | +| `his-wlk-cal` | `wi` | number | +| `his-wlk-cal` | `pv` | number | +| `it-oeb-rbb` | `iteration_no` | integer | +| `it-oeb-rbb` | `time_iteration_from_ts` | string | +| `it-oeb-rbb` | `time_iteration_from_ts_utc` | string | +| `it-oeb-rbb` | `time_iteration_to_ts` | string | +| `it-oeb-rbb` | `time_iteration_to_ts_utc` | string | +| `it-oeb-rbb` | `last_doc_dtime` | string | +| `it-oeb-rbb` | `last_doc_dtime_utc` | string | +| `it-oeb-rbb` | `time_period_from_dtime` | string | +| `it-oeb-rbb` | `time_period_from_dtime_utc` | string | +| `it-oeb-rbb` | `time_period_to_dtime` | string | +| `it-oeb-rbb` | `time_period_to_dtime_utc` | string | +| `it-oeb-rbn` | `iteration_no` | integer | +| `it-oeb-rbn` | `iteration_type` | string | +| `it-oeb-rbn` | `time_iteration_from_ts` | string | +| `it-oeb-rbn` | `time_iteration_from_ts_utc` | string | +| `it-oeb-rbn` | `time_iteration_to_ts` | string | +| `it-oeb-rbn` | `time_iteration_to_ts_utc` | string | +| `it-oeb-rbn` | `last_doc_dtime` | string | +| `it-oeb-rbn` | `last_doc_dtime_utc` | string | +| `it-omb-rbb` | `iteration_no` | integer | +| `it-omb-rbb` | `time_iteration_from_ts` | string | +| `it-omb-rbb` | `time_iteration_from_ts_utc` | string | +| `it-omb-rbb` | `time_iteration_to_ts` | string | +| `it-omb-rbb` | `time_iteration_to_ts_utc` | string | +| `it-omb-rbb` | `last_doc_dtime` | string | +| `it-omb-rbb` | `last_doc_dtime_utc` | string | +| `it-omb-rbb` | `time_period_from_dtime` | string | +| `it-omb-rbb` | `time_period_from_dtime_utc` | string | +| `it-omb-rbb` | `time_period_to_dtime` | string | +| `it-omb-rbb` | `time_period_to_dtime_utc` | string | +| `it-omb-rbn` | `iteration_no` | integer | +| `it-omb-rbn` | `iteration_type` | string | +| `it-omb-rbn` | `time_iteration_from_ts` | string | +| `it-omb-rbn` | `time_iteration_from_ts_utc` | string | +| `it-omb-rbn` | `time_iteration_to_ts` | string | +| `it-omb-rbn` | `time_iteration_to_ts_utc` | string | +| `it-omb-rbn` | `last_doc_dtime` | string | +| `it-omb-rbn` | `last_doc_dtime_utc` | string | +| `it-opmb-rmb` | `iteration_no` | integer | +| `it-opmb-rmb` | `iteration_type` | string | +| `it-opmb-rmb` | `time_iteration_from_ts` | string | +| `it-opmb-rmb` | `time_iteration_from_ts_utc` | string | +| `it-opmb-rmb` | `time_iteration_to_ts` | string | +| `it-opmb-rmb` | `time_iteration_to_ts_utc` | string | +| `it-opmb-rmb` | `last_doc_dtime` | string | +| `it-opmb-rmb` | `last_doc_dtime_utc` | string | +| `it-ot-rbn` | `iteration_no` | integer | +| `it-ot-rbn` | `iteration_type` | string | +| `it-ot-rbn` | `time_iteration_from_ts` | string | +| `it-ot-rbn` | `time_iteration_from_ts_utc` | string | +| `it-ot-rbn` | `time_iteration_to_ts` | string | +| `it-ot-rbn` | `time_iteration_to_ts_utc` | string | +| `it-ot-rbn` | `last_doc_dtime` | string | +| `it-ot-rbn` | `last_doc_dtime_utc` | string | +| `it-pp-rbb` | `iteration_no` | integer | +| `it-pp-rbb` | `time_iteration_from_ts` | string | +| `it-pp-rbb` | `time_iteration_from_ts_utc` | string | +| `it-pp-rbb` | `time_iteration_to_ts` | string | +| `it-pp-rbb` | `time_iteration_to_ts_utc` | string | +| `it-pp-rbb` | `last_doc_dtime` | string | +| `it-pp-rbb` | `last_doc_dtime_utc` | string | +| `it-pp-rbb` | `time_period_from_dtime` | string | +| `it-pp-rbb` | `time_period_from_dtime_utc` | string | +| `it-pp-rbb` | `time_period_to_dtime` | string | +| `it-pp-rbb` | `time_period_to_dtime_utc` | string | +| `it-pp-rbn` | `iteration_no` | integer | +| `it-pp-rbn` | `iteration_type` | string | +| `it-pp-rbn` | `time_iteration_from_ts` | string | +| `it-pp-rbn` | `time_iteration_from_ts_utc` | string | +| `it-pp-rbn` | `time_iteration_to_ts` | string | +| `it-pp-rbn` | `time_iteration_to_ts_utc` | string | +| `it-pp-rbn` | `last_doc_dtime` | string | +| `it-pp-rbn` | `last_doc_dtime_utc` | string | +| `it-use-rbb` | `iteration_no` | integer | +| `it-use-rbb` | `time_iteration_from_ts` | string | +| `it-use-rbb` | `time_iteration_from_ts_utc` | string | +| `it-use-rbb` | `time_iteration_to_ts` | string | +| `it-use-rbb` | `time_iteration_to_ts_utc` | string | +| `it-use-rbb` | `last_doc_dtime` | string | +| `it-use-rbb` | `last_doc_dtime_utc` | string | +| `it-use-rbb` | `time_period_from_dtime` | string | +| `it-use-rbb` | `time_period_from_dtime_utc` | string | +| `it-use-rbb` | `time_period_to_dtime` | string | +| `it-use-rbb` | `time_period_to_dtime_utc` | string | +| `it-use-rbb` | `volume` | number | +| `it-use-rbn` | `iteration_no` | integer | +| `it-use-rbn` | `iteration_type` | string | +| `it-use-rbn` | `time_iteration_from_ts` | string | +| `it-use-rbn` | `time_iteration_from_ts_utc` | string | +| `it-use-rbn` | `time_iteration_to_ts` | string | +| `it-use-rbn` | `time_iteration_to_ts_utc` | string | +| `it-use-rbn` | `last_doc_dtime` | string | +| `it-use-rbn` | `last_doc_dtime_utc` | string | +| `kmb-kro-rozl` | `kmb` | number | +| `kmb-kro-rozl` | `kro` | number | +| `krb-rozl` | `kcz` | number | +| `krb-rozl` | `kb` | number | +| `krb-rozl` | `ko` | number | +| `kse-load` | `load_actual` | number | +| `kse-load` | `load_fcst` | number | +| `lolp` | `ojnz_id` | string | +| `lolp` | `b0` | number | +| `lolp` | `b1` | number | +| `lolp` | `b2` | number | +| `lolp` | `b3` | number | +| `lolp` | `b4` | number | +| `lolp` | `b5` | number | +| `lolp` | `b6` | number | +| `lolp` | `b7` | number | +| `lolp` | `b8` | number | +| `lolp` | `b9` | number | +| `lolp` | `p0` | number | +| `lolp` | `p1` | number | +| `lolp` | `p2` | number | +| `lolp` | `p3` | number | +| `lolp` | `p4` | number | +| `lolp` | `p5` | number | +| `lolp` | `p6` | number | +| `lolp` | `p7` | number | +| `lolp` | `p8` | number | +| `lolp` | `p9` | number | +| `mbp-tp` | `onmbp` | string | +| `mbp-tp` | `fcr_g` | number | +| `mbp-tp` | `fcr_d` | number | +| `mbp-tp` | `afrr_g` | number | +| `mbp-tp` | `afrr_d` | number | +| `mbp-tp` | `mfrrd_g` | number | +| `mbp-tp` | `mfrrd_d` | number | +| `mbp-tp` | `rr_g` | number | +| `mbp-tp` | `rr_d` | number | +| `mbu-tu` | `fcr_g` | number | +| `mbu-tu` | `fcr_d` | number | +| `mbu-tu` | `afrr_g` | number | +| `mbu-tu` | `afrr_d` | number | +| `mbu-tu` | `mfrrd_g` | number | +| `mbu-tu` | `mfrrd_d` | number | +| `oeb-bpkdbo` | `ofp` | number | +| `oeb-bpkdbo` | `ofc` | number | +| `oeb-bpkdbo` | `cdo` | number | +| `oeb-bpkdbo` | `activ_direction` | string | +| `oeb-bpkdbo` | `sort` | integer | +| `ogr-b` | `node` | string | +| `ogr-b` | `resource_name` | string | +| `ogr-b` | `resource_code` | string | +| `ogr-b` | `direction` | string | +| `ogr-b` | `pol_min_power_of_unit` | number | +| `ogr-b` | `pol_min_power_of_unit_plant` | integer | +| `ogr-b` | `pol_max_power_of_unit_plant` | number | +| `ogr-b` | `pol_max_power_of_unit` | integer | +| `ogr-b` | `from_dtime` | string | +| `ogr-b` | `from_dtime_utc` | string | +| `ogr-b` | `to_dtime` | string | +| `ogr-b` | `to_dtime_utc` | string | +| `ogr-b` | `time_conditions` | string | +| `ogr-b` | `limiting_element` | string | +| `ogr-b` | `add_cond` | string | +| `ogr-b` | `year` | number | +| `ogr-b` | `month` | number | +| `ogr-b` | `is_active` | boolean | +| `ogr-b` | `valid_from_ts_utc` | string | +| `ogr-b` | `valid_to_ts_utc` | string | +| `ogr-b` | `version` | string | +| `ogr-b-head` | `title` | string | +| `ogr-b-head` | `remarks` | string | +| `ogr-b-head` | `version` | string | +| `ogr-b-head` | `is_active` | boolean | +| `ogr-d1` | `node` | string | +| `ogr-d1` | `resource_name` | string | +| `ogr-d1` | `resource_code` | string | +| `ogr-d1` | `direction` | string | +| `ogr-d1` | `pol_min_power_of_unit` | number | +| `ogr-d1` | `pol_min_power_of_unit_plant` | integer | +| `ogr-d1` | `pol_max_power_of_unit_plant` | number | +| `ogr-d1` | `pol_max_power_of_unit` | integer | +| `ogr-d1` | `from_dtime` | string | +| `ogr-d1` | `from_dtime_utc` | string | +| `ogr-d1` | `to_dtime` | string | +| `ogr-d1` | `to_dtime_utc` | string | +| `ogr-d1` | `time_conditions` | string | +| `ogr-d1` | `limiting_element` | string | +| `ogr-d1` | `add_cond` | string | +| `ogr-d1-head` | `title` | string | +| `ogr-d1-head` | `remarks` | string | +| `ogr-d2` | `node` | string | +| `ogr-d2` | `resource_name` | string | +| `ogr-d2` | `resource_code` | string | +| `ogr-d2` | `direction` | string | +| `ogr-d2` | `pol_min_power_of_unit` | number | +| `ogr-d2` | `pol_min_power_of_unit_plant` | integer | +| `ogr-d2` | `pol_max_power_of_unit_plant` | number | +| `ogr-d2` | `pol_max_power_of_unit` | integer | +| `ogr-d2` | `from_dtime` | string | +| `ogr-d2` | `from_dtime_utc` | string | +| `ogr-d2` | `to_dtime` | string | +| `ogr-d2` | `to_dtime_utc` | string | +| `ogr-d2` | `time_conditions` | string | +| `ogr-d2` | `limiting_element` | string | +| `ogr-d2` | `add_cond` | string | +| `ogr-d2-head` | `title` | string | +| `ogr-d2-head` | `remarks` | string | +| `ogr-d39` | `node` | string | +| `ogr-d39` | `resource_name` | string | +| `ogr-d39` | `resource_code` | string | +| `ogr-d39` | `direction` | string | +| `ogr-d39` | `pol_min_power_of_unit` | number | +| `ogr-d39` | `pol_min_power_of_unit_plant` | integer | +| `ogr-d39` | `pol_max_power_of_unit_plant` | number | +| `ogr-d39` | `pol_max_power_of_unit` | integer | +| `ogr-d39` | `from_dtime` | string | +| `ogr-d39` | `from_dtime_utc` | string | +| `ogr-d39` | `to_dtime` | string | +| `ogr-d39` | `to_dtime_utc` | string | +| `ogr-d39` | `time_conditions` | string | +| `ogr-d39` | `limiting_element` | string | +| `ogr-d39` | `add_cond` | string | +| `ogr-d39-head` | `title` | string | +| `ogr-d39-head` | `from_period` | string | +| `ogr-d39-head` | `to_period` | string | +| `ogr-d39-head` | `remarks` | string | +| `ogr-m` | `node` | string | +| `ogr-m` | `resource_name` | string | +| `ogr-m` | `resource_code` | string | +| `ogr-m` | `direction` | string | +| `ogr-m` | `pol_min_power_of_unit` | number | +| `ogr-m` | `pol_min_power_of_unit_plant` | integer | +| `ogr-m` | `pol_max_power_of_unit_plant` | number | +| `ogr-m` | `pol_max_power_of_unit` | integer | +| `ogr-m` | `from_dtime` | string | +| `ogr-m` | `from_dtime_utc` | string | +| `ogr-m` | `to_dtime` | string | +| `ogr-m` | `to_dtime_utc` | string | +| `ogr-m` | `time_conditions` | string | +| `ogr-m` | `limiting_element` | string | +| `ogr-m` | `add_cond` | string | +| `ogr-m-head` | `title` | string | +| `ogr-m-head` | `from_period` | string | +| `ogr-m-head` | `to_period` | string | +| `ogr-m-head` | `remarks` | string | +| `ogr-oper` | `node` | string | +| `ogr-oper` | `resource_name` | string | +| `ogr-oper` | `resource_code` | string | +| `ogr-oper` | `direction` | string | +| `ogr-oper` | `pol_min_power_of_unit` | number | +| `ogr-oper` | `pol_min_power_of_unit_plant` | integer | +| `ogr-oper` | `pol_max_power_of_unit_plant` | number | +| `ogr-oper` | `pol_max_power_of_unit` | integer | +| `ogr-oper` | `from_dtime` | string | +| `ogr-oper` | `from_dtime_utc` | string | +| `ogr-oper` | `to_dtime` | string | +| `ogr-oper` | `to_dtime_utc` | string | +| `ogr-oper` | `time_conditions` | string | +| `ogr-oper` | `limiting_element` | string | +| `ogr-oper` | `add_cond` | string | +| `ogr-oper-head` | `title` | string | +| `ogr-oper-head` | `remarks` | string | +| `ogr-r` | `node` | string | +| `ogr-r` | `resource_name` | string | +| `ogr-r` | `resource_code` | string | +| `ogr-r` | `direction` | string | +| `ogr-r` | `pol_min_power_of_unit` | number | +| `ogr-r` | `pol_min_power_of_unit_plant` | integer | +| `ogr-r` | `pol_max_power_of_unit_plant` | number | +| `ogr-r` | `pol_max_power_of_unit` | integer | +| `ogr-r` | `from_dtime` | string | +| `ogr-r` | `from_dtime_utc` | string | +| `ogr-r` | `to_dtime` | string | +| `ogr-r` | `to_dtime_utc` | string | +| `ogr-r` | `time_conditions` | string | +| `ogr-r` | `limiting_element` | string | +| `ogr-r` | `add_cond` | string | +| `ogr-r` | `year` | number | +| `ogr-r` | `month` | number | +| `ogr-r-head` | `title` | string | +| `ogr-r-head` | `from_period` | string | +| `ogr-r-head` | `to_period` | string | +| `ogr-r-head` | `remarks` | string | +| `ogr-rmb` | `id` | integer | +| `ogr-rmb` | `node` | string | +| `ogr-rmb` | `from_dtime` | string | +| `ogr-rmb` | `from_dtime_utc` | string | +| `ogr-rmb` | `to_dtime` | string | +| `ogr-rmb` | `to_dtime_utc` | string | +| `ogr-rmb` | `unit_name` | string | +| `ogr-rmb` | `direction` | string | +| `ogr-rmb` | `sminp` | integer | +| `ogr-rmb` | `smaxp` | integer | +| `ogr-rmb` | `smaxp_sminp` | integer | +| `pdgobpkd` | `kse_pow_dem` | number | +| `pdgobpkd` | `capacity_jgw_jgm_jgz_jga` | number | +| `pdgobpkd` | `capacity_jgw_zak_1` | number | +| `pdgobpkd` | `capacity_jgw_zak_2` | number | +| `pdgobpkd` | `capacity_jgm_zak_1` | number | +| `pdgobpkd` | `capacity_jgm_zak_2` | number | +| `pdgobpkd` | `capacity_jgz_zak_1` | number | +| `pdgobpkd` | `capacity_jgz_zak_2` | number | +| `pdgobpkd` | `capacity_jgz_zak_3` | number | +| `pdgobpkd` | `capacity_jga` | number | +| `pdgobpkd` | `capacity_jgo` | number | +| `pdgobpkd` | `gen_jgw_jgm_jgz_jga` | number | +| `pdgobpkd` | `gen_jgw_zak_1` | number | +| `pdgobpkd` | `gen_jgw_zak_2` | number | +| `pdgobpkd` | `gen_jgm_zak_1` | number | +| `pdgobpkd` | `gen_jgm_zak_2` | number | +| `pdgobpkd` | `gen_jgz_zak_1` | number | +| `pdgobpkd` | `gen_jgz_zak_2` | number | +| `pdgobpkd` | `gen_jgz_zak_3` | number | +| `pdgobpkd` | `gen_jga` | number | +| `pdgobpkd` | `gen_jgo` | number | +| `pdgobpkd` | `gen_not_activ_part` | number | +| `pdgobpkd` | `ogr_mwe` | number | +| `pdgobpkd` | `tot_jgm_char_pow` | number | +| `pdgobpkd` | `gen_wi` | number | +| `pdgobpkd` | `gen_fv` | number | +| `pdgobpkd` | `dom_balance_exchange_non_par` | number | +| `pdgobpkd` | `dom_balance_exchange_par` | number | +| `pdgobpkd` | `rez_over_demand` | number | +| `pdgobpkd` | `rez_under` | number | +| `pdgopkd` | `kse_pow_dem` | number | +| `pdgopkd` | `capacity_jgw_jgm_jgz_jga` | number | +| `pdgopkd` | `capacity_jgw_zak_1` | number | +| `pdgopkd` | `capacity_jgw_zak_2` | number | +| `pdgopkd` | `capacity_jgm_zak_1` | number | +| `pdgopkd` | `capacity_jgm_zak_2` | number | +| `pdgopkd` | `capacity_jgz_zak_1` | number | +| `pdgopkd` | `capacity_jgz_zak_2` | number | +| `pdgopkd` | `capacity_jgz_zak_3` | number | +| `pdgopkd` | `capacity_jga` | number | +| `pdgopkd` | `capacity_jgo` | number | +| `pdgopkd` | `gen_jgw_jgm_jgz_jga` | number | +| `pdgopkd` | `gen_jgw_zak_1` | number | +| `pdgopkd` | `gen_jgw_zak_2` | number | +| `pdgopkd` | `gen_jgm_zak_1` | number | +| `pdgopkd` | `gen_jgm_zak_2` | number | +| `pdgopkd` | `gen_jgz_zak_1` | number | +| `pdgopkd` | `gen_jgz_zak_2` | number | +| `pdgopkd` | `gen_jgz_zak_3` | number | +| `pdgopkd` | `gen_jga` | number | +| `pdgopkd` | `gen_jgo` | number | +| `pdgopkd` | `gen_not_activ_part` | number | +| `pdgopkd` | `ogr_mwe` | number | +| `pdgopkd` | `tot_jgm_char_pow` | number | +| `pdgopkd` | `gen_wi` | number | +| `pdgopkd` | `gen_fv` | number | +| `pdgopkd` | `dom_balance_exchange_non_par` | number | +| `pdgopkd` | `dom_balance_exchange_par` | number | +| `pdgopkd` | `rez_over_demand` | number | +| `pdgopkd` | `rez_under` | number | +| `pdgsz` | `usage_fcst` | integer | +| `pdgsz` | `total_power_demand` | number | +| `pdgsz` | `is_active` | boolean | +| `pdgsz` | `valid_from_ts` | string | +| `pdgsz` | `valid_to_ts` | string | +| `pdwkseub` | `power_plant` | string | +| `pdwkseub` | `resource_code` | string | +| `pdwkseub` | `non_us_cap` | number | +| `pdwkseub` | `grid_lim` | number | +| `pdwkseub` | `available_capacity` | number | +| `pk5l-wp` | `plan_dtime` | string | +| `pk5l-wp` | `plan_dtime_utc` | string | +| `pk5l-wp` | `grid_demand_fcst` | number | +| `pk5l-wp` | `req_pow_res` | number | +| `pk5l-wp` | `surplus_cap_avail_tso` | number | +| `pk5l-wp` | `gen_surplus_avail_tso_above` | number | +| `pk5l-wp` | `avail_cap_gen_units_stor_prov` | number | +| `pk5l-wp` | `avail_cap_gen_units_stor_prov_tso` | number | +| `pk5l-wp` | `fcst_gen_unit_stor_prov` | number | +| `pk5l-wp` | `fcst_gen_unit_stor_non_prov` | number | +| `pk5l-wp` | `fcst_wi_tot_gen` | number | +| `pk5l-wp` | `fcst_pv_tot_gen` | number | +| `pk5l-wp` | `planned_exchange` | number | +| `pk5l-wp` | `fcst_unav_energy` | number | +| `pk5l-wp` | `planned_restr_mwe_avail_shutdown` | number | +| `pk5l-wp` | `sum_unav_oper_cond` | number | +| `pk5l-wp` | `pred_gen_res_not_cov` | number | +| `pk5l-wp` | `cap_market_obligation` | number | +| `poeb-rbb` | `ofcg` | number | +| `poeb-rbb` | `ofcd` | number | +| `poeb-rbb` | `ofp` | number | +| `poeb-rbn` | `ofcg` | number | +| `poeb-rbn` | `ofcd` | number | +| `poeb-rbn` | `ofp` | number | +| `pomb-rbn` | `reserve_type` | string | +| `pomb-rbn` | `pofmin` | number | +| `pomb-rbn` | `pofmax` | number | +| `pomb-rbn` | `plofmin` | number | +| `pomb-rbn` | `plofmax` | number | +| `pomb-rbn` | `ofc` | number | +| `popmb-rmb` | `onmbp` | string | +| `popmb-rmb` | `reserve_type` | string | +| `popmb-rmb` | `pom` | number | +| `popmb-rmb` | `com` | number | +| `popmb-rmb` | `comrr` | number | +| `poze-redoze` | `pv_red_balance` | number | +| `poze-redoze` | `wi_red_balance` | number | +| `poze-redoze` | `pv_red_network` | number | +| `poze-redoze` | `wi_red_network` | number | +| `price-cost` | `cen_cost` | number | +| `price-cost` | `ckoeb_cost` | number | +| `price-cost` | `ceb_sr_cost` | number | +| `price-cost` | `cor_pp_cost` | number | +| `price-cost` | `imb_energy` | number | +| `price-cost` | `contracting` | string | +| `price-fcst` | `cen_fcst` | number | +| `price-fcst` | `ckoeb_fcst` | number | +| `price-fcst` | `ceb_sr_fcst` | number | +| `price-fcst` | `cor_fcst` | number | +| `price-fcst` | `imb_energy` | number | +| `price-fcst` | `contracting` | string | +| `przeplywy-mocy` | `section_code` | string | +| `przeplywy-mocy` | `value` | number | +| `pwm-dost-op` | `pl_cz` | number | +| `pwm-dost-op` | `cz_pl` | number | +| `pwm-dost-op` | `pl_sk` | number | +| `pwm-dost-op` | `sk_pl` | number | +| `pwm-dost-op` | `pl_de` | number | +| `pwm-dost-op` | `de_pl` | number | +| `pwm-dost-op` | `pl_se` | number | +| `pwm-dost-op` | `se_pl` | number | +| `pwm-dost-op` | `pl_ua` | number | +| `pwm-dost-op` | `ua_pl` | number | +| `pwm-dost-op` | `pl_lt` | number | +| `pwm-dost-op` | `lt_pl` | number | +| `pwm-dost-op` | `saldo` | number | +| `pwm-igcc-in` | `energy_afrr_exp` | number | +| `pwm-igcc-in` | `energy_afrr_imp` | number | +| `pwm-igcc-in` | `afrr_exp_pln` | number | +| `pwm-igcc-in` | `afrr_imp_pln` | number | +| `pwm-igcc-in` | `afrr_exp_eur` | number | +| `pwm-igcc-in` | `afrr_imp_eur` | number | +| `pwm-rdb` | `pl_cz` | number | +| `pwm-rdb` | `cz_pl` | number | +| `pwm-rdb` | `pl_sk` | number | +| `pwm-rdb` | `sk_pl` | number | +| `pwm-rdb` | `pl_de` | number | +| `pwm-rdb` | `de_pl` | number | +| `pwm-rdb` | `pl_se` | number | +| `pwm-rdb` | `se_pl` | number | +| `pwm-rdb` | `pl_ua` | number | +| `pwm-rdb` | `ua_pl` | number | +| `pwm-rdb` | `pl_lt` | number | +| `pwm-rdb` | `lt_pl` | number | +| `pwm-rdn` | `pl_cz` | number | +| `pwm-rdn` | `cz_pl` | number | +| `pwm-rdn` | `pl_sk` | number | +| `pwm-rdn` | `sk_pl` | number | +| `pwm-rdn` | `pl_de` | number | +| `pwm-rdn` | `de_pl` | number | +| `pwm-rdn` | `pl_se` | number | +| `pwm-rdn` | `se_pl` | number | +| `pwm-rdn` | `pl_ua_dob` | number | +| `pwm-rdn` | `ua_dob_pl` | number | +| `pwm-rdn` | `pl_ua` | number | +| `pwm-rdn` | `ua_pl` | number | +| `pwm-rdn` | `pl_lt` | number | +| `pwm-rdn` | `lt_pl` | number | +| `pwm-rdn-rdb` | `pl_cz` | number | +| `pwm-rdn-rdb` | `cz_pl` | number | +| `pwm-rdn-rdb` | `pl_sk` | number | +| `pwm-rdn-rdb` | `sk_pl` | number | +| `pwm-rdn-rdb` | `pl_de` | number | +| `pwm-rdn-rdb` | `de_pl` | number | +| `pwm-rdn-rdb` | `pl_se` | number | +| `pwm-rdn-rdb` | `se_pl` | number | +| `pwm-rdn-rdb` | `pl_ua_dob` | number | +| `pwm-rdn-rdb` | `ua_dob_pl` | number | +| `pwm-rdn-rdb` | `pl_ua` | number | +| `pwm-rdn-rdb` | `ua_pl` | number | +| `pwm-rdn-rdb` | `pl_lt` | number | +| `pwm-rdn-rdb` | `lt_pl` | number | +| `rcco2` | `rcco2_pln` | number | +| `rcco2` | `rcco2_eur` | number | +| `rce-pln` | `rce_pln` | number | +| `ro-prog` | `ro_fcst` | number | +| `ro-rozl` | `ro_cost` | number | +| `sk` | `sk_d1_fcst` | number | +| `sk` | `sk_d_fcst` | number | +| `sk` | `sk_cost` | number | +| `specwezlow` | `regional_dispatch_center` | string | +| `specwezlow` | `dso_branch` | string | +| `specwezlow` | `station_name` | string | +| `specwezlow` | `switching_station_code` | string | +| `specwezlow` | `switching_station_voltage` | string | +| `specwezlow` | `busbar_code` | string | +| `specwezlow` | `busbar_name` | string | +| `unav-pk5l` | `power_plant` | string | +| `unav-pk5l` | `type` | string | +| `unav-pk5l` | `state` | string | +| `unav-pk5l` | `unit_code` | string | +| `unav-pk5l` | `reason` | string | +| `unav-pk5l` | `start_dtime` | string | +| `unav-pk5l` | `start_dtime_utc` | string | +| `unav-pk5l` | `end_dtime` | string | +| `unav-pk5l` | `end_dtime_utc` | string | +| `unav-pk5l` | `direction` | string | +| `unav-pk5l` | `mrid_zas` | string | +| `unav-pk5l` | `snd_dtime` | string | +| `unav-pk5l` | `snd_dtime_utc` | string | +| `use-sprz-rbb` | `sprz_volume` | number | +| `use-sprz-rbn` | `day_ahead_tr_vol` | number | +| `wmb` | `fcrg` | integer | +| `wmb` | `fcrd` | integer | +| `wmb` | `frrg` | integer | +| `wmb` | `frrd` | integer | +| `wmb` | `afrrg` | integer | +| `wmb` | `afrrd` | integer | +| `wmb` | `rrg` | integer | +| `wmb` | `rrd` | integer | +| `wsp-wrm` | `az` | number | +| `wsp-wrm` | `afw` | number | +| `wsp-wrm` | `apv` | number | +| `wsp-wrm` | `ws` | number | +| `wsp-wrm` | `wmb_max` | number | +| `wsp-wrm` | `ojnz_id` | string | +| `zeb-rozl` | `zebpp` | number | +| `zeb-rozl` | `pzeb_afrrg_cost` | number | +| `zeb-rozl` | `pzeb_afrrd_cost` | number | +| `zmb` | `zmb_fcrg` | number | +| `zmb` | `zmb_fcrd` | number | +| `zmb` | `zmb_frrg` | number | +| `zmb` | `zmb_frrd` | number | +| `zmb` | `zmb_afrrg` | number | +| `zmb` | `zmb_afrrd` | number | +| `zmb` | `zmb_rrg` | number | +| `zmb` | `zmb_rrd` | number | diff --git a/scripts/gen_docs.py b/scripts/gen_docs.py new file mode 100644 index 0000000..69c97bf --- /dev/null +++ b/scripts/gen_docs.py @@ -0,0 +1,87 @@ +"""Generate ``docs/endpoints.md`` from the endpoint registry. + +Usage:: + + uv run scripts/gen_docs.py # regenerate + uv run scripts/gen_docs.py --check # exit 1 if out of date +""" + +from __future__ import annotations + +import argparse +import re +import sys +from pathlib import Path + +from pse_sdk._endpoint_spec import EndpointSpec +from pse_sdk._registry import REGISTRY + +OUTPUT = Path(__file__).resolve().parents[1] / "docs" / "endpoints.md" + +HEADER = """\ + +# Endpoints + +> API reference: `https://api.raporty.pse.pl/api/openapi` + +Data columns of every endpoint, in API order. Meta columns (`dtime`, `period`, +`business_date`, `publication_ts` and their `*_utc` variants) are left out; +`fetch()` returns them with every request. + +| Endpoint | Column | Type | +|---|---|---| +""" + + +def natural_key(name: str) -> list[str | int]: + """Sort key that orders digit runs numerically (``kpi2`` before ``kpi10``).""" + return [int(part) if part.isdigit() else part for part in re.split(r"(\d+)", name)] + + +def render(registry: dict[str, EndpointSpec]) -> str: + """Render the endpoints table for *registry*.""" + rows = [] + for name in sorted(registry, key=natural_key): + spec = registry[name] + for column, type_ in spec.columns.items(): + if column not in spec.meta_columns: + rows.append(f"| `{name}` | `{column}` | {type_} |\n") + return HEADER + "".join(rows) + + +def _check(source: str) -> int: + """Return 1 if the committed docs differ from *source*, else 0.""" + current = OUTPUT.read_text(encoding="utf-8") if OUTPUT.exists() else None + if current != source: + print( + f"{OUTPUT.name} is out of date. Run: uv run scripts/gen_docs.py", + file=sys.stderr, + ) + return 1 + print(f"{OUTPUT.name} is up to date.") + return 0 + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.add_argument( + "--check", + action="store_true", + help="exit 1 if the committed docs are out of date", + ) + args = parser.parse_args(argv) + + source = render(REGISTRY) + + if args.check: + return _check(source) + + OUTPUT.write_text(source, encoding="utf-8", newline="\n") + print(f"Wrote {len(REGISTRY)} endpoints to {OUTPUT}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/test_docs.py b/tests/test_docs.py new file mode 100644 index 0000000..0dea7f3 --- /dev/null +++ b/tests/test_docs.py @@ -0,0 +1,70 @@ +from gen_docs import HEADER, OUTPUT, natural_key, render +from pse_sdk._endpoint_spec import EndpointSpec +from pse_sdk._registry import REGISTRY + +SPEC = { + "price-cost": EndpointSpec( + columns={ + "cen_cost": "number", + "contracting": "string", + "dtime": "string", + "business_date": "string", + }, + date_column="business_date", + meta_columns=("dtime", "business_date"), + keys=("dtime",), + ), + "csire-kpi10": EndpointSpec( + columns={"kpi": "number"}, date_column=None, meta_columns=(), keys=() + ), + "csire-kpi2": EndpointSpec( + columns={"kpi": "number"}, date_column=None, meta_columns=(), keys=() + ), +} + + +def _rows(source: str) -> list[str]: + return source.removeprefix(HEADER).splitlines() + + +class TestNaturalKey: + def test_digits_compared_numerically(self): + names = ["csire-kpi10", "csire-kpi2", "csire-kpi1"] + assert sorted(names, key=natural_key) == [ + "csire-kpi1", + "csire-kpi2", + "csire-kpi10", + ] + + def test_otherwise_alphabetical(self): + names = ["ogr-d1-head", "ogr-b", "ogr-d1", "cor-q-1", "cor"] + assert sorted(names, key=natural_key) == sorted(names) + + +class TestRender: + def test_header(self): + assert render(SPEC).startswith(HEADER) + + def test_endpoints_natural_order(self): + endpoints = [row.split("`")[1] for row in _rows(render(SPEC))] + assert list(dict.fromkeys(endpoints)) == [ + "csire-kpi2", + "csire-kpi10", + "price-cost", + ] + + def test_data_columns_only_in_api_order(self): + rows = _rows(render({"price-cost": SPEC["price-cost"]})) + assert rows == [ + "| `price-cost` | `cen_cost` | number |", + "| `price-cost` | `contracting` | string |", + ] + + +class TestCommittedDocs: + def test_up_to_date(self): + assert OUTPUT.read_text(encoding="utf-8") == render(REGISTRY) + + def test_all_endpoints_listed(self): + endpoints = {row.split("`")[1] for row in _rows(render(REGISTRY))} + assert endpoints == set(REGISTRY) From 0ee26550726d72f72f3ddce3144bfec24c7ebef8 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 16:50:50 +0200 Subject: [PATCH 19/51] docs: move fetch() notes to README, document gen_docs, add CHANGELOG entry --- CHANGELOG.md | 8 ++++++++ CONTRIBUTING.md | 12 ++++++++++++ README.md | 13 ++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd4a7f4..a4a1007 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- `docs/endpoints.md` is generated from the endpoint registry + (`scripts/gen_docs.py`) and lists the data columns of all 90 endpoints, + including the previously missing `wmb` and `his-gen-pal-sire`. The + Description column is dropped. +- `fetch()` column and date-filter notes moved from `docs/endpoints.md` to + the README + ### Removed - **Breaking:** all 32 `get_*` methods of `PSEClient` (`get_cen`, `get_csdac`, `get_kb`, `get_sk_cost`, `get_demand`, ...) and the internal `make_endpoint` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 71e92a4..55400bc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -68,6 +68,18 @@ uv run scripts/gen_registry.py uv run scripts/gen_registry.py --check ``` +## Endpoint Docs + +`docs/endpoints.md` is generated from the registry — don't edit it by hand. Regenerate it after any registry change. + +```bash +# Regenerate +uv run scripts/gen_docs.py + +# Check that the committed docs are up to date +uv run scripts/gen_docs.py --check +``` + ## Dependencies Dev dependencies live in the `dev` dependency group in `pyproject.toml`; exact versions are pinned in `uv.lock`. diff --git a/README.md b/README.md index 523fb45..5b61637 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,19 @@ df = client.fetch( ) ``` +## Columns + +- `columns=None` returns all columns. +- `columns=[...]` returns these columns plus the endpoint's meta columns + (`dtime`, `period`, `business_date`, `publication_ts` and their `*_utc` + variants, where present) and key columns (e.g. `resource_code` on `gen-jw`), + so every row stays identifiable. + ## Date Formats +Rows are filtered on `business_date`. `ogr-*-head` endpoints have no date +column and are not supported yet. + | Format | Example | |--------------|--------------| | `YYYY-MM-DD` | `2024-01-01` | @@ -54,5 +65,5 @@ df = client.fetch( ## Endpoints -See [docs/endpoints.md](docs/endpoints.md) for available endpoints. +See [docs/endpoints.md](docs/endpoints.md) for available endpoints and their columns. From 850672e06092b9727b205213708aa103c134dc24 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 17:00:06 +0200 Subject: [PATCH 20/51] feat: add endpoints() and columns() discovery helpers --- scripts/gen_docs.py | 6 +++--- src/pse_sdk/_client.py | 24 ++++++++++++++++++++++-- src/pse_sdk/_query.py | 5 +++++ tests/test_client.py | 40 ++++++++++++++++++++++++++++++++++++++++ tests/test_query.py | 7 ++++++- 5 files changed, 76 insertions(+), 6 deletions(-) diff --git a/scripts/gen_docs.py b/scripts/gen_docs.py index 69c97bf..1d61e98 100644 --- a/scripts/gen_docs.py +++ b/scripts/gen_docs.py @@ -14,6 +14,7 @@ from pathlib import Path from pse_sdk._endpoint_spec import EndpointSpec +from pse_sdk._query import data_columns from pse_sdk._registry import REGISTRY OUTPUT = Path(__file__).resolve().parents[1] / "docs" / "endpoints.md" @@ -43,9 +44,8 @@ def render(registry: dict[str, EndpointSpec]) -> str: rows = [] for name in sorted(registry, key=natural_key): spec = registry[name] - for column, type_ in spec.columns.items(): - if column not in spec.meta_columns: - rows.append(f"| `{name}` | `{column}` | {type_} |\n") + for column in data_columns(spec): + rows.append(f"| `{name}` | `{column}` | {spec.columns[column]} |\n") return HEADER + "".join(rows) diff --git a/src/pse_sdk/_client.py b/src/pse_sdk/_client.py index 11fb0e9..b3326aa 100644 --- a/src/pse_sdk/_client.py +++ b/src/pse_sdk/_client.py @@ -3,8 +3,8 @@ from pse_sdk._dates import normalize_date_range, split_date_range from pse_sdk._format import OutputFormat, convert, validate_format from pse_sdk._http import HTTPClient -from pse_sdk._query import build_select, date_clauses, get_spec -from pse_sdk._registry import Endpoint +from pse_sdk._query import build_select, data_columns, date_clauses, get_spec +from pse_sdk._registry import REGISTRY, Endpoint class PSEClient: @@ -17,6 +17,8 @@ class PSEClient: from pse_sdk import PSEClient pse = PSEClient() + pse.endpoints() # ["afrr-status", "cmbp-tp", ...] + pse.columns("price-cost") # ["cen_cost", "ckoeb_cost", ...] records = pse.fetch("price-cost", "2024-01-01", "2024-01-31") df = pse.fetch( "price-cost", @@ -37,6 +39,24 @@ def __init__(self, timeout: int = 30) -> None: timeout=timeout, ) + @staticmethod + def endpoints() -> list[Endpoint]: + """Return the names of all endpoints, sorted alphabetically.""" + return list(REGISTRY) + + @staticmethod + def columns(endpoint: Endpoint) -> list[str]: + """Return the data columns of *endpoint* in API order. + + These are the names accepted by ``fetch(columns=...)``. Meta columns + (``dtime``, ``period``, ``business_date``, ``publication_ts`` and their + ``*_utc`` variants) are left out; ``fetch()`` always returns them. + + Raises: + ValueError: If *endpoint* is unknown. + """ + return data_columns(get_spec(endpoint)) + def fetch( self, endpoint: Endpoint, diff --git a/src/pse_sdk/_query.py b/src/pse_sdk/_query.py index 90ec5e9..c4fecca 100644 --- a/src/pse_sdk/_query.py +++ b/src/pse_sdk/_query.py @@ -18,6 +18,11 @@ def get_spec(endpoint: str) -> EndpointSpec: raise ValueError(f"Unknown endpoint {endpoint!r}") from None +def data_columns(spec: EndpointSpec) -> list[str]: + """Return the non-meta columns of *spec* in API order.""" + return [c for c in spec.columns if c not in spec.meta_columns] + + def build_select(spec: EndpointSpec, columns: list[str] | None) -> list[str] | None: """Return the ``$select`` columns for *columns* of *spec*. diff --git a/tests/test_client.py b/tests/test_client.py index b9c47da..8acb0f9 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -23,6 +23,46 @@ def test_custom_timeout(self, mock_http): ) +class TestEndpoints: + def test_all_registry_endpoints_sorted(self): + assert PSEClient.endpoints() == sorted(REGISTRY) + + def test_callable_on_instance(self, mock_http): + assert PSEClient().endpoints() == PSEClient.endpoints() + + def test_returns_new_list(self): + PSEClient.endpoints().clear() + assert PSEClient.endpoints() + + +class TestColumns: + def test_data_columns_in_api_order(self): + assert PSEClient.columns("price-cost") == [ + "cen_cost", + "ckoeb_cost", + "ceb_sr_cost", + "cor_pp_cost", + "imb_energy", + "contracting", + ] + + def test_key_columns_included(self): + assert {"resource_code", "operating_mode"} <= set(PSEClient.columns("gen-jw")) + + def test_callable_on_instance(self, mock_http): + assert PSEClient().columns("price-cost") == PSEClient.columns("price-cost") + + def test_unknown_endpoint_raises(self): + with pytest.raises(ValueError, match="Unknown endpoint 'nope'"): + PSEClient.columns("nope") + + @pytest.mark.parametrize("name", REGISTRY) + def test_registry_columns_no_meta(self, name): + columns = PSEClient.columns(name) + assert columns + assert not set(columns) & set(REGISTRY[name].meta_columns) + + @pytest.fixture() def pse(mock_http): client = PSEClient() diff --git a/tests/test_query.py b/tests/test_query.py index a69b3ad..531dcfa 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -1,7 +1,7 @@ import pytest from pse_sdk._endpoint_spec import EndpointSpec -from pse_sdk._query import build_select, date_clauses, get_spec +from pse_sdk._query import build_select, data_columns, date_clauses, get_spec from pse_sdk._registry import REGISTRY SPEC = EndpointSpec( @@ -28,6 +28,11 @@ def test_unknown_endpoint_raises(self): get_spec("nope") +class TestDataColumns: + def test_meta_dropped_api_order_kept(self): + assert data_columns(SPEC) == ["node", "value", "other"] + + class TestBuildSelect: def test_none_selects_all(self): assert build_select(SPEC, None) is None From 979b95896f083f32e5373cd2d55d7f5306e6b092 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 17:00:06 +0200 Subject: [PATCH 21/51] docs: document endpoints() and columns(), add CHANGELOG entry --- CHANGELOG.md | 5 +++++ README.md | 10 +++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4a1007..0b59c57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- `PSEClient.endpoints()`: names of all endpoints +- `PSEClient.columns(endpoint)`: data columns of an endpoint, in API order + (meta columns left out). Both read the bundled registry, no HTTP request. + ### Changed - `docs/endpoints.md` is generated from the endpoint registry (`scripts/gen_docs.py`) and lists the data columns of all 90 endpoints, diff --git a/README.md b/README.md index 5b61637..fba13bd 100644 --- a/README.md +++ b/README.md @@ -65,5 +65,13 @@ column and are not supported yet. ## Endpoints -See [docs/endpoints.md](docs/endpoints.md) for available endpoints and their columns. +List endpoints and their data columns without any HTTP request: + +```python +client.endpoints() # ["afrr-status", "cmbp-tp", ...] +client.columns("price-cost") # ["cen_cost", "ckoeb_cost", ...] +``` + +See [docs/endpoints.md](docs/endpoints.md) for all endpoints, their columns +and types. From e2299b33836706bf94d41015acfa61faa351e610 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 17:39:35 +0200 Subject: [PATCH 22/51] docs: fix CHANGELOG 1.0.0 entry and PSEClient docstring typo --- CHANGELOG.md | 5 +++-- src/pse_sdk/_client.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b59c57..c2c2d9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,11 +89,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `PSEClient` class for interacting with the PSE public REST API -- `OutputFormat` enum for controlling response format (JSON, CSV, XML) +- `OutputFormat` type (`Literal["dict", "dataframe"]`) for controlling response + format: list of dicts or pandas DataFrame - `HTTPClient` for managing HTTP requests with pagination support - Coverage of the `price-cost` PSE API endpoint - Date normalization and validation utilities -- Configurable `base_url` and `page_size` parameters +- Configurable HTTP `timeout` parameter - Optional pandas integration via `pip install pse-sdk[pandas]` - Unit tests for PSEClient and HTTPClient diff --git a/src/pse_sdk/_client.py b/src/pse_sdk/_client.py index b3326aa..8cd552a 100644 --- a/src/pse_sdk/_client.py +++ b/src/pse_sdk/_client.py @@ -8,7 +8,7 @@ class PSEClient: - """Client for the PSE (Polskie Sieci Elektoenergetyczne) reporting API. + """Client for the PSE (Polskie Sieci Elektroenergetyczne) reporting API. Args: timeout: HTTP timeout in seconds. From 4d5f8cb4924d2a960a01301c1017e8124673f402 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 17:43:13 +0200 Subject: [PATCH 23/51] fix: retry first page request in fetch_all Route fetch() through _request_with_retry so the first page gets the same 5xx retry/backoff as nextLink pages. --- CHANGELOG.md | 4 ++++ src/pse_sdk/_http.py | 10 ++++------ tests/test_http.py | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2c2d9a..aab86e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `get_cen(s, e)` → `fetch("price-cost", s, e, columns=["cen_cost"])`. `fetch()` also returns the `*_utc` meta columns. +### Fixed +- First page request of `fetch()` now retried on 5xx errors, like later + pages (#32) + ## [1.5.1] - 2026-10-20 ### Added diff --git a/src/pse_sdk/_http.py b/src/pse_sdk/_http.py index 2f0dfff..c3183c0 100644 --- a/src/pse_sdk/_http.py +++ b/src/pse_sdk/_http.py @@ -51,14 +51,12 @@ def fetch( if after: params.append("$after=" + quote(after)) - resp = self.session.get(url, params="&".join(params), timeout=self.timeout) - resp.raise_for_status() - return resp.json() + return self._request_with_retry(url, params="&".join(params)) - def _request_with_retry(self, url: str) -> dict: + def _request_with_retry(self, url: str, params: str | None = None) -> dict: """GET *url* with retries on transient server errors.""" for attempt in range(self._MAX_RETRIES + 1): - resp = self.session.get(url, timeout=self.timeout) + resp = self.session.get(url, params=params, timeout=self.timeout) if ( resp.status_code not in self._RETRY_STATUSES or attempt == self._MAX_RETRIES @@ -69,7 +67,7 @@ def _request_with_retry(self, url: str) -> dict: logger.warning( "HTTP %s from %s, retrying in %ss (%d/%d)", resp.status_code, - url, + resp.url, delay, attempt + 1, self._MAX_RETRIES, diff --git a/tests/test_http.py b/tests/test_http.py index 8b6eb41..496446b 100644 --- a/tests/test_http.py +++ b/tests/test_http.py @@ -63,6 +63,7 @@ def test_http_error_raises(self): c = HTTPClient(BASE_URL, 10, 5) with pytest.raises(requests.exceptions.HTTPError): c.fetch("data") + assert len(responses.calls) == 1 class TestFetchAll: @@ -179,3 +180,39 @@ def test_retry_on_other_server_errors(self, mock_sleep, status): c = HTTPClient(BASE_URL, 10, 5) result = c.fetch_all("data") assert result == [{"a": 1}, {"a": 2}] + + @responses.activate + @patch("pse_sdk._http.time.sleep") + def test_first_page_retry_on_503_then_success(self, mock_sleep): + responses.get(f"{BASE_URL}/data", status=503) + responses.get(f"{BASE_URL}/data", json={"value": [{"a": 1}]}) + + c = HTTPClient(BASE_URL, 10, 5) + result = c.fetch_all("data") + assert result == [{"a": 1}] + mock_sleep.assert_called_once_with(1) + + @responses.activate + @patch("pse_sdk._http.time.sleep") + def test_first_page_retry_exhaustion_raises(self, mock_sleep): + for _ in range(4): + responses.get(f"{BASE_URL}/data", status=500) + + c = HTTPClient(BASE_URL, 10, 5) + with pytest.raises(requests.exceptions.HTTPError): + c.fetch_all("data") + assert mock_sleep.call_count == 3 + + @responses.activate + @patch("pse_sdk._http.time.sleep") + def test_first_page_retry_keeps_query_params(self, mock_sleep): + responses.get(f"{BASE_URL}/data", status=503) + responses.get(f"{BASE_URL}/data", json={"value": []}) + + c = HTTPClient(BASE_URL, 25, 5) + c.fetch_all("data", select=["a", "b"], filter_expr="x eq 1") + assert len(responses.calls) == 2 + params = parse_qs(urlparse(responses.calls[1].request.url).query) + assert params["$first"] == ["25"] + assert params["$select"] == ["a,b"] + assert params["$filter"] == ["x eq 1"] From c4b4329770d103b9b72f7f1c17f59b3bd4cbe985 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 18:04:20 +0200 Subject: [PATCH 24/51] feat: validate endpoint and column names before request --- src/pse_sdk/_client.py | 11 ++-- src/pse_sdk/_query.py | 7 ++- src/pse_sdk/_validate.py | 102 +++++++++++++++++++++++++++++++++++ tests/test_client.py | 18 +++++++ tests/test_validate.py | 114 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 244 insertions(+), 8 deletions(-) create mode 100644 src/pse_sdk/_validate.py create mode 100644 tests/test_validate.py diff --git a/src/pse_sdk/_client.py b/src/pse_sdk/_client.py index 8cd552a..8c5ab9a 100644 --- a/src/pse_sdk/_client.py +++ b/src/pse_sdk/_client.py @@ -5,6 +5,7 @@ from pse_sdk._http import HTTPClient from pse_sdk._query import build_select, data_columns, date_clauses, get_spec from pse_sdk._registry import REGISTRY, Endpoint +from pse_sdk._validate import validate_columns class PSEClient: @@ -80,13 +81,15 @@ def fetch( format: Output format — ``"dict"`` (default) or ``"dataframe"``. Raises: - TypeError: If *start* or *end* is not a string. - ValueError: If *endpoint* is unknown or has no date column, - *format* is not supported, a date string cannot be parsed, - or *start* is after *end*. + TypeError: If *start* or *end* is not a string, or *columns* is + not a list of strings. + ValueError: If *endpoint* or a column is unknown, *endpoint* has + no date column, *format* is not supported, a date string + cannot be parsed, or *start* is after *end*. """ spec = get_spec(endpoint) validate_format(format) + validate_columns(endpoint, columns) if spec.date_column is None: raise ValueError( f"Endpoint {endpoint!r} has no date column and is not supported yet" diff --git a/src/pse_sdk/_query.py b/src/pse_sdk/_query.py index c4fecca..0868aa9 100644 --- a/src/pse_sdk/_query.py +++ b/src/pse_sdk/_query.py @@ -4,6 +4,7 @@ from pse_sdk._endpoint_spec import EndpointSpec from pse_sdk._registry import REGISTRY +from pse_sdk._validate import validate_endpoint def get_spec(endpoint: str) -> EndpointSpec: @@ -12,10 +13,8 @@ def get_spec(endpoint: str) -> EndpointSpec: Raises: ValueError: If *endpoint* is not in the registry. """ - try: - return REGISTRY[endpoint] - except KeyError: - raise ValueError(f"Unknown endpoint {endpoint!r}") from None + validate_endpoint(endpoint) + return REGISTRY[endpoint] def data_columns(spec: EndpointSpec) -> list[str]: diff --git a/src/pse_sdk/_validate.py b/src/pse_sdk/_validate.py new file mode 100644 index 0000000..decca48 --- /dev/null +++ b/src/pse_sdk/_validate.py @@ -0,0 +1,102 @@ +"""Check endpoint and column names against the registry before any request. + +Typos fail fast with a suggestion instead of an HTTP 400/404 from the API. +""" + +from __future__ import annotations + +import difflib +from collections.abc import Iterable + +from pse_sdk._registry import REGISTRY + + +def _build_column_index() -> dict[str, tuple[str, ...]]: + """Map each data column to the endpoints that have it (meta columns left out).""" + index: dict[str, list[str]] = {} + for name, spec in REGISTRY.items(): + for column in spec.columns: + if column not in spec.meta_columns: + index.setdefault(column, []).append(name) + return {column: tuple(names) for column, names in index.items()} + + +_COLUMN_ENDPOINTS = _build_column_index() + + +def suggest(name: str, choices: Iterable[str]) -> str | None: + """Return the closest match to *name* in *choices*, or ``None``. + + *name* is lowercased first, so case is ignored (registry names are all + lowercase). + """ + matches = difflib.get_close_matches(name.lower(), choices, n=1) + return matches[0] if matches else None + + +def validate_endpoint(endpoint: str) -> None: + """Raise ``ValueError`` if *endpoint* is not in the registry.""" + if endpoint in REGISTRY: + return + match = suggest(endpoint, REGISTRY) if isinstance(endpoint, str) else None + hint = f"Did you mean {match!r}?" if match else "See PSEClient.endpoints()." + raise ValueError(f"Unknown endpoint {endpoint!r}. {hint}") + + +def validate_columns(endpoint: str, columns: list[str] | None) -> None: + """Raise if any of *columns* is not a column of *endpoint*. + + Meta columns count, so requesting them explicitly is fine. ``None`` + (all columns) always passes. *endpoint* must already be validated. + + Raises: + TypeError: If *columns* is not a list or tuple, or holds a non-string. + ValueError: If a column is unknown. The message names every unknown + column with a hint. + """ + if columns is None: + return + if not isinstance(columns, (list, tuple)): + raise TypeError( + f"columns must be a list of column names, got {type(columns).__name__}" + ) + for column in columns: + if not isinstance(column, str): + raise TypeError(f"column names must be str, got {type(column).__name__}") + + known = REGISTRY[endpoint].columns + unknown = list(dict.fromkeys(c for c in columns if c not in known)) + if unknown: + raise ValueError(_unknown_columns_message(endpoint, unknown)) + + +def _column_hint(endpoint: str, column: str) -> str | None: + """Return a hint for unknown *column* of *endpoint*, or ``None``. + + Other endpoints with the exact column win over a close match in + *endpoint*. + """ + elsewhere = _COLUMN_ENDPOINTS.get(column) + if elsewhere: + return f"found in: {', '.join(elsewhere)}" + match = suggest(column, REGISTRY[endpoint].columns) + return f"did you mean {match!r}?" if match else None + + +def _sentence(hint: str) -> str: + """Capitalize *hint* and end it with a period unless it is a question.""" + return hint[0].upper() + hint[1:] + ("" if hint.endswith("?") else ".") + + +def _unknown_columns_message(endpoint: str, unknown: list[str]) -> str: + hints = [_column_hint(endpoint, column) for column in unknown] + see = f"See PSEClient.columns({endpoint!r})." + if len(unknown) == 1: + tail = _sentence(hints[0]) if hints[0] else see + return f"Unknown column {unknown[0]!r} for endpoint {endpoint!r}. {tail}" + items = ", ".join( + f"{column!r} ({hint})" if hint else repr(column) + for column, hint in zip(unknown, hints) + ) + message = f"Unknown columns for endpoint {endpoint!r}: {items}." + return f"{message} {see}" if None in hints else message diff --git a/tests/test_client.py b/tests/test_client.py index 8acb0f9..6fe944b 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -56,6 +56,10 @@ def test_unknown_endpoint_raises(self): with pytest.raises(ValueError, match="Unknown endpoint 'nope'"): PSEClient.columns("nope") + def test_unknown_endpoint_suggests(self): + with pytest.raises(ValueError, match="Did you mean 'price-cost'"): + PSEClient.columns("price-cots") + @pytest.mark.parametrize("name", REGISTRY) def test_registry_columns_no_meta(self, name): columns = PSEClient.columns(name) @@ -129,6 +133,20 @@ def test_unknown_endpoint_raises_before_request(self, pse): pse.fetch("nope", "2024-01-01", "2024-01-30") pse.client.fetch_all.assert_not_called() + def test_unknown_endpoint_suggests(self, pse): + with pytest.raises(ValueError, match="Did you mean 'price-cost'"): + pse.fetch("price-cots", "2024-01-01", "2024-01-30") + + def test_unknown_column_raises_before_request(self, pse): + with pytest.raises(ValueError, match="Unknown column 'cen_cots'.*'cen_cost'"): + pse.fetch("price-cost", "2024-01-01", "2024-01-30", columns=["cen_cots"]) + pse.client.fetch_all.assert_not_called() + + def test_str_columns_raises_before_request(self, pse): + with pytest.raises(TypeError, match="columns must be a list"): + pse.fetch("price-cost", "2024-01-01", "2024-01-30", columns="cen_cost") + pse.client.fetch_all.assert_not_called() + def test_invalid_date_range_raises_before_request(self, pse): with pytest.raises(ValueError, match="must not be after"): pse.fetch("price-cost", "2024-02-01", "2024-01-01") diff --git a/tests/test_validate.py b/tests/test_validate.py new file mode 100644 index 0000000..2cfabe1 --- /dev/null +++ b/tests/test_validate.py @@ -0,0 +1,114 @@ +import pytest + +from pse_sdk._registry import REGISTRY +from pse_sdk._validate import suggest, validate_columns, validate_endpoint + + +def error(func, *args) -> str: + with pytest.raises(ValueError) as exc: + func(*args) + return str(exc.value) + + +class TestSuggest: + def test_typo(self): + assert suggest("price-cots", REGISTRY) == "price-cost" + + def test_ignores_case(self): + assert suggest("CEN_COST", REGISTRY["price-cost"].columns) == "cen_cost" + + def test_no_match(self): + assert suggest("xyz", REGISTRY) is None + + +class TestValidateEndpoint: + def test_known(self): + validate_endpoint("price-cost") + + def test_typo_suggests(self): + assert error(validate_endpoint, "price-cots") == ( + "Unknown endpoint 'price-cots'. Did you mean 'price-cost'?" + ) + + def test_no_match_points_to_endpoints(self): + assert error(validate_endpoint, "xyz") == ( + "Unknown endpoint 'xyz'. See PSEClient.endpoints()." + ) + + def test_non_str(self): + assert error(validate_endpoint, None).startswith("Unknown endpoint None.") + + +class TestValidateColumns: + def test_none_passes(self): + validate_columns("price-cost", None) + + def test_empty_passes(self): + validate_columns("price-cost", []) + + def test_data_columns_pass(self): + validate_columns("price-cost", ["cen_cost", "ckoeb_cost"]) + + def test_meta_column_passes(self): + validate_columns("price-cost", ["dtime_utc"]) + + def test_tuple_passes(self): + validate_columns("price-cost", ("cen_cost",)) + + def test_typo_suggests(self): + assert error(validate_columns, "price-cost", ["cen_cots"]) == ( + "Unknown column 'cen_cots' for endpoint 'price-cost'. " + "Did you mean 'cen_cost'?" + ) + + def test_typo_ignores_case(self): + assert error(validate_columns, "price-cost", ["CEN_COST"]).endswith( + "Did you mean 'cen_cost'?" + ) + + def test_column_of_other_endpoints(self): + assert error(validate_columns, "kse-load", ["cen_cost"]) == ( + "Unknown column 'cen_cost' for endpoint 'kse-load'. " + "Found in: crb-rozl, energy-prices, price-cost." + ) + + def test_no_match_points_to_columns(self): + assert error(validate_columns, "price-cost", ["foo"]) == ( + "Unknown column 'foo' for endpoint 'price-cost'. " + "See PSEClient.columns('price-cost')." + ) + + def test_all_unknown_named_once(self): + assert error( + validate_columns, "price-cost", ["cen_cots", "foo", "cen_cost", "foo"] + ) == ( + "Unknown columns for endpoint 'price-cost': " + "'cen_cots' (did you mean 'cen_cost'?), 'foo'. " + "See PSEClient.columns('price-cost')." + ) + + def test_all_unknown_hinted(self): + assert error(validate_columns, "price-cost", ["cen_cots", "ckoeb_cots"]) == ( + "Unknown columns for endpoint 'price-cost': " + "'cen_cots' (did you mean 'cen_cost'?), " + "'ckoeb_cots' (did you mean 'ckoeb_cost'?)." + ) + + def test_meta_column_missing_on_endpoint(self): + message = error(validate_columns, "rcco2", ["dtime"]) + assert message.startswith("Unknown column 'dtime' for endpoint 'rcco2'.") + assert "Found in" not in message + + @pytest.mark.parametrize("columns", ["cen_cost", {"cen_cost"}, iter(["cen_cost"])]) + def test_not_list_raises(self, columns): + with pytest.raises(TypeError, match="columns must be a list of column names"): + validate_columns("price-cost", columns) + + def test_non_str_column_raises(self): + with pytest.raises(TypeError, match="column names must be str, got int"): + validate_columns("price-cost", ["cen_cost", 1]) + + +@pytest.mark.parametrize("name", sorted(REGISTRY)) +def test_registry_own_columns_pass(name): + validate_columns(name, list(REGISTRY[name].columns)) From 7bb817a8cc4c980fec66e55c5fa4d3aca6022553 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 18:04:20 +0200 Subject: [PATCH 25/51] feat: include API error message in HTTPError --- src/pse_sdk/_http.py | 21 ++++++++++++++++++- tests/test_http.py | 50 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/pse_sdk/_http.py b/src/pse_sdk/_http.py index c3183c0..c8fe083 100644 --- a/src/pse_sdk/_http.py +++ b/src/pse_sdk/_http.py @@ -9,6 +9,25 @@ logger = logging.getLogger(__name__) +def _error_message(resp: requests.Response) -> str | None: + """Return the PSE API ``error.message`` from *resp*, or ``None``.""" + try: + return resp.json()["error"]["message"] + except (ValueError, KeyError, TypeError): # non-JSON or unexpected shape + return None + + +def _raise_for_status(resp: requests.Response) -> None: + """Like ``resp.raise_for_status()``, plus the API error message if any.""" + try: + resp.raise_for_status() + except requests.HTTPError as e: + message = _error_message(resp) + if message is None: + raise + raise requests.HTTPError(f"{e}: {message}", response=resp) from None + + class HTTPClient: """Low-level HTTP client for the PSE API with OData pagination.""" @@ -61,7 +80,7 @@ def _request_with_retry(self, url: str, params: str | None = None) -> dict: resp.status_code not in self._RETRY_STATUSES or attempt == self._MAX_RETRIES ): - resp.raise_for_status() + _raise_for_status(resp) return resp.json() delay = self._BACKOFF_BASE * (2**attempt) logger.warning( diff --git a/tests/test_http.py b/tests/test_http.py index 496446b..531e7e4 100644 --- a/tests/test_http.py +++ b/tests/test_http.py @@ -65,6 +65,39 @@ def test_http_error_raises(self): c.fetch("data") assert len(responses.calls) == 1 + @responses.activate + def test_http_error_includes_api_message(self): + responses.get( + f"{BASE_URL}/data", + status=400, + json={ + "error": { + "code": "BadRequest", + "message": "Invalid field to be returned requested: x", + "status": 400, + } + }, + ) + c = HTTPClient(BASE_URL, 10, 5) + with pytest.raises(requests.exceptions.HTTPError) as exc: + c.fetch("data") + assert str(exc.value) == ( + f"400 Client Error: Bad Request for url: {BASE_URL}/data: " + "Invalid field to be returned requested: x" + ) + assert exc.value.response.status_code == 400 + + @responses.activate + @pytest.mark.parametrize( + "body", [{"body": "Bad Request"}, {"json": {"detail": "x"}}] + ) + def test_http_error_without_api_message_unchanged(self, body): + responses.get(f"{BASE_URL}/data", status=400, **body) + c = HTTPClient(BASE_URL, 10, 5) + with pytest.raises(requests.exceptions.HTTPError) as exc: + c.fetch("data") + assert str(exc.value) == f"400 Client Error: Bad Request for url: {BASE_URL}/data" + class TestFetchAll: @responses.activate @@ -203,6 +236,23 @@ def test_first_page_retry_exhaustion_raises(self, mock_sleep): c.fetch_all("data") assert mock_sleep.call_count == 3 + @responses.activate + @patch("pse_sdk._http.time.sleep") + def test_retry_exhaustion_includes_api_message(self, mock_sleep): + for _ in range(4): + responses.get( + f"{BASE_URL}/data", + status=500, + json={"error": {"message": "DatabaseOperationFailed"}}, + ) + + c = HTTPClient(BASE_URL, 10, 5) + with pytest.raises( + requests.exceptions.HTTPError, match=r"^500 Server Error.*: DatabaseOperationFailed$" + ): + c.fetch_all("data") + assert mock_sleep.call_count == 3 + @responses.activate @patch("pse_sdk._http.time.sleep") def test_first_page_retry_keeps_query_params(self, mock_sleep): From c84ae01b0279105384d5f498807b10f3429208b1 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 18:04:20 +0200 Subject: [PATCH 26/51] docs: document name validation in README and CHANGELOG --- CHANGELOG.md | 7 +++++++ README.md | 3 +++ 2 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aab86e4..ae48c3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `PSEClient.endpoints()`: names of all endpoints - `PSEClient.columns(endpoint)`: data columns of an endpoint, in API order (meta columns left out). Both read the bundled registry, no HTTP request. +- `fetch()` checks column names against the registry before any request. + Unknown columns raise `ValueError` naming each one with a hint (close + match, or other endpoints that have it) instead of an HTTP 400. `columns` + that is not a list or tuple of strings raises `TypeError` (#25) ### Changed +- Unknown endpoint errors suggest the closest endpoint name (#25) +- `HTTPError` from the API includes the API's error message, e.g. + `Invalid field to be returned requested: cen_cots` - `docs/endpoints.md` is generated from the endpoint registry (`scripts/gen_docs.py`) and lists the data columns of all 90 endpoints, including the previously missing `wmb` and `his-gen-pal-sire`. The diff --git a/README.md b/README.md index fba13bd..1df07cd 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,9 @@ df = client.fetch( (`dtime`, `period`, `business_date`, `publication_ts` and their `*_utc` variants, where present) and key columns (e.g. `resource_code` on `gen-jw`), so every row stays identifiable. +- Endpoint and column names are checked against the bundled registry before + any request. Unknown names raise `ValueError` with a hint: + `Unknown column 'cen_cots' for endpoint 'price-cost'. Did you mean 'cen_cost'?` ## Date Formats From 65fe93a444bdf7496c9ab57500a5d30d0351fca7 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 18:06:01 +0200 Subject: [PATCH 27/51] refactor: move HTTP error helpers into HTTPClient --- src/pse_sdk/_http.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/pse_sdk/_http.py b/src/pse_sdk/_http.py index c8fe083..c724f82 100644 --- a/src/pse_sdk/_http.py +++ b/src/pse_sdk/_http.py @@ -9,25 +9,6 @@ logger = logging.getLogger(__name__) -def _error_message(resp: requests.Response) -> str | None: - """Return the PSE API ``error.message`` from *resp*, or ``None``.""" - try: - return resp.json()["error"]["message"] - except (ValueError, KeyError, TypeError): # non-JSON or unexpected shape - return None - - -def _raise_for_status(resp: requests.Response) -> None: - """Like ``resp.raise_for_status()``, plus the API error message if any.""" - try: - resp.raise_for_status() - except requests.HTTPError as e: - message = _error_message(resp) - if message is None: - raise - raise requests.HTTPError(f"{e}: {message}", response=resp) from None - - class HTTPClient: """Low-level HTTP client for the PSE API with OData pagination.""" @@ -80,7 +61,7 @@ def _request_with_retry(self, url: str, params: str | None = None) -> dict: resp.status_code not in self._RETRY_STATUSES or attempt == self._MAX_RETRIES ): - _raise_for_status(resp) + self._raise_for_status(resp) return resp.json() delay = self._BACKOFF_BASE * (2**attempt) logger.warning( @@ -93,6 +74,25 @@ def _request_with_retry(self, url: str, params: str | None = None) -> dict: ) time.sleep(delay) + @staticmethod + def _raise_for_status(resp: requests.Response) -> None: + """Like ``resp.raise_for_status()``, plus the API error message if any.""" + try: + resp.raise_for_status() + except requests.HTTPError as e: + message = HTTPClient._error_message(resp) + if message is None: + raise + raise requests.HTTPError(f"{e}: {message}", response=resp) from None + + @staticmethod + def _error_message(resp: requests.Response) -> str | None: + """Return the PSE API ``error.message`` from *resp*, or ``None``.""" + try: + return resp.json()["error"]["message"] + except (ValueError, KeyError, TypeError): # non-JSON or unexpected shape + return None + def fetch_all( self, path: str, From 002ff8f0fc02bf204eeb59c36f73bfaf7827d651 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 18:06:01 +0200 Subject: [PATCH 28/51] docs: drop name validation note from README --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 1df07cd..fba13bd 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,6 @@ df = client.fetch( (`dtime`, `period`, `business_date`, `publication_ts` and their `*_utc` variants, where present) and key columns (e.g. `resource_code` on `gen-jw`), so every row stays identifiable. -- Endpoint and column names are checked against the bundled registry before - any request. Unknown names raise `ValueError` with a hint: - `Unknown column 'cen_cots' for endpoint 'price-cost'. Did you mean 'cen_cost'?` ## Date Formats From a55777bc4adec247a4dad7f767bad9309ab97c3f Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 18:06:44 +0200 Subject: [PATCH 29/51] refactor: order HTTPClient methods public first, helpers in call order --- src/pse_sdk/_http.py | 48 ++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/pse_sdk/_http.py b/src/pse_sdk/_http.py index c724f82..576f828 100644 --- a/src/pse_sdk/_http.py +++ b/src/pse_sdk/_http.py @@ -53,6 +53,30 @@ def fetch( return self._request_with_retry(url, params="&".join(params)) + def fetch_all( + self, + path: str, + *, + select: list[str] | None = None, + filter_expr: str | None = None, + order_by: str | None = None, + ) -> list[dict]: + """Fetch all pages, auto-paginating via nextLink.""" + data = self.fetch( + path, + select=select, + filter_expr=filter_expr, + order_by=order_by, + first=self.page_size, + ) + records: list[dict] = data.get("value", []) + + while "nextLink" in data: + data = self._request_with_retry(data["nextLink"]) + records.extend(data.get("value", [])) + + return records + def _request_with_retry(self, url: str, params: str | None = None) -> dict: """GET *url* with retries on transient server errors.""" for attempt in range(self._MAX_RETRIES + 1): @@ -92,27 +116,3 @@ def _error_message(resp: requests.Response) -> str | None: return resp.json()["error"]["message"] except (ValueError, KeyError, TypeError): # non-JSON or unexpected shape return None - - def fetch_all( - self, - path: str, - *, - select: list[str] | None = None, - filter_expr: str | None = None, - order_by: str | None = None, - ) -> list[dict]: - """Fetch all pages, auto-paginating via nextLink.""" - data = self.fetch( - path, - select=select, - filter_expr=filter_expr, - order_by=order_by, - first=self.page_size, - ) - records: list[dict] = data.get("value", []) - - while "nextLink" in data: - data = self._request_with_retry(data["nextLink"]) - records.extend(data.get("value", [])) - - return records From 72bdad1073f41a95263b8a935c5e41a3b03b026f Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 18:13:33 +0200 Subject: [PATCH 30/51] refactor: simplify name validation - one message format for 1..n unknown columns, drop _sentence - compute "found in" endpoints on the error path, drop import-time index - suggest() handles non-str names - extract _check_types from validate_columns --- src/pse_sdk/_validate.py | 73 ++++++++++++++++++---------------------- tests/test_client.py | 2 +- tests/test_validate.py | 16 ++++----- 3 files changed, 41 insertions(+), 50 deletions(-) diff --git a/src/pse_sdk/_validate.py b/src/pse_sdk/_validate.py index decca48..41d3bc8 100644 --- a/src/pse_sdk/_validate.py +++ b/src/pse_sdk/_validate.py @@ -11,25 +11,14 @@ from pse_sdk._registry import REGISTRY -def _build_column_index() -> dict[str, tuple[str, ...]]: - """Map each data column to the endpoints that have it (meta columns left out).""" - index: dict[str, list[str]] = {} - for name, spec in REGISTRY.items(): - for column in spec.columns: - if column not in spec.meta_columns: - index.setdefault(column, []).append(name) - return {column: tuple(names) for column, names in index.items()} - - -_COLUMN_ENDPOINTS = _build_column_index() - - def suggest(name: str, choices: Iterable[str]) -> str | None: """Return the closest match to *name* in *choices*, or ``None``. *name* is lowercased first, so case is ignored (registry names are all - lowercase). + lowercase). A non-string *name* has no match. """ + if not isinstance(name, str): + return None matches = difflib.get_close_matches(name.lower(), choices, n=1) return matches[0] if matches else None @@ -38,7 +27,7 @@ def validate_endpoint(endpoint: str) -> None: """Raise ``ValueError`` if *endpoint* is not in the registry.""" if endpoint in REGISTRY: return - match = suggest(endpoint, REGISTRY) if isinstance(endpoint, str) else None + match = suggest(endpoint, REGISTRY) hint = f"Did you mean {match!r}?" if match else "See PSEClient.endpoints()." raise ValueError(f"Unknown endpoint {endpoint!r}. {hint}") @@ -56,6 +45,15 @@ def validate_columns(endpoint: str, columns: list[str] | None) -> None: """ if columns is None: return + _check_types(columns) + known = REGISTRY[endpoint].columns + unknown = list(dict.fromkeys(c for c in columns if c not in known)) + if unknown: + raise ValueError(_unknown_columns_message(endpoint, unknown)) + + +def _check_types(columns: list[str]) -> None: + """Raise ``TypeError`` unless *columns* is a list or tuple of strings.""" if not isinstance(columns, (list, tuple)): raise TypeError( f"columns must be a list of column names, got {type(columns).__name__}" @@ -64,39 +62,32 @@ def validate_columns(endpoint: str, columns: list[str] | None) -> None: if not isinstance(column, str): raise TypeError(f"column names must be str, got {type(column).__name__}") - known = REGISTRY[endpoint].columns - unknown = list(dict.fromkeys(c for c in columns if c not in known)) - if unknown: - raise ValueError(_unknown_columns_message(endpoint, unknown)) + +def _unknown_columns_message(endpoint: str, unknown: list[str]) -> str: + hints = [_column_hint(endpoint, column) for column in unknown] + items = ", ".join( + f"{column!r} ({hint})" if hint else repr(column) + for column, hint in zip(unknown, hints) + ) + noun = "column" if len(unknown) == 1 else "columns" + message = f"Unknown {noun} for endpoint {endpoint!r}: {items}." + if None in hints: + message += f" See PSEClient.columns({endpoint!r})." + return message def _column_hint(endpoint: str, column: str) -> str | None: """Return a hint for unknown *column* of *endpoint*, or ``None``. - Other endpoints with the exact column win over a close match in - *endpoint*. + Other endpoints with the exact column (as a data column) win over a close + match in *endpoint*. """ - elsewhere = _COLUMN_ENDPOINTS.get(column) + elsewhere = [ + name + for name, spec in REGISTRY.items() + if column in spec.columns and column not in spec.meta_columns + ] if elsewhere: return f"found in: {', '.join(elsewhere)}" match = suggest(column, REGISTRY[endpoint].columns) return f"did you mean {match!r}?" if match else None - - -def _sentence(hint: str) -> str: - """Capitalize *hint* and end it with a period unless it is a question.""" - return hint[0].upper() + hint[1:] + ("" if hint.endswith("?") else ".") - - -def _unknown_columns_message(endpoint: str, unknown: list[str]) -> str: - hints = [_column_hint(endpoint, column) for column in unknown] - see = f"See PSEClient.columns({endpoint!r})." - if len(unknown) == 1: - tail = _sentence(hints[0]) if hints[0] else see - return f"Unknown column {unknown[0]!r} for endpoint {endpoint!r}. {tail}" - items = ", ".join( - f"{column!r} ({hint})" if hint else repr(column) - for column, hint in zip(unknown, hints) - ) - message = f"Unknown columns for endpoint {endpoint!r}: {items}." - return f"{message} {see}" if None in hints else message diff --git a/tests/test_client.py b/tests/test_client.py index 6fe944b..9ef223f 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -138,7 +138,7 @@ def test_unknown_endpoint_suggests(self, pse): pse.fetch("price-cots", "2024-01-01", "2024-01-30") def test_unknown_column_raises_before_request(self, pse): - with pytest.raises(ValueError, match="Unknown column 'cen_cots'.*'cen_cost'"): + with pytest.raises(ValueError, match="Unknown column for endpoint 'price-cost': 'cen_cots'.*'cen_cost'"): pse.fetch("price-cost", "2024-01-01", "2024-01-30", columns=["cen_cots"]) pse.client.fetch_all.assert_not_called() diff --git a/tests/test_validate.py b/tests/test_validate.py index 2cfabe1..c489630 100644 --- a/tests/test_validate.py +++ b/tests/test_validate.py @@ -57,24 +57,24 @@ def test_tuple_passes(self): def test_typo_suggests(self): assert error(validate_columns, "price-cost", ["cen_cots"]) == ( - "Unknown column 'cen_cots' for endpoint 'price-cost'. " - "Did you mean 'cen_cost'?" + "Unknown column for endpoint 'price-cost': " + "'cen_cots' (did you mean 'cen_cost'?)." ) def test_typo_ignores_case(self): assert error(validate_columns, "price-cost", ["CEN_COST"]).endswith( - "Did you mean 'cen_cost'?" + "(did you mean 'cen_cost'?)." ) def test_column_of_other_endpoints(self): assert error(validate_columns, "kse-load", ["cen_cost"]) == ( - "Unknown column 'cen_cost' for endpoint 'kse-load'. " - "Found in: crb-rozl, energy-prices, price-cost." + "Unknown column for endpoint 'kse-load': " + "'cen_cost' (found in: crb-rozl, energy-prices, price-cost)." ) def test_no_match_points_to_columns(self): assert error(validate_columns, "price-cost", ["foo"]) == ( - "Unknown column 'foo' for endpoint 'price-cost'. " + "Unknown column for endpoint 'price-cost': 'foo'. " "See PSEClient.columns('price-cost')." ) @@ -96,8 +96,8 @@ def test_all_unknown_hinted(self): def test_meta_column_missing_on_endpoint(self): message = error(validate_columns, "rcco2", ["dtime"]) - assert message.startswith("Unknown column 'dtime' for endpoint 'rcco2'.") - assert "Found in" not in message + assert message.startswith("Unknown column for endpoint 'rcco2': 'dtime'") + assert "found in" not in message @pytest.mark.parametrize("columns", ["cen_cost", {"cen_cost"}, iter(["cen_cost"])]) def test_not_list_raises(self, columns): From 1c3ee77e60c3adec36c36ce1ab90e7b97e7f274f Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 18:16:15 +0200 Subject: [PATCH 31/51] style: format tests with ruff --- tests/test_client.py | 14 +++++++++++--- tests/test_date.py | 10 ++++++++-- tests/test_http.py | 7 +++++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index 9ef223f..8cd0f8e 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -76,7 +76,9 @@ def pse(mock_http): class TestFetch: def test_select_meta_keys_and_columns(self, pse): - pse.fetch("price-cost", "2024-01-01", "2024-01-15", columns=["cen_cost", "ckoeb_cost"]) + pse.fetch( + "price-cost", "2024-01-01", "2024-01-15", columns=["cen_cost", "ckoeb_cost"] + ) assert pse.client.fetch_all.call_count == 1 assert pse.client.fetch_all.call_args.args == ("price-cost",) assert pse.client.fetch_all.call_args.kwargs["select"] == [ @@ -97,7 +99,10 @@ def test_columns_none_selects_all(self, pse): def test_filter_on_date_column(self, pse): pse.fetch("price-cost", "2024-01-01", "2024-01-30") filter_expr = pse.client.fetch_all.call_args.kwargs["filter_expr"] - assert filter_expr == "business_date ge '2024-01-01' and business_date le '2024-01-30'" + assert ( + filter_expr + == "business_date ge '2024-01-01' and business_date le '2024-01-30'" + ) def test_long_range_chunked(self, pse): pse.fetch("price-cost", "2024-01-01", "2024-02-29") @@ -138,7 +143,10 @@ def test_unknown_endpoint_suggests(self, pse): pse.fetch("price-cots", "2024-01-01", "2024-01-30") def test_unknown_column_raises_before_request(self, pse): - with pytest.raises(ValueError, match="Unknown column for endpoint 'price-cost': 'cen_cots'.*'cen_cost'"): + with pytest.raises( + ValueError, + match="Unknown column for endpoint 'price-cost': 'cen_cots'.*'cen_cost'", + ): pse.fetch("price-cost", "2024-01-01", "2024-01-30", columns=["cen_cots"]) pse.client.fetch_all.assert_not_called() diff --git a/tests/test_date.py b/tests/test_date.py index 16f4c92..f098c5c 100644 --- a/tests/test_date.py +++ b/tests/test_date.py @@ -31,10 +31,16 @@ def test_impossible_date(self): class TestNormalizeDateRange: def test_equal_dates_pass(self): - assert normalize_date_range("2024-01-01", "2024-01-01") == ("2024-01-01", "2024-01-01") + assert normalize_date_range("2024-01-01", "2024-01-01") == ( + "2024-01-01", + "2024-01-01", + ) def test_start_before_end_pass(self): - assert normalize_date_range("2024-01-01", "2024-01-31") == ("2024-01-01", "2024-01-31") + assert normalize_date_range("2024-01-01", "2024-01-31") == ( + "2024-01-01", + "2024-01-31", + ) def test_start_after_end_raises(self): with pytest.raises(ValueError, match="must not be after"): diff --git a/tests/test_http.py b/tests/test_http.py index 531e7e4..c075443 100644 --- a/tests/test_http.py +++ b/tests/test_http.py @@ -96,7 +96,9 @@ def test_http_error_without_api_message_unchanged(self, body): c = HTTPClient(BASE_URL, 10, 5) with pytest.raises(requests.exceptions.HTTPError) as exc: c.fetch("data") - assert str(exc.value) == f"400 Client Error: Bad Request for url: {BASE_URL}/data" + assert ( + str(exc.value) == f"400 Client Error: Bad Request for url: {BASE_URL}/data" + ) class TestFetchAll: @@ -248,7 +250,8 @@ def test_retry_exhaustion_includes_api_message(self, mock_sleep): c = HTTPClient(BASE_URL, 10, 5) with pytest.raises( - requests.exceptions.HTTPError, match=r"^500 Server Error.*: DatabaseOperationFailed$" + requests.exceptions.HTTPError, + match=r"^500 Server Error.*: DatabaseOperationFailed$", ): c.fetch_all("data") assert mock_sleep.call_count == 3 From e28696150c3812b5f827d212d525a20f8faaf3bf Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 18:16:15 +0200 Subject: [PATCH 32/51] chore: lint tests with ruff --- CONTRIBUTING.md | 4 ++-- pyproject.toml | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 55400bc..97daef0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,10 +48,10 @@ Run these from the project root: uv run pytest # Lint -uv run ruff check src/ scripts/ +uv run ruff check src/ scripts/ tests/ # Format -uv run ruff format src/ scripts/ +uv run ruff format src/ scripts/ tests/ ``` Fix any lint/test failures before opening a PR. diff --git a/pyproject.toml b/pyproject.toml index d5795c1..9dda883 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,5 +31,8 @@ testpaths = ["tests"] pythonpath = ["scripts"] [tool.ruff] -src = ["src"] +src = ["src", "scripts"] extend-exclude = ["src/pse_sdk/_registry.py"] + +[tool.ruff.lint.per-file-ignores] +"tests/**" = ["S102"] From 73d8f17b813884f20fb8d8e7783ffaa19b80bc58 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 18:19:00 +0200 Subject: [PATCH 33/51] refactor: remove module docstrings --- scripts/gen_docs.py | 10 +--------- src/pse_sdk/__init__.py | 2 -- src/pse_sdk/_endpoint_spec.py | 2 -- src/pse_sdk/_query.py | 2 -- src/pse_sdk/_validate.py | 5 ----- 5 files changed, 1 insertion(+), 20 deletions(-) diff --git a/scripts/gen_docs.py b/scripts/gen_docs.py index 1d61e98..898b657 100644 --- a/scripts/gen_docs.py +++ b/scripts/gen_docs.py @@ -1,11 +1,3 @@ -"""Generate ``docs/endpoints.md`` from the endpoint registry. - -Usage:: - - uv run scripts/gen_docs.py # regenerate - uv run scripts/gen_docs.py --check # exit 1 if out of date -""" - from __future__ import annotations import argparse @@ -64,7 +56,7 @@ def _check(source: str) -> int: def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser( - description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter + description="Generate docs/endpoints.md from the endpoint registry." ) parser.add_argument( "--check", diff --git a/src/pse_sdk/__init__.py b/src/pse_sdk/__init__.py index 0347f71..f8efb39 100644 --- a/src/pse_sdk/__init__.py +++ b/src/pse_sdk/__init__.py @@ -1,5 +1,3 @@ -"""PSE SDK — easy access to PSE public API.""" - from pse_sdk._client import PSEClient from pse_sdk._format import OutputFormat diff --git a/src/pse_sdk/_endpoint_spec.py b/src/pse_sdk/_endpoint_spec.py index 3045646..d36cab3 100644 --- a/src/pse_sdk/_endpoint_spec.py +++ b/src/pse_sdk/_endpoint_spec.py @@ -1,5 +1,3 @@ -"""Endpoint schema type used by the generated ``_registry.py``.""" - from __future__ import annotations from dataclasses import dataclass diff --git a/src/pse_sdk/_query.py b/src/pse_sdk/_query.py index 0868aa9..efff26a 100644 --- a/src/pse_sdk/_query.py +++ b/src/pse_sdk/_query.py @@ -1,5 +1,3 @@ -"""Build OData query parts for an endpoint from the generated registry.""" - from __future__ import annotations from pse_sdk._endpoint_spec import EndpointSpec diff --git a/src/pse_sdk/_validate.py b/src/pse_sdk/_validate.py index 41d3bc8..50897d9 100644 --- a/src/pse_sdk/_validate.py +++ b/src/pse_sdk/_validate.py @@ -1,8 +1,3 @@ -"""Check endpoint and column names against the registry before any request. - -Typos fail fast with a suggestion instead of an HTTP 400/404 from the API. -""" - from __future__ import annotations import difflib From ac8d4cb7cc457cbd71b427b8b355acedabb358a1 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 19:29:29 +0200 Subject: [PATCH 34/51] feat: filter ogr-*-head endpoints on period --- scripts/gen_registry.py | 30 +++++++++++++++++++++++++----- src/pse_sdk/_client.py | 10 +++------- src/pse_sdk/_endpoint_spec.py | 2 +- src/pse_sdk/_registry.py | 12 ++++++------ 4 files changed, 35 insertions(+), 19 deletions(-) diff --git a/scripts/gen_registry.py b/scripts/gen_registry.py index 6de786f..15bea13 100644 --- a/scripts/gen_registry.py +++ b/scripts/gen_registry.py @@ -29,7 +29,6 @@ "publication_ts", "publication_ts_utc", ) -DATE_COLUMN = "business_date" TEMPLATE = string.Template("""\ # Generated by scripts/gen_registry.py from $url @@ -67,17 +66,38 @@ def parse_keys(paths: dict) -> dict[str, tuple[str, ...]]: return keys +def date_column(name: str, columns: dict, keys: tuple[str, ...]) -> str: + """Return the column *name* is range-filtered on. + + ``business_date`` where present. Otherwise a lone ``period`` key: header + tables (``ogr-*-head``) hold one plan per date, so the key is the date. + + Raises: + ValueError: If neither applies. + """ + if "business_date" in columns: + return "business_date" + if keys == ("period",): + return "period" + raise ValueError(f"{name}: no date column (no business_date, keys={keys})") + + def build_registry(spec: dict) -> dict[str, dict]: - """Build a registry entry for every endpoint in *spec*, sorted by name.""" - keys = parse_keys(spec["paths"]) + """Build a registry entry for every endpoint in *spec*, sorted by name. + + Raises: + ValueError: If an endpoint has no date column. + """ + all_keys = parse_keys(spec["paths"]) registry: dict[str, dict] = {} for name, schema in sorted(spec["components"]["schemas"].items()): columns = {col: prop["type"] for col, prop in schema["properties"].items()} + keys = all_keys.get(name, ()) registry[name] = { "columns": columns, - "date_column": DATE_COLUMN if DATE_COLUMN in columns else None, + "date_column": date_column(name, columns, keys), "meta_columns": tuple(c for c in META_COLUMNS if c in columns), - "keys": keys.get(name, ()), + "keys": keys, } return registry diff --git a/src/pse_sdk/_client.py b/src/pse_sdk/_client.py index 8c5ab9a..bf63e70 100644 --- a/src/pse_sdk/_client.py +++ b/src/pse_sdk/_client.py @@ -83,17 +83,13 @@ def fetch( Raises: TypeError: If *start* or *end* is not a string, or *columns* is not a list of strings. - ValueError: If *endpoint* or a column is unknown, *endpoint* has - no date column, *format* is not supported, a date string - cannot be parsed, or *start* is after *end*. + ValueError: If *endpoint* or a column is unknown, *format* is not + supported, a date string cannot be parsed, or *start* is after + *end*. """ spec = get_spec(endpoint) validate_format(format) validate_columns(endpoint, columns) - if spec.date_column is None: - raise ValueError( - f"Endpoint {endpoint!r} has no date column and is not supported yet" - ) start_str, end_str = normalize_date_range(start, end) select = build_select(spec, columns) diff --git a/src/pse_sdk/_endpoint_spec.py b/src/pse_sdk/_endpoint_spec.py index d36cab3..1b2889b 100644 --- a/src/pse_sdk/_endpoint_spec.py +++ b/src/pse_sdk/_endpoint_spec.py @@ -11,6 +11,6 @@ class EndpointSpec: """Schema of one PSE API endpoint.""" columns: dict[str, ColumnType] # all columns in API order -> JSON type - date_column: str | None # column for date-range filtering, None = no filter + date_column: str # column for date-range filtering meta_columns: tuple[str, ...] # dtime/period/business_date/publication_ts (+ _utc) keys: tuple[str, ...] # primary key columns diff --git a/src/pse_sdk/_registry.py b/src/pse_sdk/_registry.py index 1e645af..73cd988 100644 --- a/src/pse_sdk/_registry.py +++ b/src/pse_sdk/_registry.py @@ -1223,7 +1223,7 @@ ), "ogr-d1-head": EndpointSpec( columns={"title": "string", "period": "string", "remarks": "string"}, - date_column=None, + date_column="period", meta_columns=("period",), keys=("period",), ), @@ -1254,7 +1254,7 @@ ), "ogr-d2-head": EndpointSpec( columns={"title": "string", "period": "string", "remarks": "string"}, - date_column=None, + date_column="period", meta_columns=("period",), keys=("period",), ), @@ -1291,7 +1291,7 @@ "to_period": "string", "remarks": "string", }, - date_column=None, + date_column="period", meta_columns=("period",), keys=("period",), ), @@ -1328,7 +1328,7 @@ "to_period": "string", "remarks": "string", }, - date_column=None, + date_column="period", meta_columns=("period",), keys=("period",), ), @@ -1359,7 +1359,7 @@ ), "ogr-oper-head": EndpointSpec( columns={"title": "string", "period": "string", "remarks": "string"}, - date_column=None, + date_column="period", meta_columns=("period",), keys=("period",), ), @@ -1404,7 +1404,7 @@ "to_period": "string", "remarks": "string", }, - date_column=None, + date_column="period", meta_columns=("period",), keys=("period",), ), From e31a77ff8e49409ba678ab73e896004f0603b930 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 19:29:29 +0200 Subject: [PATCH 35/51] test: cover period date column for ogr-*-head endpoints --- tests/test_client.py | 8 ++++---- tests/test_docs.py | 4 ++-- tests/test_registry.py | 20 ++++++++++++++------ 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index 8cd0f8e..dbef52c 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -128,10 +128,10 @@ def test_invalid_format_raises_before_request(self, pse): pse.fetch("price-cost", "2024-01-01", "2024-01-30", format="df") pse.client.fetch_all.assert_not_called() - def test_no_date_column_raises_before_request(self, pse): - with pytest.raises(ValueError, match="'ogr-d1-head' has no date column"): - pse.fetch("ogr-d1-head", "2024-01-01", "2024-01-30") - pse.client.fetch_all.assert_not_called() + def test_head_endpoint_filters_on_period(self, pse): + pse.fetch("ogr-d1-head", "2024-01-01", "2024-01-30") + filter_expr = pse.client.fetch_all.call_args.kwargs["filter_expr"] + assert filter_expr == "period ge '2024-01-01' and period le '2024-01-30'" def test_unknown_endpoint_raises_before_request(self, pse): with pytest.raises(ValueError, match="Unknown endpoint 'nope'"): diff --git a/tests/test_docs.py b/tests/test_docs.py index 0dea7f3..0eb0d00 100644 --- a/tests/test_docs.py +++ b/tests/test_docs.py @@ -15,10 +15,10 @@ keys=("dtime",), ), "csire-kpi10": EndpointSpec( - columns={"kpi": "number"}, date_column=None, meta_columns=(), keys=() + columns={"kpi": "number"}, date_column="business_date", meta_columns=(), keys=() ), "csire-kpi2": EndpointSpec( - columns={"kpi": "number"}, date_column=None, meta_columns=(), keys=() + columns={"kpi": "number"}, date_column="business_date", meta_columns=(), keys=() ), } diff --git a/tests/test_registry.py b/tests/test_registry.py index 141a8a0..fcf509f 100644 --- a/tests/test_registry.py +++ b/tests/test_registry.py @@ -2,7 +2,7 @@ import pytest -from gen_registry import build_registry, parse_keys, render +from gen_registry import build_registry, date_column, parse_keys, render from pse_sdk._registry import REGISTRY, Endpoint @@ -86,8 +86,16 @@ def test_columns_keep_spec_order_and_types(self): def test_date_column_business_date(self): assert build_registry(SPEC)["price-cost"]["date_column"] == "business_date" - def test_date_column_none_without_business_date(self): - assert build_registry(SPEC)["ogr-d1-head"]["date_column"] is None + def test_date_column_period_key(self): + assert build_registry(SPEC)["ogr-d1-head"]["date_column"] == "period" + + def test_business_date_wins_over_period_key(self): + columns = {"period": "string", "business_date": "string"} + assert date_column("x", columns, ("period",)) == "business_date" + + def test_unclassifiable_endpoint_raises(self): + with pytest.raises(ValueError, match="rcco2: no date column"): + date_column("rcco2", {"period": "string"}, ("dtime_utc",)) def test_meta_columns_fixed_order(self): assert build_registry(SPEC)["price-cost"]["meta_columns"] == ( @@ -155,8 +163,8 @@ def test_rcco2_has_no_dtime(self): "ogr-r-head", ], ) - def test_head_endpoints_have_no_date_column(self, name): - assert REGISTRY[name].date_column is None + def test_head_endpoints_filter_on_period(self, name): + assert REGISTRY[name].date_column == "period" @pytest.mark.parametrize("name", REGISTRY) @@ -171,4 +179,4 @@ def test_keys_are_columns(self, name): def test_date_column_is_column(self, name): spec = REGISTRY[name] - assert spec.date_column is None or spec.date_column in spec.columns + assert spec.date_column in spec.columns From 32577cd7fad595f09882d7d1e9da73f6144456cf Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 19:29:29 +0200 Subject: [PATCH 36/51] docs: note period date filter for ogr-*-head endpoints --- CHANGELOG.md | 2 ++ README.md | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae48c3c..e51b8df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Unknown columns raise `ValueError` naming each one with a hint (close match, or other endpoints that have it) instead of an HTTP 400. `columns` that is not a list or tuple of strings raises `TypeError` (#25) +- `fetch()` supports the six `ogr-*-head` endpoints, filtered on `period` + (#37) ### Changed - Unknown endpoint errors suggest the closest endpoint name (#25) diff --git a/README.md b/README.md index fba13bd..9f894a8 100644 --- a/README.md +++ b/README.md @@ -47,8 +47,10 @@ df = client.fetch( ## Date Formats -Rows are filtered on `business_date`. `ogr-*-head` endpoints have no date -column and are not supported yet. +Rows are filtered on `business_date`, or on `period` (plan date) for +`ogr-*-head` endpoints. On `ogr-d39-head`, `ogr-m-head` and `ogr-r-head` that +is the plan's first day, so a range returns plans starting in it, not plans +active in it. | Format | Example | |--------------|--------------| From 9c706f25b9ead2c5e9b50a0a395ddd2eeb557995 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 19:53:05 +0200 Subject: [PATCH 37/51] docs: list date and key columns in endpoints.md --- docs/endpoints.md | 103 ++++++++++++++++++++++++++++++++++++++++++-- scripts/gen_docs.py | 30 ++++++++++--- tests/test_docs.py | 19 +++++++- 3 files changed, 140 insertions(+), 12 deletions(-) diff --git a/docs/endpoints.md b/docs/endpoints.md index ccb12c3..e6e528f 100644 --- a/docs/endpoints.md +++ b/docs/endpoints.md @@ -3,9 +3,11 @@ > API reference: `https://api.raporty.pse.pl/api/openapi` -Data columns of every endpoint, in API order. Meta columns (`dtime`, `period`, -`business_date`, `publication_ts` and their `*_utc` variants) are left out; -`fetch()` returns them with every request. +See [usage.md](usage.md) for how `fetch()` uses these columns. + +## Data columns + +In API order. Meta columns are left out. | Endpoint | Column | Type | |---|---|---| @@ -781,3 +783,98 @@ Data columns of every endpoint, in API order. Meta columns (`dtime`, `period`, | `zmb` | `zmb_afrrd` | number | | `zmb` | `zmb_rrg` | number | | `zmb` | `zmb_rrd` | number | + +## Date and key columns + +| Endpoint | Date column | Key columns | +|---|---|---| +| `afrr-status` | `business_date` | `event_time_utc`, `afrr_status`, `reason_code` | +| `cmbp-tp` | `business_date` | `dtime_utc` | +| `cmbu-tu` | `business_date` | `dtime_utc` | +| `codn` | `business_date` | `business_date`, `period_from_date`, `period_to_date` | +| `cor` | `business_date` | `dtime_utc` | +| `cor-q-1` | `business_date` | `business_date`, `period_to_date`, `period_from_date` | +| `cordmax` | `business_date` | `business_date`, `cordmax_fcst`, `cordmax_cost` | +| `crb-rozl` | `business_date` | `dtime_utc` | +| `csdac-pln` | `business_date` | `dtime_utc` | +| `csire-kpi1` | `business_date` | `business_date`, `row_no` | +| `csire-kpi2` | `business_date` | `business_date`, `row_no` | +| `csire-kpi3` | `business_date` | `business_date`, `row_no` | +| `csire-kpi4` | `business_date` | `business_date`, `row_no` | +| `csire-kpi5` | `business_date` | `business_date`, `row_no` | +| `csire-kpi6` | `business_date` | `business_date`, `row_no` | +| `csire-kpi7` | `business_date` | `business_date`, `row_no` | +| `csire-kpi8` | `business_date` | `business_date`, `row_no` | +| `csire-kpi9` | `business_date` | `business_date`, `row_no` | +| `csire-kpi10` | `business_date` | `business_date`, `row_no` | +| `eb-rozl` | `business_date` | `dtime_utc` | +| `en-rozl` | `business_date` | `dtime_utc` | +| `energy-prices` | `business_date` | `dtime_utc` | +| `gen-jw` | `business_date` | `dtime_utc`, `resource_code`, `operating_mode` | +| `his-bil-mocy` | `business_date` | `business_date`, `peak_hour`, `peak` | +| `his-gen-pal` | `business_date` | `business_date`, `dtime_utc`, `alias_entsoe` | +| `his-gen-pal-sire` | `business_date` | `business_date`, `dtime_utc`, `alias_sire`, `alias_entsoe` | +| `his-wlk-cal` | `business_date` | `dtime_utc` | +| `it-oeb-rbb` | `business_date` | `business_date`, `iteration_no` | +| `it-oeb-rbn` | `business_date` | `business_date`, `iteration_no` | +| `it-omb-rbb` | `business_date` | `business_date`, `iteration_no` | +| `it-omb-rbn` | `business_date` | `business_date`, `iteration_no` | +| `it-opmb-rmb` | `business_date` | `business_date`, `iteration_no` | +| `it-ot-rbn` | `business_date` | `business_date`, `iteration_no` | +| `it-pp-rbb` | `business_date` | `business_date`, `iteration_no` | +| `it-pp-rbn` | `business_date` | `business_date`, `iteration_no` | +| `it-use-rbb` | `business_date` | `business_date`, `iteration_no` | +| `it-use-rbn` | `business_date` | `business_date`, `iteration_no` | +| `kmb-kro-rozl` | `business_date` | `dtime_utc` | +| `krb-rozl` | `business_date` | `dtime_utc` | +| `kse-load` | `business_date` | `dtime_utc` | +| `lolp` | `business_date` | `business_date`, `dtime_utc` | +| `mbp-tp` | `business_date` | `dtime_utc` | +| `mbu-tu` | `business_date` | `dtime_utc` | +| `oeb-bpkdbo` | `business_date` | `dtime_utc`, `activ_direction`, `ofp`, `ofc`, `cdo` | +| `ogr-b` | `business_date` | `node`, `business_date`, `to_dtime_utc`, `from_dtime_utc`, `period` | +| `ogr-b-head` | `business_date` | `business_date` | +| `ogr-d1` | `business_date` | `node`, `business_date`, `to_dtime`, `from_dtime` | +| `ogr-d1-head` | `period` | `period` | +| `ogr-d2` | `business_date` | `node`, `business_date`, `to_dtime`, `from_dtime` | +| `ogr-d2-head` | `period` | `period` | +| `ogr-d39` | `business_date` | `node`, `business_date`, `to_dtime`, `from_dtime` | +| `ogr-d39-head` | `period` | `period` | +| `ogr-m` | `business_date` | `node`, `business_date`, `to_dtime`, `from_dtime` | +| `ogr-m-head` | `period` | `period` | +| `ogr-oper` | `business_date` | `node`, `business_date`, `to_dtime`, `from_dtime` | +| `ogr-oper-head` | `period` | `period` | +| `ogr-r` | `business_date` | `node`, `business_date`, `to_dtime`, `from_dtime`, `period` | +| `ogr-r-head` | `period` | `period` | +| `ogr-rmb` | `business_date` | `from_dtime`, `node` | +| `pdgobpkd` | `business_date` | `dtime_utc`, `business_date` | +| `pdgopkd` | `business_date` | `dtime_utc`, `business_date` | +| `pdgsz` | `business_date` | `dtime_utc`, `valid_from_ts`, `valid_to_ts` | +| `pdwkseub` | `business_date` | `dtime_utc`, `resource_code` | +| `pk5l-wp` | `business_date` | `plan_dtime_utc` | +| `poeb-rbb` | `business_date` | `business_date`, `dtime_utc`, `ofcg`, `ofcd`, `ofp` | +| `poeb-rbn` | `business_date` | `business_date`, `dtime_utc`, `ofcg`, `ofcd`, `ofp` | +| `pomb-rbn` | `business_date` | `dtime_utc`, `pofmin`, `pofmax`, `plofmin`, `plofmax`, `ofc`, `reserve_type` | +| `popmb-rmb` | `business_date` | `dtime_utc`, `pom`, `com`, `comrr`, `reserve_type` | +| `poze-redoze` | `business_date` | `business_date`, `period` | +| `price-cost` | `business_date` | `dtime_utc` | +| `price-fcst` | `business_date` | `dtime_utc` | +| `przeplywy-mocy` | `business_date` | `dtime_utc`, `section_code` | +| `pwm-dost-op` | `business_date` | `business_date`, `dtime_utc` | +| `pwm-igcc-in` | `business_date` | `business_date`, `dtime_utc` | +| `pwm-rdb` | `business_date` | `business_date`, `dtime_utc` | +| `pwm-rdn` | `business_date` | `business_date`, `dtime_utc` | +| `pwm-rdn-rdb` | `business_date` | `business_date`, `dtime_utc` | +| `rcco2` | `business_date` | `business_date`, `rcco2_pln` | +| `rce-pln` | `business_date` | `dtime_utc` | +| `ro-prog` | `business_date` | `dtime_utc` | +| `ro-rozl` | `business_date` | `dtime_utc` | +| `sk` | `business_date` | `dtime_utc` | +| `specwezlow` | `business_date` | `business_date`, `regional_dispatch_center` | +| `unav-pk5l` | `business_date` | `power_plant`, `unit_code`, `start_dtime_utc`, `end_dtime_utc` | +| `use-sprz-rbb` | `business_date` | `dtime_utc` | +| `use-sprz-rbn` | `business_date` | `dtime_utc` | +| `wmb` | `business_date` | `dtime_utc` | +| `wsp-wrm` | `business_date` | `dtime_utc` | +| `zeb-rozl` | `business_date` | `dtime` | +| `zmb` | `business_date` | `dtime_utc` | diff --git a/scripts/gen_docs.py b/scripts/gen_docs.py index 898b657..eb15d3d 100644 --- a/scripts/gen_docs.py +++ b/scripts/gen_docs.py @@ -17,28 +17,44 @@ > API reference: `https://api.raporty.pse.pl/api/openapi` -Data columns of every endpoint, in API order. Meta columns (`dtime`, `period`, -`business_date`, `publication_ts` and their `*_utc` variants) are left out; -`fetch()` returns them with every request. +See [usage.md](usage.md) for how `fetch()` uses these columns. + +## Data columns + +In API order. Meta columns are left out. | Endpoint | Column | Type | |---|---|---| """ +KEYS_HEADER = """ +## Date and key columns + +| Endpoint | Date column | Key columns | +|---|---|---| +""" + def natural_key(name: str) -> list[str | int]: """Sort key that orders digit runs numerically (``kpi2`` before ``kpi10``).""" return [int(part) if part.isdigit() else part for part in re.split(r"(\d+)", name)] +def _code(names: tuple[str, ...]) -> str: + """Join *names* as inline code, or ``-`` if empty.""" + return ", ".join(f"`{name}`" for name in names) or "-" + + def render(registry: dict[str, EndpointSpec]) -> str: - """Render the endpoints table for *registry*.""" - rows = [] + """Render the data and key columns tables for *registry*.""" + data_rows = [] + key_rows = [] for name in sorted(registry, key=natural_key): spec = registry[name] for column in data_columns(spec): - rows.append(f"| `{name}` | `{column}` | {spec.columns[column]} |\n") - return HEADER + "".join(rows) + data_rows.append(f"| `{name}` | `{column}` | {spec.columns[column]} |\n") + key_rows.append(f"| `{name}` | `{spec.date_column}` | {_code(spec.keys)} |\n") + return HEADER + "".join(data_rows) + KEYS_HEADER + "".join(key_rows) def _check(source: str) -> int: diff --git a/tests/test_docs.py b/tests/test_docs.py index 0eb0d00..e941e56 100644 --- a/tests/test_docs.py +++ b/tests/test_docs.py @@ -1,4 +1,4 @@ -from gen_docs import HEADER, OUTPUT, natural_key, render +from gen_docs import HEADER, KEYS_HEADER, OUTPUT, natural_key, render from pse_sdk._endpoint_spec import EndpointSpec from pse_sdk._registry import REGISTRY @@ -24,7 +24,11 @@ def _rows(source: str) -> list[str]: - return source.removeprefix(HEADER).splitlines() + return source.removeprefix(HEADER).split(KEYS_HEADER)[0].splitlines() + + +def _key_rows(source: str) -> list[str]: + return source.split(KEYS_HEADER)[1].splitlines() class TestNaturalKey: @@ -60,6 +64,13 @@ def test_data_columns_only_in_api_order(self): "| `price-cost` | `contracting` | string |", ] + def test_key_rows(self): + assert _key_rows(render(SPEC)) == [ + "| `csire-kpi2` | `business_date` | - |", + "| `csire-kpi10` | `business_date` | - |", + "| `price-cost` | `business_date` | `dtime` |", + ] + class TestCommittedDocs: def test_up_to_date(self): @@ -68,3 +79,7 @@ def test_up_to_date(self): def test_all_endpoints_listed(self): endpoints = {row.split("`")[1] for row in _rows(render(REGISTRY))} assert endpoints == set(REGISTRY) + + def test_all_endpoints_in_key_table(self): + endpoints = [row.split("`")[1] for row in _key_rows(render(REGISTRY))] + assert sorted(endpoints) == sorted(REGISTRY) From 629876df4cf0365d9b9d2bda178e5015f742ec93 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 20 Oct 2026 19:53:05 +0200 Subject: [PATCH 38/51] docs: move fetch notes to docs/usage.md --- CHANGELOG.md | 8 ++++---- README.md | 14 ++------------ docs/usage.md | 30 ++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 docs/usage.md diff --git a/CHANGELOG.md b/CHANGELOG.md index e51b8df..2ba8195 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,10 +24,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `Invalid field to be returned requested: cen_cots` - `docs/endpoints.md` is generated from the endpoint registry (`scripts/gen_docs.py`) and lists the data columns of all 90 endpoints, - including the previously missing `wmb` and `his-gen-pal-sire`. The - Description column is dropped. -- `fetch()` column and date-filter notes moved from `docs/endpoints.md` to - the README + including the previously missing `wmb` and `his-gen-pal-sire`, plus the + date and key columns of each. The Description column is dropped. +- `fetch()` column and date-filter notes moved from `docs/endpoints.md` and + the README to the new `docs/usage.md` ### Removed - **Breaking:** all 32 `get_*` methods of `PSEClient` (`get_cen`, `get_csdac`, diff --git a/README.md b/README.md index 9f894a8..ff2f5c3 100644 --- a/README.md +++ b/README.md @@ -37,21 +37,11 @@ df = client.fetch( ) ``` -## Columns - -- `columns=None` returns all columns. -- `columns=[...]` returns these columns plus the endpoint's meta columns - (`dtime`, `period`, `business_date`, `publication_ts` and their `*_utc` - variants, where present) and key columns (e.g. `resource_code` on `gen-jw`), - so every row stays identifiable. +See [docs/usage.md](docs/usage.md) for which columns are returned and how rows +are filtered by date. ## Date Formats -Rows are filtered on `business_date`, or on `period` (plan date) for -`ogr-*-head` endpoints. On `ogr-d39-head`, `ogr-m-head` and `ogr-r-head` that -is the plan's first day, so a range returns plans starting in it, not plans -active in it. - | Format | Example | |--------------|--------------| | `YYYY-MM-DD` | `2024-01-01` | diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 0000000..3fc8b93 --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,30 @@ +# Usage + +How `fetch()` picks columns and rows. Per-endpoint data, date and key columns +are listed in [endpoints.md](endpoints.md). + +## Columns + +- `columns=None` returns all columns. +- `columns=[...]` returns these columns plus the endpoint's meta columns + (`dtime`, `period`, `business_date`, `publication_ts` and their `*_utc` + variants, where present) and key columns (e.g. `resource_code` on `gen-jw`), + so every row stays identifiable. +- `client.columns(endpoint)` lists the names accepted by `columns=`. Meta + columns are not listed there, but are always returned. + +## Date filter + +`start` and `end` are inclusive. Rows are filtered on the endpoint's date +column: `business_date` for most endpoints, `period` (plan date) for +`ogr-d1-head`, `ogr-d2-head`, `ogr-d39-head`, `ogr-m-head`, `ogr-oper-head` and +`ogr-r-head`. + +On `ogr-d39-head`, `ogr-m-head` and `ogr-r-head`, `period` is the plan's first +day, so a range returns plans starting in it, not plans active in it. Their +`from_period` and `to_period` columns give the plan's span. + +## Requests + +The range is fetched in 30-day chunks, and each chunk follows the API's pages. +Long ranges take several requests. From 77d1717c76b53c0fce68ec8d50d9f36175b3d7b9 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 27 Oct 2026 19:17:19 +0100 Subject: [PATCH 39/51] fix: raise ValueError for non-string endpoint --- src/pse_sdk/_validate.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/pse_sdk/_validate.py b/src/pse_sdk/_validate.py index 50897d9..be03618 100644 --- a/src/pse_sdk/_validate.py +++ b/src/pse_sdk/_validate.py @@ -19,11 +19,17 @@ def suggest(name: str, choices: Iterable[str]) -> str | None: def validate_endpoint(endpoint: str) -> None: - """Raise ``ValueError`` if *endpoint* is not in the registry.""" - if endpoint in REGISTRY: - return - match = suggest(endpoint, REGISTRY) - hint = f"Did you mean {match!r}?" if match else "See PSEClient.endpoints()." + """Raise ``ValueError`` if *endpoint* is not in the registry. + + A non-string *endpoint* is unknown; the message names its type. + """ + if isinstance(endpoint, str): + if endpoint in REGISTRY: + return + match = suggest(endpoint, REGISTRY) + hint = f"Did you mean {match!r}?" if match else "See PSEClient.endpoints()." + else: + hint = f"Endpoint must be a str, got {type(endpoint).__name__}." raise ValueError(f"Unknown endpoint {endpoint!r}. {hint}") From 80131e066378ff72a9facd1dd5d1ae19bd97b9bf Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 27 Oct 2026 19:17:19 +0100 Subject: [PATCH 40/51] test: cover list and dict endpoints --- tests/test_client.py | 11 +++++++++++ tests/test_validate.py | 15 +++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index dbef52c..2c56d7d 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -60,6 +60,11 @@ def test_unknown_endpoint_suggests(self): with pytest.raises(ValueError, match="Did you mean 'price-cost'"): PSEClient.columns("price-cots") + @pytest.mark.parametrize("endpoint", [["price-cost"], {"price-cost": 1}]) + def test_non_str_endpoint_raises(self, endpoint): + with pytest.raises(ValueError, match="Endpoint must be a str"): + PSEClient.columns(endpoint) + @pytest.mark.parametrize("name", REGISTRY) def test_registry_columns_no_meta(self, name): columns = PSEClient.columns(name) @@ -142,6 +147,12 @@ def test_unknown_endpoint_suggests(self, pse): with pytest.raises(ValueError, match="Did you mean 'price-cost'"): pse.fetch("price-cots", "2024-01-01", "2024-01-30") + @pytest.mark.parametrize("endpoint", [["price-cost"], {"price-cost": 1}]) + def test_non_str_endpoint_raises_before_request(self, pse, endpoint): + with pytest.raises(ValueError, match="Endpoint must be a str"): + pse.fetch(endpoint, "2024-01-01", "2024-01-30") + pse.client.fetch_all.assert_not_called() + def test_unknown_column_raises_before_request(self, pse): with pytest.raises( ValueError, diff --git a/tests/test_validate.py b/tests/test_validate.py index c489630..616c609 100644 --- a/tests/test_validate.py +++ b/tests/test_validate.py @@ -35,8 +35,19 @@ def test_no_match_points_to_endpoints(self): "Unknown endpoint 'xyz'. See PSEClient.endpoints()." ) - def test_non_str(self): - assert error(validate_endpoint, None).startswith("Unknown endpoint None.") + @pytest.mark.parametrize( + ("endpoint", "type_name"), + [ + (None, "NoneType"), + (5, "int"), + (["price-cost"], "list"), + ({"price-cost": 1}, "dict"), + ], + ) + def test_non_str(self, endpoint, type_name): + assert error(validate_endpoint, endpoint) == ( + f"Unknown endpoint {endpoint!r}. Endpoint must be a str, got {type_name}." + ) class TestValidateColumns: From c2e8e68a2d6b45c3a3b52241389ac22f13f0b47e Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 27 Oct 2026 19:17:19 +0100 Subject: [PATCH 41/51] docs: note non-string endpoint fix in changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ba8195..83250c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - First page request of `fetch()` now retried on 5xx errors, like later pages (#32) +- Non-string endpoint (e.g. a list or dict) raises `ValueError` naming its + type, e.g. `Endpoint must be a str, got list.`, instead of + `TypeError: unhashable type` (#56) ## [1.5.1] - 2026-10-20 From 70fd737b0c0a672edf0961ec41fc66916317c5fd Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 27 Oct 2026 19:48:17 +0100 Subject: [PATCH 42/51] docs: streamline usage page into step-by-step guide --- docs/usage.md | 74 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 3fc8b93..38b5a48 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -1,30 +1,62 @@ # Usage -How `fetch()` picks columns and rows. Per-endpoint data, date and key columns -are listed in [endpoints.md](endpoints.md). +How to find an endpoint, fetch a date range, pick columns and choose an +output format. All endpoints and their columns are listed in +[endpoints.md](endpoints.md). -## Columns +## Find an endpoint -- `columns=None` returns all columns. -- `columns=[...]` returns these columns plus the endpoint's meta columns - (`dtime`, `period`, `business_date`, `publication_ts` and their `*_utc` - variants, where present) and key columns (e.g. `resource_code` on `gen-jw`), - so every row stays identifiable. -- `client.columns(endpoint)` lists the names accepted by `columns=`. Meta - columns are not listed there, but are always returned. +```python +from pse_sdk import PSEClient -## Date filter +client = PSEClient() -`start` and `end` are inclusive. Rows are filtered on the endpoint's date -column: `business_date` for most endpoints, `period` (plan date) for -`ogr-d1-head`, `ogr-d2-head`, `ogr-d39-head`, `ogr-m-head`, `ogr-oper-head` and -`ogr-r-head`. +client.endpoints() # ["afrr-status", "cmbp-tp", ...] +client.columns("price-cost") # ["cen_cost", "ckoeb_cost", ...] +``` -On `ogr-d39-head`, `ogr-m-head` and `ogr-r-head`, `period` is the plan's first -day, so a range returns plans starting in it, not plans active in it. Their -`from_period` and `to_period` columns give the plan's span. +## Fetch a date range -## Requests +```python +rows = client.fetch("price-cost", "2025-10-01", "2025-10-31") +``` -The range is fetched in 30-day chunks, and each chunk follows the API's pages. -Long ranges take several requests. +`start` and `end` are inclusive and accept these formats: + +| Format | Example | +|--------------|--------------| +| `YYYY-MM-DD` | `2025-10-01` | +| `YYYY/MM/DD` | `2025/10/01` | +| `YYYYMMDD` | `20251001` | + +## Pick columns + +`columns=None` (the default) returns all columns. Pass a list to get only +some of them: + +```python +rows = client.fetch("price-cost", "2025-10-01", "2025-10-01", columns=["cen_cost"]) +rows[0] +``` + +```python +{ + "dtime": "2025-10-01 00:15:00", + "period": "00:00 - 00:15", + "cen_cost": 449.02, + "dtime_utc": "2025-09-30 22:15:00", + "period_utc": "22:00 - 22:15", + "business_date": "2025-10-01", + "publication_ts": "2026-06-16 12:11:18.607", + "publication_ts_utc": "2026-06-16 10:11:18.607", +} +``` + +## Choose an output format + +| `format` | Returns | +|---------------------|----------------| +| `"dict"` (default) | `list[dict]` | +| `"dataframe"` | `pd.DataFrame` | + +`"dataframe"` requires pandas \ No newline at end of file From 06137cb7d3e55162c57909101f4a5ebc18cf7a70 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 27 Oct 2026 19:48:17 +0100 Subject: [PATCH 43/51] docs: trim README to install and quick start --- README.md | 44 ++++++++++---------------------------------- 1 file changed, 10 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index ff2f5c3..da11393 100644 --- a/README.md +++ b/README.md @@ -24,46 +24,22 @@ from pse_sdk import PSEClient client = PSEClient() -# Returns list[dict] with all columns -records = client.fetch("price-cost", start="2024-01-01", end="2024-01-31") +# list[dict] with all columns +rows = client.fetch("price-cost", start="2025-10-01", end="2025-10-31") -# Returns pd.DataFrame (requires pandas) with selected columns +# pd.DataFrame (requires pandas) with selected columns df = client.fetch( "price-cost", - start="2024-01-01", - end="2024-01-31", + start="2025-10-01", + end="2025-10-31", columns=["cen_cost", "ckoeb_cost"], format="dataframe", ) ``` -See [docs/usage.md](docs/usage.md) for which columns are returned and how rows -are filtered by date. - -## Date Formats - -| Format | Example | -|--------------|--------------| -| `YYYY-MM-DD` | `2024-01-01` | -| `YYYY/MM/DD` | `2024/01/01` | -| `YYYYMMDD` | `20240101` | - -## Output Formats - -| Format | Return Type | -|---------------|----------------| -| `"dict"` | `list[dict]` | -| `"dataframe"` | `pd.DataFrame` | - -## Endpoints - -List endpoints and their data columns without any HTTP request: - -```python -client.endpoints() # ["afrr-status", "cmbp-tp", ...] -client.columns("price-cost") # ["cen_cost", "ckoeb_cost", ...] -``` - -See [docs/endpoints.md](docs/endpoints.md) for all endpoints, their columns -and types. +## Documentation +- [docs/usage.md](docs/usage.md): finding endpoints, date ranges, columns and + output formats +- [docs/endpoints.md](docs/endpoints.md): all endpoints, their columns and + types From 5e2df9a353e7474302280e15889f42bd1e843c43 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 27 Oct 2026 19:51:13 +0100 Subject: [PATCH 44/51] docs: remove PSE API endpoints map PDF --- docs/pse-api-endpoints-map.pdf | Bin 219590 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/pse-api-endpoints-map.pdf diff --git a/docs/pse-api-endpoints-map.pdf b/docs/pse-api-endpoints-map.pdf deleted file mode 100644 index 502ecf340a233d4d48e4e44def3e764a69cdbedb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 219590 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⁣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%0Dw>WAn|H(%#Q8=KGd2s-40$l`knyc%Jbg+Wt-H5d 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 Date: Tue, 27 Oct 2026 20:25:52 +0100 Subject: [PATCH 45/51] chore: switch license to MIT --- LICENSE.md | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index ab9f3c4..142caa8 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,23 +1,21 @@ -Copyright (C) 2026 FranekGit - All Rights Reserved. - -IMPORTANT NOTICE: PLEASE READ CAREFULLY BEFORE USING THE SOFTWARE. - -1. PROPRIETARY AND CONFIDENTIAL INFORMATION - -The contents of this project, including its source code, are proprietary and confidential. Unauthorized copying, transferring, or reproduction of any part of this project, through any medium, is strictly prohibited. - -2. LICENSE GRANT - -The receipt or possession of the source code and/or any parts thereof does not convey or imply any right to use them for any purpose other than the specific purpose for which they were provided to you. - -3. DISCLAIMER OF WARRANTIES - -The software is provided "AS IS," without warranty of any kind, express or implied, including, but not limited to, the warranties of merchantability, fitness for a particular purpose, and non-infringement. - -4. LIMITATION OF LIABILITY - -In no event shall Sourceduty, its authors, or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the Software or the use or other dealings in the Software. - -5. COPYRIGHT NOTICE - -You are required to include the above copyright notice and this permission notice in all copies or substantial portions of the Software. \ No newline at end of file +MIT License + +Copyright (c) 2026 FranekGit + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 2e093c47160338b62e834ccf471e9c6f54b0e389 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 27 Oct 2026 20:25:52 +0100 Subject: [PATCH 46/51] chore: add PyPI metadata and read version from package --- pyproject.toml | 31 ++++++++++++++++++++++++++----- uv.lock | 7 +++---- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9dda883..c0617c4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,25 @@ [build-system] -requires = ["hatchling"] +requires = ["hatchling>=1.27"] build-backend = "hatchling.build" [project] name = "pse-sdk" -version = "1.5.1" +dynamic = ["version"] description = "Python SDK for the PSE (Polskie Sieci Elektroenergetyczne) public REST API" readme = "README.md" +license = "MIT" +license-files = ["LICENSE.md"] +authors = [{ name = "FranekGit" }] requires-python = ">=3.10" +keywords = ["pse", "energy", "electricity", "poland"] +classifiers = [ + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Typing :: Typed", +] dependencies = [ "requests>=2.28", ] @@ -15,14 +27,23 @@ dependencies = [ [project.optional-dependencies] pandas = ["pandas>=1.5"] +[project.urls] +Homepage = "https://github.com/FranekGit/pse-python-sdk" +Documentation = "https://github.com/FranekGit/pse-python-sdk/blob/main/docs/usage.md" +Changelog = "https://github.com/FranekGit/pse-python-sdk/blob/main/CHANGELOG.md" +Issues = "https://github.com/FranekGit/pse-python-sdk/issues" + [dependency-groups] dev = [ - "pytest", - "responses", - "ruff", + "pytest>=8", + "responses>=0.25", + "ruff>=0.15", "pandas>=1.5", ] +[tool.hatch.version] +path = "src/pse_sdk/__init__.py" + [tool.hatch.build.targets.wheel] packages = ["src/pse_sdk"] diff --git a/uv.lock b/uv.lock index 1593e42..40e0b50 100644 --- a/uv.lock +++ b/uv.lock @@ -621,7 +621,6 @@ wheels = [ [[package]] name = "pse-sdk" -version = "1.5.1" source = { editable = "." } dependencies = [ { name = "requests" }, @@ -652,9 +651,9 @@ provides-extras = ["pandas"] [package.metadata.requires-dev] dev = [ { name = "pandas", specifier = ">=1.5" }, - { name = "pytest" }, - { name = "responses" }, - { name = "ruff" }, + { name = "pytest", specifier = ">=8" }, + { name = "responses", specifier = ">=0.25" }, + { name = "ruff", specifier = ">=0.15" }, ] [[package]] From d96706eb1048469f6a85810d5a2209d7f625633f Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 27 Oct 2026 20:25:52 +0100 Subject: [PATCH 47/51] docs: use absolute links in README for PyPI --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index da11393..b1b9647 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- PSE Logo + PSE Logo

# pse-python-sdk @@ -39,7 +39,7 @@ df = client.fetch( ## Documentation -- [docs/usage.md](docs/usage.md): finding endpoints, date ranges, columns and +- [docs/usage.md](https://github.com/FranekGit/pse-python-sdk/blob/main/docs/usage.md): finding endpoints, date ranges, columns and output formats -- [docs/endpoints.md](docs/endpoints.md): all endpoints, their columns and +- [docs/endpoints.md](https://github.com/FranekGit/pse-python-sdk/blob/main/docs/endpoints.md): all endpoints, their columns and types From ea6d331083871a64e329cf07ab23b12f96607ee2 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 27 Oct 2026 20:25:52 +0100 Subject: [PATCH 48/51] chore: add tests and PyPI publish workflows --- .github/workflows/publish.yml | 56 +++++++++++++++++++++++++++++++++++ .github/workflows/tests.yml | 44 +++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..aea4a7b --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,56 @@ +name: Publish + +# Tag push (v*) publishes to PyPI. Manual run publishes to TestPyPI. +on: + push: + tags: ["v*"] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: astral-sh/setup-uv@v10.3.2 + - run: uv build + - name: Check tag matches package version + if: github.ref_type == 'tag' + run: test -f "dist/pse_sdk-${GITHUB_REF_NAME#v}.tar.gz" + - uses: actions/upload-artifact@v7 + with: + name: dist + path: dist/ + + testpypi: + if: github.event_name == 'workflow_dispatch' + needs: build + runs-on: ubuntu-latest + environment: + name: testpypi + url: https://test.pypi.org/project/pse-sdk/ + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v8 + with: + name: dist + path: dist/ + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + + pypi: + if: github.ref_type == 'tag' + needs: build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/project/pse-sdk/ + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v8 + with: + name: dist + path: dist/ + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..48adad1 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,44 @@ +name: Tests + +on: + push: + branches: [main, develop] + pull_request: + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: astral-sh/setup-uv@v10.3.2 + - run: uv run --locked ruff check src/ scripts/ tests/ + - run: uv run --locked ruff format --check src/ scripts/ tests/ + - run: uv run --locked scripts/gen_docs.py --check + + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + steps: + - uses: actions/checkout@v7 + - uses: astral-sh/setup-uv@v10.3.2 + with: + python-version: ${{ matrix.python-version }} + # manual_test.py calls the live API on import + - run: uv run --locked pytest --ignore=tests/manual_test.py + + test-lowest: + # Oldest dependency versions pyproject.toml allows. pandas 1.5 predates + # numpy 2 and breaks against it, so numpy is held below 2. + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: astral-sh/setup-uv@v10.3.2 + with: + python-version: "3.10" + - run: echo "numpy<2" > constraints.txt + - run: uv venv + - run: uv pip install --resolution lowest-direct -e . --group dev -c constraints.txt + - run: .venv/bin/python -m pytest --ignore=tests/manual_test.py From 6c56c0ef6476caa2f604da72d816a971b80900a6 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 27 Oct 2026 20:25:52 +0100 Subject: [PATCH 49/51] docs: note PyPI release and MIT license in changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83250c9..07e36da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Published on PyPI as `pse-sdk`: `pip install pse-sdk`, or + `pip install "pse-sdk[pandas]"` for DataFrame output - `PSEClient.endpoints()`: names of all endpoints - `PSEClient.columns(endpoint)`: data columns of an endpoint, in API order (meta columns left out). Both read the bundled registry, no HTTP request. @@ -19,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 (#37) ### Changed +- License is now MIT (was proprietary) - Unknown endpoint errors suggest the closest endpoint name (#25) - `HTTPError` from the API includes the API's error message, e.g. `Invalid field to be returned requested: cen_cots` From 6249c92a0d252f7bde500dacd3b5422072a5c149 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 27 Oct 2026 20:47:46 +0100 Subject: [PATCH 50/51] docs: mark SDK as unofficial and update install command --- CHANGELOG.md | 2 ++ README.md | 5 ++++- pyproject.toml | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07e36da..81d20ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - License is now MIT (was proprietary) +- README and package description state the SDK is unofficial and not + affiliated with PSE S.A. - Unknown endpoint errors suggest the closest endpoint name (#25) - `HTTPError` from the API includes the API's error message, e.g. `Invalid field to be returned requested: cen_cots` diff --git a/README.md b/README.md index b1b9647..f23a664 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,13 @@ Python SDK for the PSE (Polskie Sieci Elektroenergetyczne) public reporting API. > API reference: https://api.raporty.pse.pl/app/home +> **Disclaimer:** not affiliated with PSE S.A. + + ## Installation ```bash -pip install git+https://github.com/FranekGit/pse-python-sdk.git +pip install pse-sdk ``` ## Quick Start diff --git a/pyproject.toml b/pyproject.toml index c0617c4..da01a7c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "hatchling.build" [project] name = "pse-sdk" dynamic = ["version"] -description = "Python SDK for the PSE (Polskie Sieci Elektroenergetyczne) public REST API" +description = "Unofficial Python SDK for the PSE (Polskie Sieci Elektroenergetyczne) public REST API" readme = "README.md" license = "MIT" license-files = ["LICENSE.md"] From 7af9bcd1eb92630f237e770c5d9b42f791959c35 Mon Sep 17 00:00:00 2001 From: FranekGit <93712627+FranekGit@users.noreply.github.com> Date: Tue, 27 Oct 2026 20:51:16 +0100 Subject: [PATCH 51/51] chore: release 2.0.0 --- CHANGELOG.md | 12 +++++++++++- README.md | 2 +- src/pse_sdk/__init__.py | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81d20ea..24761f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [2.0.0] - 2026-10-27 ### Added +- `PSEClient.fetch(endpoint, start, end, columns=None, format="dict")`: + query any of the 90 PSE API endpoints for a date range, fetched in + 30-day chunks. Replaces the `get_*` methods - Published on PyPI as `pse-sdk`: `pip install pse-sdk`, or `pip install "pse-sdk[pandas]"` for DataFrame output - `PSEClient.endpoints()`: names of all endpoints @@ -33,6 +36,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 date and key columns of each. The Description column is dropped. - `fetch()` column and date-filter notes moved from `docs/endpoints.md` and the README to the new `docs/usage.md` +- Development uses uv: dev dependencies in a uv dependency group, + `uv.lock` and `.python-version` committed +- Package version is read from `pse_sdk.__version__` (hatch dynamic + version) +- GitHub Actions: `tests.yml` runs ruff, `gen_docs.py --check` and tests on + Python 3.10–3.14; `publish.yml` publishes to PyPI on `v*` tag push + (TestPyPI on manual run) ### Removed - **Breaking:** all 32 `get_*` methods of `PSEClient` (`get_cen`, `get_csdac`, diff --git a/README.md b/README.md index f23a664..0cfb509 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ # pse-python-sdk -![Version](https://img.shields.io/badge/version-v1.5.1-blue) +![Version](https://img.shields.io/badge/version-v2.0.0-blue) Python SDK for the PSE (Polskie Sieci Elektroenergetyczne) public reporting API. diff --git a/src/pse_sdk/__init__.py b/src/pse_sdk/__init__.py index f8efb39..093c317 100644 --- a/src/pse_sdk/__init__.py +++ b/src/pse_sdk/__init__.py @@ -1,5 +1,5 @@ from pse_sdk._client import PSEClient from pse_sdk._format import OutputFormat -__version__ = "1.5.1" +__version__ = "2.0.0" __all__ = ["OutputFormat", "PSEClient"]