eolas-data 1.3.4__tar.gz → 1.3.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {eolas_data-1.3.4 → eolas_data-1.3.5}/PKG-INFO +2 -2
- {eolas_data-1.3.4 → eolas_data-1.3.5}/README.md +1 -1
- {eolas_data-1.3.4 → eolas_data-1.3.5}/eolas_data/client.py +110 -51
- {eolas_data-1.3.4 → eolas_data-1.3.5}/pyproject.toml +1 -1
- {eolas_data-1.3.4 → eolas_data-1.3.5}/tests/test_progress.py +19 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/.github/workflows/catalog-drift.yml +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/.github/workflows/publish.yml +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/.github/workflows/smoke.yml +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/.github/workflows/test.yml +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/.gitignore +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/eolas_data/__init__.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/eolas_data/_dataset_names.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/eolas_data/_regen_names.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/eolas_data/cdc.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/eolas_data/cli.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/eolas_data/console.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/eolas_data/dataset.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/eolas_data/exceptions.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/eolas_data/library.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/eolas_data/meta.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/eolas_data/rows.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/eolas_data/schedule.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/eolas_data/search.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/scripts/preflight.sh +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/tests/test_as_arrow.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/tests/test_cdc_roundtrip.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/tests/test_cli.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/tests/test_client.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/tests/test_keyring.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/tests/test_library.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/tests/test_meta.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/tests/test_rows.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/tests/test_schedule.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/tests/test_search.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/tests/test_smoke_live.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/tests/test_sync_bulk.py +0 -0
- {eolas_data-1.3.4 → eolas_data-1.3.5}/tests/test_sync_changes.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: eolas-data
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.5
|
|
4
4
|
Summary: Python client for the eolas.fyi statistical data API (NZ, Australia, OECD)
|
|
5
5
|
Project-URL: Homepage, https://eolas.fyi
|
|
6
6
|
Project-URL: Documentation, https://docs.eolas.fyi/
|
|
@@ -231,7 +231,7 @@ r = client.sync_bulk("nz_cpi", path="nz_cpi.parquet")
|
|
|
231
231
|
path = client.download_bulk("treasury_fiscal_spending", path="t.parquet")
|
|
232
232
|
```
|
|
233
233
|
|
|
234
|
-
**Progress bars:** `
|
|
234
|
+
**Progress bars:** `get_local()` shows two phases in interactive sessions — a **download** byte bar while fetching from CDN, then a **read** spinner while Parquet/GeoParquet is loaded (often the slow part on multi-million-row geo datasets). Control with `progress=True` (both), `False` (neither), `"download"`, or `"read"`. Set `EOLAS_NO_PROGRESS=1` to suppress both in batch scripts. Cached files skip the download bar and print an informative message instead.
|
|
235
235
|
|
|
236
236
|
CLI mirror: `eolas download <name>` for one-shot, `eolas sync <name> [--watch hourly]` for an incremental check. Full docs: [docs.eolas.fyi/bulk-downloads/](https://docs.eolas.fyi/bulk-downloads/).
|
|
237
237
|
|
|
@@ -183,7 +183,7 @@ r = client.sync_bulk("nz_cpi", path="nz_cpi.parquet")
|
|
|
183
183
|
path = client.download_bulk("treasury_fiscal_spending", path="t.parquet")
|
|
184
184
|
```
|
|
185
185
|
|
|
186
|
-
**Progress bars:** `
|
|
186
|
+
**Progress bars:** `get_local()` shows two phases in interactive sessions — a **download** byte bar while fetching from CDN, then a **read** spinner while Parquet/GeoParquet is loaded (often the slow part on multi-million-row geo datasets). Control with `progress=True` (both), `False` (neither), `"download"`, or `"read"`. Set `EOLAS_NO_PROGRESS=1` to suppress both in batch scripts. Cached files skip the download bar and print an informative message instead.
|
|
187
187
|
|
|
188
188
|
CLI mirror: `eolas download <name>` for one-shot, `eolas sync <name> [--watch hourly]` for an incremental check. Full docs: [docs.eolas.fyi/bulk-downloads/](https://docs.eolas.fyi/bulk-downloads/).
|
|
189
189
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import contextlib
|
|
3
4
|
import datetime
|
|
4
5
|
import json
|
|
5
6
|
import logging
|
|
@@ -7,7 +8,10 @@ import os
|
|
|
7
8
|
import pathlib
|
|
8
9
|
import sys
|
|
9
10
|
from dataclasses import dataclass
|
|
10
|
-
from typing import Optional, Union
|
|
11
|
+
from typing import Literal, Optional, Union
|
|
12
|
+
|
|
13
|
+
ProgressControl = Union[bool, str, None]
|
|
14
|
+
ProgressPhase = Literal["download", "read"]
|
|
11
15
|
|
|
12
16
|
import pandas as pd
|
|
13
17
|
import requests
|
|
@@ -362,7 +366,7 @@ class Client:
|
|
|
362
366
|
freshness: str = "auto",
|
|
363
367
|
format: str = "parquet",
|
|
364
368
|
path: Optional[Union[str, "pathlib.Path"]] = None,
|
|
365
|
-
progress:
|
|
369
|
+
progress: ProgressControl = None,
|
|
366
370
|
) -> "Union[pathlib.Path, bytes]":
|
|
367
371
|
"""Download a complete dataset as a single binary file.
|
|
368
372
|
|
|
@@ -387,14 +391,10 @@ class Client:
|
|
|
387
391
|
bytes. Pass a ``str`` or ``pathlib.Path`` to write to disk and
|
|
388
392
|
return the resolved path. Parent directories are created if
|
|
389
393
|
needed.
|
|
390
|
-
progress: Control the download progress bar.
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
``True`` forces the bar on regardless (useful in log-tailing
|
|
395
|
-
scenarios). ``False`` forces it off. When ``path`` is
|
|
396
|
-
``None`` (bytes mode) progress is always disabled — there is
|
|
397
|
-
no file path to label.
|
|
394
|
+
progress: Control the download progress bar (``"download"`` phase).
|
|
395
|
+
See :meth:`get_local` for the full selector vocabulary
|
|
396
|
+
(``"read"`` applies only to :meth:`get_local`). When ``path``
|
|
397
|
+
is ``None`` (bytes mode) progress is always disabled.
|
|
398
398
|
|
|
399
399
|
Returns:
|
|
400
400
|
``pathlib.Path`` when ``path`` is set (the resolved, written path).
|
|
@@ -467,13 +467,13 @@ class Client:
|
|
|
467
467
|
out = pathlib.Path(path).expanduser().resolve()
|
|
468
468
|
out.parent.mkdir(parents=True, exist_ok=True)
|
|
469
469
|
|
|
470
|
-
show = self._resolve_show_progress(progress)
|
|
470
|
+
show = self._resolve_show_progress(progress, "download")
|
|
471
471
|
resp = self._raw_bulk_get(bulk_path, params=params, stream=True)
|
|
472
472
|
total = int(resp.headers.get("Content-Length", 0)) or None
|
|
473
473
|
self._stream_to_file_with_progress(
|
|
474
474
|
resp, out,
|
|
475
475
|
total_bytes=total,
|
|
476
|
-
label=out.name,
|
|
476
|
+
label=f"Downloading {out.name}",
|
|
477
477
|
show_progress=show,
|
|
478
478
|
)
|
|
479
479
|
return out
|
|
@@ -485,7 +485,7 @@ class Client:
|
|
|
485
485
|
path: Union[str, "pathlib.Path"],
|
|
486
486
|
format: str = "parquet",
|
|
487
487
|
freshness: str = "auto",
|
|
488
|
-
progress:
|
|
488
|
+
progress: ProgressControl = None,
|
|
489
489
|
) -> SyncResult:
|
|
490
490
|
"""Incrementally sync a bulk dataset file — only re-download when the snapshot changes.
|
|
491
491
|
|
|
@@ -514,11 +514,10 @@ class Client:
|
|
|
514
514
|
``"geoparquet"``.
|
|
515
515
|
freshness: ``"auto"`` (default), ``"monthly"``, or ``"current"``.
|
|
516
516
|
Passed verbatim to the bulk endpoint.
|
|
517
|
-
progress: Control the download progress bar.
|
|
518
|
-
|
|
519
|
-
``
|
|
520
|
-
|
|
521
|
-
is shown regardless of this setting.
|
|
517
|
+
progress: Control the download progress bar (``"download"`` phase).
|
|
518
|
+
See :meth:`get_local` for the full selector vocabulary.
|
|
519
|
+
When ``status="unchanged"`` no download bar is shown; an
|
|
520
|
+
informative cached-file message is printed instead.
|
|
522
521
|
|
|
523
522
|
Returns:
|
|
524
523
|
A :class:`SyncResult` dataclass with ``status``,
|
|
@@ -606,6 +605,7 @@ class Client:
|
|
|
606
605
|
and prev.get("snapshot_id") == current_sid
|
|
607
606
|
and out.exists()
|
|
608
607
|
):
|
|
608
|
+
print(f"Using cached {out.name} (up to date).", file=sys.stderr)
|
|
609
609
|
return SyncResult(
|
|
610
610
|
status="unchanged",
|
|
611
611
|
previous_snapshot_id=prev.get("snapshot_id"),
|
|
@@ -617,14 +617,14 @@ class Client:
|
|
|
617
617
|
# Download (atomic replace).
|
|
618
618
|
out.parent.mkdir(parents=True, exist_ok=True)
|
|
619
619
|
tmp = out.with_suffix(out.suffix + f".eolas-tmp-{os.urandom(4).hex()}")
|
|
620
|
-
show = self._resolve_show_progress(progress)
|
|
620
|
+
show = self._resolve_show_progress(progress, "download")
|
|
621
621
|
try:
|
|
622
622
|
resp = self._raw_bulk_get(bulk_path, params=params, stream=True)
|
|
623
623
|
total = int(resp.headers.get("Content-Length", 0)) or None
|
|
624
624
|
bytes_dl = self._stream_to_file_with_progress(
|
|
625
625
|
resp, tmp,
|
|
626
626
|
total_bytes=total,
|
|
627
|
-
label=out.name,
|
|
627
|
+
label=f"Downloading {out.name}",
|
|
628
628
|
show_progress=show,
|
|
629
629
|
)
|
|
630
630
|
if bytes_dl == 0:
|
|
@@ -1159,28 +1159,73 @@ class Client:
|
|
|
1159
1159
|
# ------------------------------------------------------------------
|
|
1160
1160
|
|
|
1161
1161
|
@staticmethod
|
|
1162
|
-
def
|
|
1163
|
-
"""
|
|
1164
|
-
|
|
1165
|
-
Priority (highest first):
|
|
1166
|
-
1. Explicit ``progress=True/False`` kwarg from the caller.
|
|
1167
|
-
2. ``EOLAS_NO_PROGRESS=1`` environment variable — always suppresses.
|
|
1168
|
-
3. Jupyter / IPython kernel detection — Jupyter wraps stdout in an
|
|
1169
|
-
``OutStream``, so ``sys.stdout.isatty()`` returns False even when
|
|
1170
|
-
the user is clearly in an interactive notebook session. If
|
|
1171
|
-
``ipykernel`` is loaded (the standard signal used by tqdm itself),
|
|
1172
|
-
we're in a notebook → show the bar; ``tqdm.auto`` then renders it
|
|
1173
|
-
as an ipywidget (or text bar as fallback).
|
|
1174
|
-
4. ``sys.stdout.isatty()`` auto-detection for plain terminals.
|
|
1175
|
-
"""
|
|
1176
|
-
if progress is not None:
|
|
1177
|
-
return bool(progress)
|
|
1162
|
+
def _progress_auto_detect() -> bool:
|
|
1163
|
+
"""Whether progress feedback should show when ``progress`` is None."""
|
|
1178
1164
|
if os.getenv("EOLAS_NO_PROGRESS", "").strip() in ("1", "true", "yes"):
|
|
1179
1165
|
return False
|
|
1180
1166
|
if "ipykernel" in sys.modules:
|
|
1181
1167
|
return True
|
|
1182
1168
|
return sys.stdout.isatty()
|
|
1183
1169
|
|
|
1170
|
+
@staticmethod
|
|
1171
|
+
def _resolve_progress_phases(progress: ProgressControl) -> dict[str, bool]:
|
|
1172
|
+
"""Map ``progress`` to download/read phase flags.
|
|
1173
|
+
|
|
1174
|
+
Accepts ``None``, ``True``/``False``, or ``"both"``, ``"download"``,
|
|
1175
|
+
``"read"``, ``"none"`` (and ``"all"`` as alias for ``"both"``).
|
|
1176
|
+
"""
|
|
1177
|
+
if progress is False:
|
|
1178
|
+
return {"download": False, "read": False}
|
|
1179
|
+
if progress is True:
|
|
1180
|
+
return {"download": True, "read": True}
|
|
1181
|
+
if isinstance(progress, str):
|
|
1182
|
+
key = progress.strip().lower()
|
|
1183
|
+
table = {
|
|
1184
|
+
"both": (True, True),
|
|
1185
|
+
"all": (True, True),
|
|
1186
|
+
"download": (True, False),
|
|
1187
|
+
"read": (False, True),
|
|
1188
|
+
"none": (False, False),
|
|
1189
|
+
}
|
|
1190
|
+
if key not in table:
|
|
1191
|
+
raise ValueError(
|
|
1192
|
+
"progress must be True, False, None, or one of "
|
|
1193
|
+
"'both', 'download', 'read', 'none'."
|
|
1194
|
+
)
|
|
1195
|
+
d, r = table[key]
|
|
1196
|
+
return {"download": d, "read": r}
|
|
1197
|
+
auto = Client._progress_auto_detect()
|
|
1198
|
+
return {"download": auto, "read": auto}
|
|
1199
|
+
|
|
1200
|
+
@staticmethod
|
|
1201
|
+
def _resolve_show_progress(
|
|
1202
|
+
progress: ProgressControl,
|
|
1203
|
+
phase: ProgressPhase = "download",
|
|
1204
|
+
) -> bool:
|
|
1205
|
+
"""Resolve ``progress`` for one bulk phase (download or read)."""
|
|
1206
|
+
return Client._resolve_progress_phases(progress)[phase]
|
|
1207
|
+
|
|
1208
|
+
@staticmethod
|
|
1209
|
+
@contextlib.contextmanager
|
|
1210
|
+
def _with_read_progress(label: str, show: bool):
|
|
1211
|
+
"""Indeterminate spinner while materialising a cached bulk file."""
|
|
1212
|
+
if not show:
|
|
1213
|
+
yield
|
|
1214
|
+
return
|
|
1215
|
+
import tqdm.auto
|
|
1216
|
+
|
|
1217
|
+
bar = tqdm.auto.tqdm(
|
|
1218
|
+
total=1,
|
|
1219
|
+
desc=f"Loading {label} from disk",
|
|
1220
|
+
leave=False,
|
|
1221
|
+
bar_format="{desc}…",
|
|
1222
|
+
)
|
|
1223
|
+
try:
|
|
1224
|
+
yield
|
|
1225
|
+
finally:
|
|
1226
|
+
bar.update(1)
|
|
1227
|
+
bar.close()
|
|
1228
|
+
|
|
1184
1229
|
@staticmethod
|
|
1185
1230
|
def _stream_to_file_with_progress(
|
|
1186
1231
|
resp: "requests.Response",
|
|
@@ -1796,7 +1841,7 @@ class Client:
|
|
|
1796
1841
|
as_geo: Optional[bool] = None,
|
|
1797
1842
|
as_arrow: bool = False,
|
|
1798
1843
|
meta: bool = True,
|
|
1799
|
-
progress:
|
|
1844
|
+
progress: ProgressControl = None,
|
|
1800
1845
|
) -> "pd.DataFrame":
|
|
1801
1846
|
"""Download (or serve from cache) a whole dataset as a local DataFrame.
|
|
1802
1847
|
|
|
@@ -1833,7 +1878,14 @@ class Client:
|
|
|
1833
1878
|
meta: When ``True`` (default), attach table/column metadata from
|
|
1834
1879
|
:meth:`info` (session-cached). Pass ``False`` to skip the extra
|
|
1835
1880
|
round-trip.
|
|
1836
|
-
progress:
|
|
1881
|
+
progress: Control progress for two phases: **download** (streaming
|
|
1882
|
+
byte bar via :meth:`sync_bulk`) and **read** (spinner while
|
|
1883
|
+
Parquet/GeoParquet is loaded into a DataFrame). ``None``
|
|
1884
|
+
(default) enables both in interactive sessions. ``True``/``False``
|
|
1885
|
+
force both on/off. Use ``"download"``, ``"read"``, ``"both"``,
|
|
1886
|
+
or ``"none"`` for one phase only. Suppressed by
|
|
1887
|
+
``EOLAS_NO_PROGRESS=1``. Cached snapshots skip the download bar
|
|
1888
|
+
and print an informative message instead.
|
|
1837
1889
|
|
|
1838
1890
|
Returns:
|
|
1839
1891
|
``pd.DataFrame`` (tabular) or ``geopandas.GeoDataFrame`` (geo +
|
|
@@ -1896,24 +1948,31 @@ class Client:
|
|
|
1896
1948
|
# ---- sync (download if needed, HEAD check if cached) -----------------
|
|
1897
1949
|
self.sync_bulk(name, path=file_path, format=fmt, freshness=freshness, progress=progress)
|
|
1898
1950
|
|
|
1951
|
+
show_read = self._resolve_show_progress(progress, "read")
|
|
1952
|
+
read_label = file_path.name
|
|
1953
|
+
|
|
1899
1954
|
# ---- read the local file into a DataFrame ----------------------------
|
|
1900
1955
|
if as_arrow:
|
|
1901
1956
|
import pyarrow.parquet as _pq
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1957
|
+
with self._with_read_progress(read_label, show_read):
|
|
1958
|
+
if fmt == "csv_gz":
|
|
1959
|
+
import pyarrow as _pa
|
|
1960
|
+
return _pa.Table.from_pandas(
|
|
1961
|
+
pd.read_csv(file_path), preserve_index=False,
|
|
1962
|
+
)
|
|
1963
|
+
return _pq.read_table(file_path)
|
|
1906
1964
|
|
|
1907
1965
|
def _read_bulk_file(path: pathlib.Path, bulk_fmt: str):
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1966
|
+
with self._with_read_progress(read_label, show_read):
|
|
1967
|
+
if bulk_fmt == "csv_gz":
|
|
1968
|
+
return pd.read_csv(path)
|
|
1969
|
+
if bulk_fmt == "geoparquet" and resolved_as_geo:
|
|
1970
|
+
try:
|
|
1971
|
+
import geopandas as gpd
|
|
1972
|
+
return gpd.read_parquet(path)
|
|
1973
|
+
except ImportError:
|
|
1974
|
+
pass
|
|
1975
|
+
return pd.read_parquet(path)
|
|
1917
1976
|
|
|
1918
1977
|
try:
|
|
1919
1978
|
result = _read_bulk_file(file_path, fmt)
|
|
@@ -421,6 +421,25 @@ def test_eolas_no_progress_env_suppresses_bar(client, tmp_path, monkeypatch):
|
|
|
421
421
|
assert all(disabled_values), "EOLAS_NO_PROGRESS=1 must disable tqdm even when isatty=True"
|
|
422
422
|
|
|
423
423
|
|
|
424
|
+
def test_progress_phase_selectors():
|
|
425
|
+
from eolas_data.client import Client
|
|
426
|
+
|
|
427
|
+
phases = Client._resolve_progress_phases("download")
|
|
428
|
+
assert phases == {"download": True, "read": False}
|
|
429
|
+
|
|
430
|
+
phases = Client._resolve_progress_phases("read")
|
|
431
|
+
assert phases == {"download": False, "read": True}
|
|
432
|
+
|
|
433
|
+
phases = Client._resolve_progress_phases("both")
|
|
434
|
+
assert phases == {"download": True, "read": True}
|
|
435
|
+
|
|
436
|
+
phases = Client._resolve_progress_phases("none")
|
|
437
|
+
assert phases == {"download": False, "read": False}
|
|
438
|
+
|
|
439
|
+
assert Client._resolve_show_progress("read", "read") is True
|
|
440
|
+
assert Client._resolve_show_progress("download", "read") is False
|
|
441
|
+
|
|
442
|
+
|
|
424
443
|
def test_progress_resolver_detects_jupyter():
|
|
425
444
|
"""Jupyter wraps stdout so isatty() is False, but the user IS interactive
|
|
426
445
|
and tqdm.auto can render a widget. Resolver must return True when
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|