usdata 0.2.1__tar.gz → 0.3.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 (30) hide show
  1. {usdata-0.2.1 → usdata-0.3.0}/PKG-INFO +27 -5
  2. {usdata-0.2.1 → usdata-0.3.0}/README.md +26 -4
  3. {usdata-0.2.1 → usdata-0.3.0}/pyproject.toml +1 -1
  4. {usdata-0.2.1 → usdata-0.3.0}/pyproject.toml.orig +1 -1
  5. {usdata-0.2.1 → usdata-0.3.0}/src/usdata/__init__.py +7 -2
  6. {usdata-0.2.1 → usdata-0.3.0}/src/usdata/cli/app.py +56 -13
  7. usdata-0.3.0/src/usdata/data/registry.yaml +666 -0
  8. {usdata-0.2.1 → usdata-0.3.0}/src/usdata/manifest.py +2 -0
  9. {usdata-0.2.1 → usdata-0.3.0}/src/usdata/models.py +67 -4
  10. {usdata-0.2.1 → usdata-0.3.0}/src/usdata/providers/base.py +4 -0
  11. usdata-0.3.0/src/usdata/pull.py +162 -0
  12. {usdata-0.2.1 → usdata-0.3.0}/src/usdata/registry.py +58 -7
  13. usdata-0.2.1/src/usdata/data/registry.yaml +0 -50
  14. {usdata-0.2.1 → usdata-0.3.0}/src/usdata/cache.py +0 -0
  15. {usdata-0.2.1 → usdata-0.3.0}/src/usdata/cli/__init__.py +0 -0
  16. {usdata-0.2.1 → usdata-0.3.0}/src/usdata/data/nexrad_sites.csv +0 -0
  17. {usdata-0.2.1 → usdata-0.3.0}/src/usdata/data/places.yaml +0 -0
  18. {usdata-0.2.1 → usdata-0.3.0}/src/usdata/fetch.py +0 -0
  19. {usdata-0.2.1 → usdata-0.3.0}/src/usdata/protocols/__init__.py +0 -0
  20. {usdata-0.2.1 → usdata-0.3.0}/src/usdata/protocols/http.py +0 -0
  21. {usdata-0.2.1 → usdata-0.3.0}/src/usdata/protocols/s3.py +0 -0
  22. {usdata-0.2.1 → usdata-0.3.0}/src/usdata/provenance.py +0 -0
  23. {usdata-0.2.1 → usdata-0.3.0}/src/usdata/providers/__init__.py +0 -0
  24. {usdata-0.2.1 → usdata-0.3.0}/src/usdata/providers/noaa/__init__.py +0 -0
  25. {usdata-0.2.1 → usdata-0.3.0}/src/usdata/providers/noaa/coastwatch.py +0 -0
  26. {usdata-0.2.1 → usdata-0.3.0}/src/usdata/providers/noaa/ghcnd.py +0 -0
  27. {usdata-0.2.1 → usdata-0.3.0}/src/usdata/providers/noaa/nexrad.py +0 -0
  28. {usdata-0.2.1 → usdata-0.3.0}/src/usdata/providers/noaa/sites.py +0 -0
  29. {usdata-0.2.1 → usdata-0.3.0}/src/usdata/py.typed +0 -0
  30. {usdata-0.2.1 → usdata-0.3.0}/src/usdata/query.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: usdata
3
- Version: 0.2.1
3
+ Version: 0.3.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,nasa,open-data,scientific-data,provenance
6
6
  Author: Jake Van Slyke
@@ -28,6 +28,22 @@ provenance of U.S. public scientific data (NOAA, USGS, NASA, and more).
28
28
  > data with provenance; other registry entries are stubs.
29
29
  > See [docs/roadmap.md](docs/roadmap.md).
30
30
 
31
+ ## Providers
32
+
33
+ <!-- registry:start -->
34
+ | Provider | Available | Stub | Planned | Next up (0.3) | Datasets |
35
+ |---|---:|---:|---:|---|---|
36
+ | [NOAA](docs/providers/noaa.md) | 2 | 1 | 26 | `coastwatch-sst` | `ghcn-daily`, `nexrad-level2`, _coastwatch-sst_, +26 planned |
37
+ | [Census Bureau](docs/providers/census.md) | 0 | 0 | 1 | — | +1 planned |
38
+ | [EPA](docs/providers/epa.md) | 0 | 0 | 1 | — | +1 planned |
39
+ | [FEMA](docs/providers/fema.md) | 0 | 0 | 1 | — | +1 planned |
40
+ | [NASA](docs/providers/nasa.md) | 0 | 0 | 1 | — | +1 planned |
41
+ | [USDA](docs/providers/usda.md) | 0 | 0 | 1 | — | +1 planned |
42
+ | [USGS](docs/providers/usgs.md) | 0 | 0 | 3 | — | +3 planned |
43
+
44
+ Available datasets are in `code`, stubs in _italics_; planned ones are counted. Each provider page has access notes and full dataset details; [docs/roadmap.md](docs/roadmap.md) lists datasets by target version.
45
+ <!-- registry:end -->
46
+
31
47
  ## Install
32
48
 
33
49
  ```sh
@@ -65,15 +81,20 @@ usdata fetch noaa:ghcn-daily -p stations=USW00013967 --start 2024-01-01 --end 20
65
81
  usdata fetch noaa:nexrad-level2 --lat 35.47 --lon -97.52 \
66
82
  --start 2024-05-06T20:00 --end 2024-05-06T23:00 # nearest radar (KTLX)
67
83
  usdata fetch noaa:nexrad-level2 -p site=KTLX --start 2024-05-06T20:00 --end 2024-05-06T20:30 --dry-run
68
- usdata pull dataset.yaml
84
+ usdata pull dataset.yaml # resolve, fetch, write dataset.lock.json
85
+ usdata verify dataset.yaml # exit 1 if any cached input drifted
69
86
  ```
70
87
 
71
88
  Fetched files land in `~/.cache/usdata/<provider>/<dataset>/` (override with
72
89
  `USDATA_CACHE_DIR` or `--cache-dir`), each with a `.provenance.json` sidecar
73
90
  recording source URL, retrieval time, checksum, size, and license.
74
91
 
75
- A manifest declares every input a project needs; `pull` fetches them and writes
76
- a lockfile with checksums and provenance so the inputs can be reproduced:
92
+ A manifest declares every input a project needs. `pull` resolves each source,
93
+ fetches it, and writes `dataset.lock.json` pinning every asset with its checksum
94
+ and provenance. A second `pull` restores exactly what the lockfile pins without
95
+ re-querying upstream, so the inputs stay reproducible even if the source
96
+ changes. `verify` re-hashes the cached files against the lockfile. Editing the
97
+ manifest after locking requires `pull --force` to re-resolve.
77
98
 
78
99
  ```yaml
79
100
  name: tornado-environment
@@ -106,7 +127,8 @@ Releases: `just release minor` opens a version-bump PR; merging it publishes
106
127
  to PyPI and creates the tag and GitHub release. See
107
128
  [docs/versioning.md](docs/versioning.md).
108
129
 
109
- See [docs/architecture.md](docs/architecture.md) for how the pieces fit,
130
+ See [docs/providers/](docs/providers/) for per-provider access notes,
131
+ [docs/architecture.md](docs/architecture.md) for how the pieces fit,
110
132
  [docs/adr/](docs/adr/) for why, and [CONTRIBUTING.md](CONTRIBUTING.md) to add
111
133
  a dataset.
112
134
 
@@ -7,6 +7,22 @@ provenance of U.S. public scientific data (NOAA, USGS, NASA, and more).
7
7
  > data with provenance; other registry entries are stubs.
8
8
  > See [docs/roadmap.md](docs/roadmap.md).
9
9
 
10
+ ## Providers
11
+
12
+ <!-- registry:start -->
13
+ | Provider | Available | Stub | Planned | Next up (0.3) | Datasets |
14
+ |---|---:|---:|---:|---|---|
15
+ | [NOAA](docs/providers/noaa.md) | 2 | 1 | 26 | `coastwatch-sst` | `ghcn-daily`, `nexrad-level2`, _coastwatch-sst_, +26 planned |
16
+ | [Census Bureau](docs/providers/census.md) | 0 | 0 | 1 | — | +1 planned |
17
+ | [EPA](docs/providers/epa.md) | 0 | 0 | 1 | — | +1 planned |
18
+ | [FEMA](docs/providers/fema.md) | 0 | 0 | 1 | — | +1 planned |
19
+ | [NASA](docs/providers/nasa.md) | 0 | 0 | 1 | — | +1 planned |
20
+ | [USDA](docs/providers/usda.md) | 0 | 0 | 1 | — | +1 planned |
21
+ | [USGS](docs/providers/usgs.md) | 0 | 0 | 3 | — | +3 planned |
22
+
23
+ Available datasets are in `code`, stubs in _italics_; planned ones are counted. Each provider page has access notes and full dataset details; [docs/roadmap.md](docs/roadmap.md) lists datasets by target version.
24
+ <!-- registry:end -->
25
+
10
26
  ## Install
11
27
 
12
28
  ```sh
@@ -44,15 +60,20 @@ usdata fetch noaa:ghcn-daily -p stations=USW00013967 --start 2024-01-01 --end 20
44
60
  usdata fetch noaa:nexrad-level2 --lat 35.47 --lon -97.52 \
45
61
  --start 2024-05-06T20:00 --end 2024-05-06T23:00 # nearest radar (KTLX)
46
62
  usdata fetch noaa:nexrad-level2 -p site=KTLX --start 2024-05-06T20:00 --end 2024-05-06T20:30 --dry-run
47
- usdata pull dataset.yaml
63
+ usdata pull dataset.yaml # resolve, fetch, write dataset.lock.json
64
+ usdata verify dataset.yaml # exit 1 if any cached input drifted
48
65
  ```
49
66
 
50
67
  Fetched files land in `~/.cache/usdata/<provider>/<dataset>/` (override with
51
68
  `USDATA_CACHE_DIR` or `--cache-dir`), each with a `.provenance.json` sidecar
52
69
  recording source URL, retrieval time, checksum, size, and license.
53
70
 
54
- A manifest declares every input a project needs; `pull` fetches them and writes
55
- a lockfile with checksums and provenance so the inputs can be reproduced:
71
+ A manifest declares every input a project needs. `pull` resolves each source,
72
+ fetches it, and writes `dataset.lock.json` pinning every asset with its checksum
73
+ and provenance. A second `pull` restores exactly what the lockfile pins without
74
+ re-querying upstream, so the inputs stay reproducible even if the source
75
+ changes. `verify` re-hashes the cached files against the lockfile. Editing the
76
+ manifest after locking requires `pull --force` to re-resolve.
56
77
 
57
78
  ```yaml
58
79
  name: tornado-environment
@@ -85,7 +106,8 @@ Releases: `just release minor` opens a version-bump PR; merging it publishes
85
106
  to PyPI and creates the tag and GitHub release. See
86
107
  [docs/versioning.md](docs/versioning.md).
87
108
 
88
- See [docs/architecture.md](docs/architecture.md) for how the pieces fit,
109
+ See [docs/providers/](docs/providers/) for per-provider access notes,
110
+ [docs/architecture.md](docs/architecture.md) for how the pieces fit,
89
111
  [docs/adr/](docs/adr/) for why, and [CONTRIBUTING.md](CONTRIBUTING.md) to add
90
112
  a dataset.
91
113
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "usdata"
3
- version = "0.2.1"
3
+ version = "0.3.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.2.1"
3
+ version = "0.3.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"
@@ -11,6 +11,7 @@ except PackageNotFoundError: # running from a source tree without an install
11
11
  __version__ = "0.0.0"
12
12
 
13
13
  from usdata.models import Asset, BBox, Dataset, Provenance, Query, TimeRange
14
+ from usdata.pull import pull, verify
14
15
  from usdata.query import build_query
15
16
  from usdata.registry import DatasetNotFound, Registry, SearchResult, default_registry
16
17
 
@@ -28,13 +29,17 @@ __all__ = [
28
29
  "build_query",
29
30
  "default_registry",
30
31
  "get",
32
+ "pull",
31
33
  "search",
34
+ "verify",
32
35
  ]
33
36
 
34
37
 
35
- def search(text: str | None = None, **kwargs: Any) -> list[SearchResult]:
38
+ def search(
39
+ text: str | None = None, *, include_planned: bool = False, **kwargs: Any
40
+ ) -> list[SearchResult]:
36
41
  """Search the curated registry. Keyword arguments match ``build_query``."""
37
- return default_registry().search(build_query(text, **kwargs))
42
+ return default_registry().search(build_query(text, **kwargs), include_planned=include_planned)
38
43
 
39
44
 
40
45
  def get(dataset_id: str) -> Dataset:
@@ -9,10 +9,13 @@ import httpx
9
9
  import typer
10
10
 
11
11
  from usdata import __version__, build_query, default_registry
12
- from usdata.fetch import fetch_asset
13
- from usdata.manifest import Manifest
12
+ from usdata.fetch import ChecksumMismatch, fetch_asset
13
+ from usdata.manifest import lockfile_path
14
14
  from usdata.providers import load_adapter
15
15
  from usdata.providers.base import NotImplementedProvider
16
+ from usdata.pull import ManifestChanged, UnknownDatasets
17
+ from usdata.pull import pull as pull_manifest
18
+ from usdata.pull import verify as verify_manifest
16
19
  from usdata.query import UnknownPlace
17
20
  from usdata.registry import DatasetNotFound
18
21
 
@@ -49,6 +52,9 @@ def search(
49
52
  state: Annotated[str | None, typer.Option(help="State name or postal code.")] = None,
50
53
  start: Annotated[str | None, typer.Option(help="ISO date or datetime.")] = None,
51
54
  end: Annotated[str | None, typer.Option(help="ISO date or datetime.")] = None,
55
+ planned: Annotated[
56
+ bool, typer.Option("--planned", help="Include planned datasets that have no adapter yet.")
57
+ ] = False,
52
58
  ) -> None:
53
59
  """Search the curated dataset registry."""
54
60
  try:
@@ -56,13 +62,14 @@ def search(
56
62
  except UnknownPlace as e:
57
63
  typer.secho(f"Unknown place: {e}", err=True, fg="red")
58
64
  raise typer.Exit(code=2) from None
59
- results = default_registry().search(query)
65
+ results = default_registry().search(query, include_planned=planned)
60
66
  if not results:
61
67
  typer.echo("No datasets matched.")
62
68
  raise typer.Exit(code=1)
63
69
  width = max(len(r.dataset.id) for r in results)
64
70
  for r in results:
65
- typer.echo(f"{r.dataset.id:<{width}} {r.dataset.title}")
71
+ ds = r.dataset
72
+ typer.echo(f"{ds.id:<{width}} {ds.status.value:<9} {ds.version_label:<12} {ds.title}")
66
73
 
67
74
 
68
75
  @app.command()
@@ -77,6 +84,8 @@ def info(
77
84
  raise typer.Exit(code=2) from None
78
85
  typer.echo(f"{ds.id}\n {ds.title}\n")
79
86
  typer.echo(f" {ds.description.strip()}\n")
87
+ typer.echo(f" status: {ds.status.value} ({ds.version_label})")
88
+ typer.echo(f" domain: {ds.domain}")
80
89
  typer.echo(f" provider: {ds.provider}")
81
90
  typer.echo(f" protocol: {ds.protocol.value}")
82
91
  typer.echo(f" license: {ds.license or 'unknown'}")
@@ -169,13 +178,47 @@ def fetch(
169
178
 
170
179
 
171
180
  @app.command()
172
- def pull(manifest: Annotated[Path, typer.Argument(exists=True, dir_okay=False)]) -> None:
173
- """Fetch every source in a manifest and write a lockfile."""
174
- m = Manifest.load(manifest)
175
- missing = m.validate_against()
176
- if missing:
177
- typer.secho(f"Unknown datasets in manifest: {', '.join(missing)}", err=True, fg="red")
181
+ def pull(
182
+ manifest: Annotated[Path, typer.Argument(exists=True, dir_okay=False)],
183
+ cache_dir: Annotated[Path | None, typer.Option(help="Override the cache directory.")] = None,
184
+ force: Annotated[
185
+ bool,
186
+ typer.Option(help="Ignore an existing lockfile: re-resolve every source and rewrite it."),
187
+ ] = False,
188
+ ) -> None:
189
+ """Fetch every source in a manifest and write (or restore from) its lockfile."""
190
+ try:
191
+ result = pull_manifest(manifest, root=cache_dir, force=force)
192
+ except (UnknownDatasets, ManifestChanged, UnknownPlace, ValueError) as e:
193
+ typer.secho(str(e), err=True, fg="red")
194
+ raise typer.Exit(code=2) from None
195
+ except NotImplementedProvider as e:
196
+ typer.secho(str(e), err=True, fg="yellow")
197
+ raise typer.Exit(code=3) from None
198
+ except (httpx.HTTPError, ChecksumMismatch) as e:
199
+ typer.secho(f"fetch failed: {e}", err=True, fg="red")
200
+ raise typer.Exit(code=4) from None
201
+ for f in result.fetched:
202
+ tag = "cached" if f.from_cache else "fetched"
203
+ typer.echo(f"{f.path}\t{tag}\t{f.provenance.size} bytes")
204
+ mode = "restored from" if result.from_lockfile else "wrote"
205
+ typer.echo(f"{len(result.fetched)} asset(s); {mode} {result.lockfile_path}", err=True)
206
+
207
+
208
+ @app.command()
209
+ def verify(
210
+ manifest: Annotated[Path, typer.Argument(exists=True, dir_okay=False)],
211
+ cache_dir: Annotated[Path | None, typer.Option(help="Override the cache directory.")] = None,
212
+ ) -> None:
213
+ """Check cached files against a manifest's lockfile. Exit 1 on any drift."""
214
+ lock = lockfile_path(manifest)
215
+ if not lock.exists():
216
+ typer.secho(f"no lockfile at {lock}; run pull first", err=True, fg="red")
178
217
  raise typer.Exit(code=2)
179
- typer.echo(f"{m.name} v{m.version}: {len(m.sources)} source(s) validated")
180
- typer.secho("pull is not implemented yet; no data was fetched.", err=True, fg="yellow")
181
- raise typer.Exit(code=3)
218
+ drift = verify_manifest(manifest, root=cache_dir)
219
+ for d in drift:
220
+ typer.echo(f"{d.asset_id}\t{d.problem}\t{d.path}")
221
+ if drift:
222
+ typer.secho(f"{len(drift)} asset(s) drifted from {lock.name}", err=True, fg="red")
223
+ raise typer.Exit(code=1)
224
+ typer.echo(f"all assets match {lock.name}", err=True)