usdata 0.32.0__tar.gz → 0.34.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.
Files changed (95) hide show
  1. {usdata-0.32.0 → usdata-0.34.0}/PKG-INFO +2 -2
  2. {usdata-0.32.0 → usdata-0.34.0}/README.md +1 -1
  3. {usdata-0.32.0 → usdata-0.34.0}/pyproject.toml +1 -1
  4. {usdata-0.32.0 → usdata-0.34.0}/pyproject.toml.orig +1 -1
  5. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/__init__.py +3 -0
  6. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/_grib.py +7 -2
  7. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/cli/app.py +92 -25
  8. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/data/registry.yaml +357 -21
  9. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/inspect.py +18 -15
  10. usdata-0.34.0/src/usdata/messages.py +60 -0
  11. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/models.py +67 -0
  12. usdata-0.34.0/src/usdata/protocols/arcgis.py +405 -0
  13. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/protocols/s3.py +60 -20
  14. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/provenance.py +1 -0
  15. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/base.py +31 -1
  16. usdata-0.34.0/src/usdata/providers/noaa/enso.py +76 -0
  17. usdata-0.34.0/src/usdata/providers/noaa/eri.py +468 -0
  18. usdata-0.34.0/src/usdata/providers/noaa/ersst.py +95 -0
  19. usdata-0.34.0/src/usdata/providers/noaa/geotiff.py +191 -0
  20. usdata-0.34.0/src/usdata/providers/noaa/goes.py +222 -0
  21. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/noaa/grib_index.py +24 -37
  22. usdata-0.34.0/src/usdata/providers/noaa/gsom_files.py +83 -0
  23. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/noaa/hrrr.py +18 -1
  24. usdata-0.34.0/src/usdata/providers/noaa/nws_damage.py +381 -0
  25. usdata-0.34.0/src/usdata/providers/noaa/utm.py +54 -0
  26. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/testing.py +8 -4
  27. usdata-0.32.0/src/usdata/providers/noaa/goes.py +0 -171
  28. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/__main__.py +0 -0
  29. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/_aqs.py +0 -0
  30. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/_census.py +0 -0
  31. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/_fetch.py +0 -0
  32. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/_files.py +0 -0
  33. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/_hurdat2.py +0 -0
  34. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/_netcdf.py +0 -0
  35. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/_progress.py +0 -0
  36. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/_radar.py +0 -0
  37. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/cache.py +0 -0
  38. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/cache_ops.py +0 -0
  39. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/cite.py +0 -0
  40. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/cli/__init__.py +0 -0
  41. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/cli/cache.py +0 -0
  42. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/cli/cite.py +0 -0
  43. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/cli/doctor.py +0 -0
  44. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/cli/inspect.py +0 -0
  45. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/cli/progress.py +0 -0
  46. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/data/nexrad_sites.csv +0 -0
  47. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/data/places.csv +0 -0
  48. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/data/places.sources.json +0 -0
  49. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/doctor.py +0 -0
  50. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/manifest.py +0 -0
  51. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/mirror.py +0 -0
  52. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/protocols/__init__.py +0 -0
  53. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/protocols/erddap.py +0 -0
  54. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/protocols/http.py +0 -0
  55. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/protocols/listing.py +0 -0
  56. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/__init__.py +0 -0
  57. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/census/__init__.py +0 -0
  58. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/census/acs.py +0 -0
  59. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/credentials.py +0 -0
  60. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/epa/__init__.py +0 -0
  61. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/epa/aqs.py +0 -0
  62. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/fema/__init__.py +0 -0
  63. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/fema/declarations.py +0 -0
  64. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/http.py +0 -0
  65. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/noaa/__init__.py +0 -0
  66. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/noaa/coastwatch.py +0 -0
  67. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/noaa/coops.py +0 -0
  68. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/noaa/gfs.py +0 -0
  69. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/noaa/ghcnd.py +0 -0
  70. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/noaa/glm.py +0 -0
  71. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/noaa/gsom.py +0 -0
  72. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/noaa/gsoy.py +0 -0
  73. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/noaa/hurdat2.py +0 -0
  74. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/noaa/ibtracs.py +0 -0
  75. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/noaa/lcd.py +0 -0
  76. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/noaa/mrms.py +0 -0
  77. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/noaa/nbm.py +0 -0
  78. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/noaa/nexrad.py +0 -0
  79. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/noaa/nexrad_level3.py +0 -0
  80. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/noaa/normals.py +0 -0
  81. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/noaa/nws_vtec.py +0 -0
  82. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/noaa/rap.py +0 -0
  83. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/noaa/sites.py +0 -0
  84. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/noaa/spc.py +0 -0
  85. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/noaa/storm_events.py +0 -0
  86. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/params.py +0 -0
  87. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/usgs/__init__.py +0 -0
  88. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/usgs/daily.py +0 -0
  89. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/providers/usgs/earthquakes.py +0 -0
  90. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/pull.py +0 -0
  91. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/py.typed +0 -0
  92. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/query.py +0 -0
  93. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/readers.py +0 -0
  94. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/registry.py +0 -0
  95. {usdata-0.32.0 → usdata-0.34.0}/src/usdata/selection.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: usdata
3
- Version: 0.32.0
3
+ Version: 0.34.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-eight datasets are available today and eighteen more are planned, grouped
116
+ Thirty-four datasets are available today and seventeen 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-eight datasets are available today and eighteen more are planned, grouped
71
+ Thirty-four datasets are available today and seventeen 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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "usdata"
3
- version = "0.32.0"
3
+ version = "0.34.0"
4
4
  description = "Unified Python SDK and CLI for discovering, fetching, and tracking provenance of U.S. public scientific data"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "usdata"
3
- version = "0.32.0"
3
+ version = "0.34.0"
4
4
  description = "Unified Python SDK and CLI for discovering, fetching, and tracking provenance of U.S. public scientific data"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -13,6 +13,7 @@ except PackageNotFoundError: # running from a source tree without an install
13
13
  from usdata._fetch import ChecksumMismatch, FetchedAsset, fetch, fetch_asset
14
14
  from usdata.cite import Citation, cite_dataset, cite_lockfile
15
15
  from usdata.inspect import inspect_asset, inspect_path
16
+ from usdata.messages import MessageListing, list_messages
16
17
  from usdata.models import (
17
18
  Asset,
18
19
  BBox,
@@ -36,6 +37,7 @@ __all__ = [
36
37
  "Dataset",
37
38
  "DatasetNotFound",
38
39
  "FetchedAsset",
40
+ "MessageListing",
39
41
  "Place",
40
42
  "Provenance",
41
43
  "Query",
@@ -55,6 +57,7 @@ __all__ = [
55
57
  "get",
56
58
  "inspect_asset",
57
59
  "inspect_path",
60
+ "list_messages",
58
61
  "pull",
59
62
  "search",
60
63
  "select_by_time",
@@ -96,7 +96,9 @@ def import_eccodes() -> Any:
96
96
  imported later in the same process, both copies free the same memory as the
97
97
  interpreter exits, which aborts it with "double free or corruption" and exit
98
98
  status 134 after the work is done. Loading pyproj's copy first avoids it.
99
- A pyproj that fails to import is left for whoever uses it to report.
99
+ A pyproj that fails to import is left for whoever uses it to report. This is
100
+ a workaround for https://github.com/ecmwf/eckit/issues/354; once an eckitlib
101
+ wheel no longer clashes with pyproj's PROJ, it can go.
100
102
 
101
103
  Raises:
102
104
  ModuleNotFoundError: eccodes is not installed.
@@ -481,8 +483,11 @@ def open_grib2(
481
483
  needs_select = select is None and not fetched.provenance.is_partial
482
484
  object_messages = fetched.provenance.object_messages
483
485
  selectors = fetched.provenance.selectors if object_messages else []
486
+ fields_seen: dict[int, int] = {}
484
487
  for index, h in _messages(eccodes, fetched.path):
485
488
  count += 1
489
+ # Fields of one message share its index; the nth of them is the index's field n+1.
490
+ field = fields_seen[index] = fields_seen.get(index, -1) + 1
486
491
  if count > 1 and needs_select:
487
492
  continue
488
493
  hits = {key: _matched(eccodes, h, key, wanted) for key, wanted in options.items()}
@@ -536,7 +541,7 @@ def open_grib2(
536
541
  "step": attrs.get("step"),
537
542
  }
538
543
  if selectors:
539
- message["selector"] = selectors[index]
544
+ message["selector"] = fetched.provenance.field_selector(index, field)
540
545
  short = base_name(
541
546
  message["shortName"], [attrs.get(key) for key in PARAMETER_KEYS], fetched.asset.id
542
547
  )
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import json
6
6
  from collections.abc import Iterable
7
7
  from pathlib import Path
8
- from typing import Annotated
8
+ from typing import Annotated, Any
9
9
 
10
10
  import httpx
11
11
  import typer
@@ -20,6 +20,7 @@ from usdata.cli.doctor import doctor
20
20
  from usdata.cli.inspect import inspect
21
21
  from usdata.cli.progress import progress
22
22
  from usdata.manifest import lockfile_path
23
+ from usdata.messages import list_messages
23
24
  from usdata.models import READER_EXTRAS_TEXT, Dataset, Status, describe_duration
24
25
  from usdata.providers import adapter_class, load_adapter
25
26
  from usdata.providers.base import NotImplementedProvider
@@ -67,6 +68,33 @@ _QUERY_FLAGS = {
67
68
  }
68
69
 
69
70
 
71
+ def _parse_params(
72
+ param: list[str] | None, command: str, flags: dict[str, str | None]
73
+ ) -> dict[str, Any]:
74
+ """``--param key=value`` pairs as a dict of strings; exit 2 on a malformed or reserved key.
75
+
76
+ ``flags`` maps each reserved query option to the flag ``command`` takes it
77
+ through, or to ``None`` when ``command`` does not take it.
78
+ """
79
+ params: dict[str, Any] = {}
80
+ for item in param or []:
81
+ key, sep, value = item.partition("=")
82
+ if not sep:
83
+ typer.secho(f"--param expects key=value, got {item!r}", err=True, fg="red")
84
+ raise typer.Exit(code=2)
85
+ if key in flags:
86
+ flag = flags[key]
87
+ hint = (
88
+ f"use {flag} instead of --param"
89
+ if flag is not None
90
+ else f"{command} does not support this option"
91
+ )
92
+ typer.secho(f"{key} is a reserved query option; {hint}", err=True, fg="red")
93
+ raise typer.Exit(code=2)
94
+ params[key] = value
95
+ return params
96
+
97
+
70
98
  def _size_phrase(known_bytes: int, unknown: int, *, sources: Iterable[str] = ()) -> str:
71
99
  """How many bytes a dry run would move, said honestly when some size is missing.
72
100
 
@@ -357,26 +385,7 @@ def fetch(
357
385
  ] = False,
358
386
  ) -> None:
359
387
  """Resolve a query against one dataset and download the matching assets."""
360
- params: dict[str, str] = {}
361
- for item in param or []:
362
- key, sep, value = item.partition("=")
363
- if not sep:
364
- typer.secho(f"--param expects key=value, got {item!r}", err=True, fg="red")
365
- raise typer.Exit(code=2)
366
- if key in _QUERY_FLAGS:
367
- flag = _QUERY_FLAGS[key]
368
- hint = (
369
- f"use {flag} instead of --param"
370
- if flag is not None
371
- else "fetch does not support this option"
372
- )
373
- typer.secho(
374
- f"{key} is a reserved query option; {hint}",
375
- err=True,
376
- fg="red",
377
- )
378
- raise typer.Exit(code=2)
379
- params[key] = value
388
+ params = _parse_params(param, "fetch", _QUERY_FLAGS)
380
389
  box = None
381
390
  if bbox:
382
391
  try:
@@ -437,6 +446,64 @@ def fetch(
437
446
  typer.echo(f"{f.path}\t{tag}\t{f.provenance.size} bytes")
438
447
 
439
448
 
449
+ _MESSAGES_FLAGS: dict[str, str | None] = {
450
+ key: flag if key in ("start", "end") else None for key, flag in _QUERY_FLAGS.items()
451
+ }
452
+ """The query options ``messages`` takes: a model run is selected by its window alone."""
453
+
454
+
455
+ @app.command()
456
+ def messages(
457
+ dataset_id: Annotated[str, typer.Argument(help="Dataset id, e.g. noaa:hrrr")],
458
+ start: Annotated[str | None, typer.Option(help="ISO date or datetime.")] = None,
459
+ end: Annotated[
460
+ str | None,
461
+ typer.Option(help="ISO date or datetime; a date alone runs to the end of that UTC day."),
462
+ ] = None,
463
+ param: Annotated[
464
+ list[str] | None,
465
+ typer.Option("--param", "-p", help="Provider-specific key=value, repeatable."),
466
+ ] = None,
467
+ as_json: Annotated[
468
+ bool,
469
+ typer.Option("--json", help="Emit a JSON array of listings and nothing else on stdout."),
470
+ ] = False,
471
+ ) -> None:
472
+ """List the GRIB2 messages each file of a query holds, spelled as the messages param takes them.
473
+
474
+ One tab-separated line per field: asset id, message number, bytes, and the
475
+ selector that names exactly that field. Only the index sidecars are read.
476
+ """
477
+ params = _parse_params(param, "messages", _MESSAGES_FLAGS)
478
+ try:
479
+ ds = default_registry().get(dataset_id)
480
+ listings = list_messages(ds, build_query(start=start, end=end, **params))
481
+ except (DatasetNotFound, ValueError) as e:
482
+ typer.secho(str(e), err=True, fg="red")
483
+ raise typer.Exit(code=2) from None
484
+ except NotImplementedProvider as e:
485
+ typer.secho(str(e), err=True, fg="yellow")
486
+ raise typer.Exit(code=3) from None
487
+ except httpx.HTTPError as e:
488
+ typer.secho(f"request failed: {e}", err=True, fg="red")
489
+ raise typer.Exit(code=4) from None
490
+ if as_json:
491
+ typer.echo(json.dumps([listing.model_dump(mode="json") for listing in listings], indent=2))
492
+ else:
493
+ for listing in listings:
494
+ for entry in listing.messages:
495
+ number = entry.number if entry.field is None else f"{entry.number}.{entry.field}"
496
+ typer.echo(f"{listing.asset.id}\t{number}\t{entry.length}\t{entry.selector}")
497
+ count = sum(len(listing.messages) for listing in listings)
498
+ typer.echo(
499
+ f"{count} message(s) in {len(listings)} file(s); a selector may stop after its "
500
+ "level text, as CAPE:surface, to match every step",
501
+ err=True,
502
+ )
503
+ if not listings:
504
+ raise typer.Exit(code=1)
505
+
506
+
440
507
  def _print_plan(plan: Plan, *, as_json: bool, quiet: bool) -> None:
441
508
  """Report what a pull would fetch: assets, a subtotal per source, a total on stderr."""
442
509
  if as_json:
@@ -574,7 +641,10 @@ def verify(
574
641
  bool, typer.Option("--json", help="With --listing, emit the comparison as JSON on stdout.")
575
642
  ] = False,
576
643
  ) -> None:
577
- """Check cached files against a manifest's lockfile. Exit 1 on any drift."""
644
+ """Check cached files, or with --listing today's source listings, against the lockfile.
645
+
646
+ Exit 1 on any drift.
647
+ """
578
648
  if as_json and not listing:
579
649
  typer.secho("--json applies to --listing only", err=True, fg="red")
580
650
  raise typer.Exit(code=2)
@@ -605,9 +675,6 @@ def _verify_listing(manifest: Path, *, as_json: bool) -> None:
605
675
  """Compare the lockfile with today's listings; exit 1 when a re-resolve would differ."""
606
676
  try:
607
677
  comparison = compare_manifest_listing(manifest)
608
- except EmptySource as e:
609
- typer.secho(str(e), err=True, fg="yellow")
610
- raise typer.Exit(code=1) from None
611
678
  except (DatasetNotFound, UnknownDatasets, ManifestChanged, UnknownPlace, ValueError) as e:
612
679
  typer.secho(str(e), err=True, fg="red")
613
680
  raise typer.Exit(code=2) from None