usdata 0.29.0__tar.gz → 0.31.0__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.
- {usdata-0.29.0 → usdata-0.31.0}/PKG-INFO +2 -2
- {usdata-0.29.0 → usdata-0.31.0}/README.md +1 -1
- {usdata-0.29.0 → usdata-0.31.0}/pyproject.toml +1 -1
- {usdata-0.29.0 → usdata-0.31.0}/pyproject.toml.orig +1 -1
- usdata-0.31.0/src/usdata/_census.py +51 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/_fetch.py +14 -1
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/_grib.py +12 -7
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/cache.py +14 -5
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/data/places.csv +3300 -3292
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/data/places.sources.json +17 -1
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/data/registry.yaml +43 -8
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/manifest.py +79 -8
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/models.py +20 -4
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/provenance.py +1 -3
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/base.py +22 -0
- usdata-0.31.0/src/usdata/providers/census/__init__.py +1 -0
- usdata-0.31.0/src/usdata/providers/census/acs.py +243 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/epa/aqs.py +4 -1
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/fema/declarations.py +6 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/noaa/coastwatch.py +5 -5
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/noaa/nws_vtec.py +4 -1
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/pull.py +40 -11
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/query.py +54 -5
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/readers.py +21 -10
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/testing.py +2 -1
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/__init__.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/__main__.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/_aqs.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/_files.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/_hurdat2.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/_netcdf.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/_progress.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/_radar.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/cache_ops.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/cite.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/cli/__init__.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/cli/app.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/cli/cache.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/cli/cite.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/cli/doctor.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/cli/inspect.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/cli/progress.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/data/nexrad_sites.csv +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/doctor.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/inspect.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/mirror.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/protocols/__init__.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/protocols/erddap.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/protocols/http.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/protocols/listing.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/protocols/s3.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/__init__.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/credentials.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/epa/__init__.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/fema/__init__.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/http.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/noaa/__init__.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/noaa/coops.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/noaa/gfs.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/noaa/ghcnd.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/noaa/glm.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/noaa/goes.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/noaa/grib_index.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/noaa/gsom.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/noaa/gsoy.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/noaa/hrrr.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/noaa/hurdat2.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/noaa/ibtracs.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/noaa/lcd.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/noaa/mrms.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/noaa/nbm.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/noaa/nexrad.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/noaa/nexrad_level3.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/noaa/normals.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/noaa/rap.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/noaa/sites.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/noaa/spc.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/noaa/storm_events.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/params.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/usgs/__init__.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/usgs/daily.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/providers/usgs/earthquakes.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/py.typed +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/registry.py +0 -0
- {usdata-0.29.0 → usdata-0.31.0}/src/usdata/selection.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: usdata
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.31.0
|
|
4
4
|
Summary: Unified Python SDK and CLI for discovering, fetching, and tracking provenance of U.S. public scientific data
|
|
5
5
|
Keywords: noaa,usgs,open-data,scientific-data,provenance,reproducible-research,weather,climate,meteorology
|
|
6
6
|
Author: Jake Van Slyke
|
|
@@ -113,7 +113,7 @@ breaking changes.
|
|
|
113
113
|
| [docs.usdata.dev](https://docs.usdata.dev/) | How to use it: [install](https://docs.usdata.dev/install/), [getting started](https://docs.usdata.dev/getting-started/), guides, dataset notes, and reference |
|
|
114
114
|
| [Severe-weather case study](https://usdata.dev/studies/severe-weather-case-study/) | One tornado, six sources, one manifest and lockfile, ending in a citation |
|
|
115
115
|
|
|
116
|
-
Twenty-
|
|
116
|
+
Twenty-eight datasets are available today and eighteen more are planned, grouped
|
|
117
117
|
by agency and product family in the [catalog](docs/providers/README.md).
|
|
118
118
|
|
|
119
119
|
## How this compares
|
|
@@ -68,7 +68,7 @@ breaking changes.
|
|
|
68
68
|
| [docs.usdata.dev](https://docs.usdata.dev/) | How to use it: [install](https://docs.usdata.dev/install/), [getting started](https://docs.usdata.dev/getting-started/), guides, dataset notes, and reference |
|
|
69
69
|
| [Severe-weather case study](https://usdata.dev/studies/severe-weather-case-study/) | One tornado, six sources, one manifest and lockfile, ending in a citation |
|
|
70
70
|
|
|
71
|
-
Twenty-
|
|
71
|
+
Twenty-eight datasets are available today and eighteen more are planned, grouped
|
|
72
72
|
by agency and product family in the [catalog](docs/providers/README.md).
|
|
73
73
|
|
|
74
74
|
## How this compares
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Local reading of Census Data API tables into a pandas DataFrame, behind pandas.
|
|
2
|
+
|
|
3
|
+
A fetched ``census:acs-5year`` file is the service's response as served: a JSON
|
|
4
|
+
array whose first row names the columns and whose other rows are one geography
|
|
5
|
+
each, every value a string. The table is those rows under those names, with the
|
|
6
|
+
estimate and margin-of-error columns as numbers and everything else, geography
|
|
7
|
+
codes and their leading zeros included, as text.
|
|
8
|
+
|
|
9
|
+
Negative annotation codes such as ``-555555555`` stay the numbers they are.
|
|
10
|
+
Each means something different (too few sample cases, not applicable, a
|
|
11
|
+
controlled estimate), so they are not turned into missing values (ADR 0045).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import re
|
|
18
|
+
from importlib import import_module
|
|
19
|
+
from typing import TYPE_CHECKING, Any
|
|
20
|
+
|
|
21
|
+
from usdata.readers import MissingReaderDependency, source_attrs
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from usdata._fetch import FetchedAsset
|
|
25
|
+
|
|
26
|
+
NUMERIC = re.compile(r"[A-Z]\d{5}[A-Z0-9]*_\d{3}[EM]")
|
|
27
|
+
"""An estimate (``...E``) or margin of error (``...M``) variable, such as ``B01003_001E``.
|
|
28
|
+
|
|
29
|
+
Annotation columns (``...EA``, ``...MA``) and ``NAME`` do not match and stay text.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def open_acs(fetched: FetchedAsset) -> Any:
|
|
34
|
+
"""Parse a fetched ACS table into a pandas DataFrame, one row per geography."""
|
|
35
|
+
try:
|
|
36
|
+
pandas = import_module("pandas")
|
|
37
|
+
except ModuleNotFoundError as error:
|
|
38
|
+
if error.name != "pandas":
|
|
39
|
+
raise
|
|
40
|
+
raise MissingReaderDependency(
|
|
41
|
+
'ACS reading requires pandas; install it with: pip install "usdata[pandas]" '
|
|
42
|
+
'(or uv add "usdata[pandas]")'
|
|
43
|
+
) from error
|
|
44
|
+
# Reading a fetched asset is strictly local; the cached file is never rewritten.
|
|
45
|
+
header, *rows = json.loads(fetched.path.read_text(encoding="utf-8"))
|
|
46
|
+
frame = pandas.DataFrame(rows, columns=header, dtype="string")
|
|
47
|
+
for column in header:
|
|
48
|
+
if NUMERIC.fullmatch(column):
|
|
49
|
+
frame[column] = pandas.to_numeric(frame[column])
|
|
50
|
+
frame.attrs["usdata"] = source_attrs(fetched)
|
|
51
|
+
return frame
|
|
@@ -39,7 +39,7 @@ class FetchedAsset(BaseModel):
|
|
|
39
39
|
def open(self) -> Any:
|
|
40
40
|
"""Open local data with the reader its format implies, with that reader's defaults.
|
|
41
41
|
|
|
42
|
-
CSV and ERDDAP CSV, HURDAT2
|
|
42
|
+
CSV and ERDDAP CSV, HURDAT2, AQS daily JSON, and ACS tables return a pandas DataFrame;
|
|
43
43
|
NetCDF4 and GRIB2 a loaded xarray Dataset; NEXRAD Level II an xarray
|
|
44
44
|
DataTree. Provenance is kept in the result's ``attrs["usdata"]``. A file
|
|
45
45
|
that needs options, or whose metadata leaves its format ambiguous, is
|
|
@@ -234,6 +234,19 @@ def _fetch_with(
|
|
|
234
234
|
``staging`` is passed to each fetch; see ``_fetch_asset``.
|
|
235
235
|
"""
|
|
236
236
|
assets = ordered(adapter.list_assets(query))
|
|
237
|
+
return _fetch_listed(adapter, dataset, assets, root=root, force=force, staging=staging)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _fetch_listed(
|
|
241
|
+
adapter: Provider,
|
|
242
|
+
dataset: Dataset,
|
|
243
|
+
assets: list[Asset],
|
|
244
|
+
*,
|
|
245
|
+
root: Path | None = None,
|
|
246
|
+
force: bool = False,
|
|
247
|
+
staging: Path | None = None,
|
|
248
|
+
) -> list[FetchedAsset]:
|
|
249
|
+
"""Fetch assets already listed and put in order, as the loop does once it has listed them."""
|
|
237
250
|
_progress.batch([asset.size for asset in assets])
|
|
238
251
|
return [
|
|
239
252
|
_fetch_asset(dataset, a, adapter, root=root, force=force, staging=staging) for a in assets
|
|
@@ -304,15 +304,19 @@ def base_name(short_name: str | None, parameter: Sequence[Any], source: str) ->
|
|
|
304
304
|
return _product_name(source) or f"parameter_{discipline}_{category}_{number}"
|
|
305
305
|
|
|
306
306
|
|
|
307
|
-
def _time(date: Any, time: Any) -> str | None:
|
|
307
|
+
def _time(date: Any, time: Any, second: Any = None) -> str | None:
|
|
308
|
+
"""An ecCodes date and ``HHMM`` time as ISO 8601 UTC, with the message's seconds.
|
|
309
|
+
|
|
310
|
+
ecCodes spells times to the minute and keeps the reference time's seconds
|
|
311
|
+
in a key of their own, which MRMS sets: a file stamped ``20:00:41`` would
|
|
312
|
+
otherwise read as ``20:00:00``. A validity time is the reference time plus
|
|
313
|
+
a whole-minute step, so it carries the same seconds.
|
|
314
|
+
"""
|
|
308
315
|
if date is None or time is None:
|
|
309
316
|
return None
|
|
310
317
|
try:
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
.replace(tzinfo=UTC)
|
|
314
|
-
.isoformat()
|
|
315
|
-
)
|
|
318
|
+
stamp = datetime.strptime(f"{int(date):08d}{int(time):04d}", "%Y%m%d%H%M")
|
|
319
|
+
return stamp.replace(second=int(second or 0), tzinfo=UTC).isoformat()
|
|
316
320
|
except ValueError:
|
|
317
321
|
return None
|
|
318
322
|
|
|
@@ -494,11 +498,12 @@ def open_grib2(
|
|
|
494
498
|
# The file's own spelling stays beside the rewritten one, as cfgrib keeps it.
|
|
495
499
|
attrs["GRIB_units"] = units
|
|
496
500
|
attrs["units"] = plain
|
|
501
|
+
second = _get(eccodes, h, "second", int)
|
|
497
502
|
for label, date_key, time_key in (
|
|
498
503
|
("reference_time", "dataDate", "dataTime"),
|
|
499
504
|
("valid_time", "validityDate", "validityTime"),
|
|
500
505
|
):
|
|
501
|
-
stamp = _time(attrs.get(date_key), attrs.get(time_key))
|
|
506
|
+
stamp = _time(attrs.get(date_key), attrs.get(time_key), second)
|
|
502
507
|
if stamp:
|
|
503
508
|
attrs[label] = stamp
|
|
504
509
|
message: dict[str, Any] = {
|
|
@@ -10,6 +10,8 @@ from pathlib import Path
|
|
|
10
10
|
from usdata.models import Asset
|
|
11
11
|
|
|
12
12
|
ENV_VAR = "USDATA_CACHE_DIR"
|
|
13
|
+
SIDECAR_SUFFIX = ".provenance.json"
|
|
14
|
+
"""What a provenance sidecar's name adds to its data file's; ``provenance`` names it from here."""
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
def cache_dir() -> Path:
|
|
@@ -38,7 +40,9 @@ def cached_path(dataset_id: str, asset_id: str, root: Path | None = None) -> Pat
|
|
|
38
40
|
The path the cache uses for that asset, whether or not it exists.
|
|
39
41
|
|
|
40
42
|
Raises:
|
|
41
|
-
ValueError: Either id is unsafe,
|
|
43
|
+
ValueError: Either id is unsafe, names a sidecar or temporary file, or the
|
|
44
|
+
path would escape the cache root. Symlinks inside the root are
|
|
45
|
+
followed like any directory, so part of the cache can live elsewhere.
|
|
42
46
|
"""
|
|
43
47
|
provider, sep, name = dataset_id.partition(":")
|
|
44
48
|
if not sep or any(
|
|
@@ -48,11 +52,16 @@ def cached_path(dataset_id: str, asset_id: str, root: Path | None = None) -> Pat
|
|
|
48
52
|
safe_id = asset_id.replace("/", "_")
|
|
49
53
|
if not safe_id or safe_id in {".", ".."} or "\\" in safe_id or "\x00" in safe_id:
|
|
50
54
|
raise ValueError(f"unsafe asset id: {asset_id!r}")
|
|
55
|
+
# A file of this name would be taken for another asset's sidecar, or for a temporary file.
|
|
56
|
+
if safe_id.endswith(SIDECAR_SUFFIX) or (safe_id.startswith(".") and safe_id.endswith(".part")):
|
|
57
|
+
raise ValueError(f"asset id {asset_id!r} names a file the cache keeps for itself")
|
|
51
58
|
base = (root or cache_dir()).expanduser().resolve()
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
home = base / provider / name
|
|
60
|
+
path = home / safe_id
|
|
61
|
+
# Checked on the path as written, so a directory the user symlinked elsewhere is still
|
|
62
|
+
# inside the cache; only an id that re-anchors the path, such as C:x on Windows, is not.
|
|
63
|
+
if path.parent != home:
|
|
64
|
+
raise ValueError(f"cache path escapes root: {path}")
|
|
56
65
|
return path
|
|
57
66
|
|
|
58
67
|
|