usdata 0.29.0__tar.gz → 0.30.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 (82) hide show
  1. {usdata-0.29.0 → usdata-0.30.0}/PKG-INFO +1 -1
  2. {usdata-0.29.0 → usdata-0.30.0}/pyproject.toml +1 -1
  3. {usdata-0.29.0 → usdata-0.30.0}/pyproject.toml.orig +1 -1
  4. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/_fetch.py +13 -0
  5. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/_grib.py +12 -7
  6. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/cache.py +14 -5
  7. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/data/places.csv +3300 -3292
  8. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/data/places.sources.json +17 -1
  9. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/manifest.py +79 -8
  10. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/models.py +20 -4
  11. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/provenance.py +1 -3
  12. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/base.py +22 -0
  13. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/epa/aqs.py +4 -1
  14. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/fema/declarations.py +6 -0
  15. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/noaa/coastwatch.py +5 -5
  16. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/noaa/nws_vtec.py +4 -1
  17. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/pull.py +40 -11
  18. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/query.py +36 -5
  19. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/readers.py +6 -6
  20. {usdata-0.29.0 → usdata-0.30.0}/README.md +0 -0
  21. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/__init__.py +0 -0
  22. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/__main__.py +0 -0
  23. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/_aqs.py +0 -0
  24. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/_files.py +0 -0
  25. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/_hurdat2.py +0 -0
  26. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/_netcdf.py +0 -0
  27. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/_progress.py +0 -0
  28. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/_radar.py +0 -0
  29. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/cache_ops.py +0 -0
  30. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/cite.py +0 -0
  31. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/cli/__init__.py +0 -0
  32. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/cli/app.py +0 -0
  33. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/cli/cache.py +0 -0
  34. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/cli/cite.py +0 -0
  35. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/cli/doctor.py +0 -0
  36. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/cli/inspect.py +0 -0
  37. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/cli/progress.py +0 -0
  38. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/data/nexrad_sites.csv +0 -0
  39. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/data/registry.yaml +0 -0
  40. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/doctor.py +0 -0
  41. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/inspect.py +0 -0
  42. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/mirror.py +0 -0
  43. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/protocols/__init__.py +0 -0
  44. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/protocols/erddap.py +0 -0
  45. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/protocols/http.py +0 -0
  46. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/protocols/listing.py +0 -0
  47. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/protocols/s3.py +0 -0
  48. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/__init__.py +0 -0
  49. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/credentials.py +0 -0
  50. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/epa/__init__.py +0 -0
  51. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/fema/__init__.py +0 -0
  52. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/http.py +0 -0
  53. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/noaa/__init__.py +0 -0
  54. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/noaa/coops.py +0 -0
  55. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/noaa/gfs.py +0 -0
  56. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/noaa/ghcnd.py +0 -0
  57. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/noaa/glm.py +0 -0
  58. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/noaa/goes.py +0 -0
  59. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/noaa/grib_index.py +0 -0
  60. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/noaa/gsom.py +0 -0
  61. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/noaa/gsoy.py +0 -0
  62. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/noaa/hrrr.py +0 -0
  63. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/noaa/hurdat2.py +0 -0
  64. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/noaa/ibtracs.py +0 -0
  65. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/noaa/lcd.py +0 -0
  66. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/noaa/mrms.py +0 -0
  67. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/noaa/nbm.py +0 -0
  68. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/noaa/nexrad.py +0 -0
  69. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/noaa/nexrad_level3.py +0 -0
  70. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/noaa/normals.py +0 -0
  71. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/noaa/rap.py +0 -0
  72. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/noaa/sites.py +0 -0
  73. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/noaa/spc.py +0 -0
  74. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/noaa/storm_events.py +0 -0
  75. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/params.py +0 -0
  76. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/usgs/__init__.py +0 -0
  77. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/usgs/daily.py +0 -0
  78. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/providers/usgs/earthquakes.py +0 -0
  79. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/py.typed +0 -0
  80. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/registry.py +0 -0
  81. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/selection.py +0 -0
  82. {usdata-0.29.0 → usdata-0.30.0}/src/usdata/testing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: usdata
3
- Version: 0.29.0
3
+ Version: 0.30.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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "usdata"
3
- version = "0.29.0"
3
+ version = "0.30.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.29.0"
3
+ version = "0.30.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"
@@ -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
- return (
312
- datetime.strptime(f"{int(date):08d}{int(time):04d}", "%Y%m%d%H%M")
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, or the path would escape the cache root.
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
- path = base / provider / name / safe_id
53
- for candidate in (path, path.with_name(path.name + ".provenance.json")):
54
- if not candidate.resolve().is_relative_to(base):
55
- raise ValueError(f"cache path escapes root: {candidate}")
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