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.
- {usdata-0.2.1 → usdata-0.3.0}/PKG-INFO +27 -5
- {usdata-0.2.1 → usdata-0.3.0}/README.md +26 -4
- {usdata-0.2.1 → usdata-0.3.0}/pyproject.toml +1 -1
- {usdata-0.2.1 → usdata-0.3.0}/pyproject.toml.orig +1 -1
- {usdata-0.2.1 → usdata-0.3.0}/src/usdata/__init__.py +7 -2
- {usdata-0.2.1 → usdata-0.3.0}/src/usdata/cli/app.py +56 -13
- usdata-0.3.0/src/usdata/data/registry.yaml +666 -0
- {usdata-0.2.1 → usdata-0.3.0}/src/usdata/manifest.py +2 -0
- {usdata-0.2.1 → usdata-0.3.0}/src/usdata/models.py +67 -4
- {usdata-0.2.1 → usdata-0.3.0}/src/usdata/providers/base.py +4 -0
- usdata-0.3.0/src/usdata/pull.py +162 -0
- {usdata-0.2.1 → usdata-0.3.0}/src/usdata/registry.py +58 -7
- usdata-0.2.1/src/usdata/data/registry.yaml +0 -50
- {usdata-0.2.1 → usdata-0.3.0}/src/usdata/cache.py +0 -0
- {usdata-0.2.1 → usdata-0.3.0}/src/usdata/cli/__init__.py +0 -0
- {usdata-0.2.1 → usdata-0.3.0}/src/usdata/data/nexrad_sites.csv +0 -0
- {usdata-0.2.1 → usdata-0.3.0}/src/usdata/data/places.yaml +0 -0
- {usdata-0.2.1 → usdata-0.3.0}/src/usdata/fetch.py +0 -0
- {usdata-0.2.1 → usdata-0.3.0}/src/usdata/protocols/__init__.py +0 -0
- {usdata-0.2.1 → usdata-0.3.0}/src/usdata/protocols/http.py +0 -0
- {usdata-0.2.1 → usdata-0.3.0}/src/usdata/protocols/s3.py +0 -0
- {usdata-0.2.1 → usdata-0.3.0}/src/usdata/provenance.py +0 -0
- {usdata-0.2.1 → usdata-0.3.0}/src/usdata/providers/__init__.py +0 -0
- {usdata-0.2.1 → usdata-0.3.0}/src/usdata/providers/noaa/__init__.py +0 -0
- {usdata-0.2.1 → usdata-0.3.0}/src/usdata/providers/noaa/coastwatch.py +0 -0
- {usdata-0.2.1 → usdata-0.3.0}/src/usdata/providers/noaa/ghcnd.py +0 -0
- {usdata-0.2.1 → usdata-0.3.0}/src/usdata/providers/noaa/nexrad.py +0 -0
- {usdata-0.2.1 → usdata-0.3.0}/src/usdata/providers/noaa/sites.py +0 -0
- {usdata-0.2.1 → usdata-0.3.0}/src/usdata/py.typed +0 -0
- {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.
|
|
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
|
|
76
|
-
|
|
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/
|
|
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
|
|
55
|
-
|
|
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/
|
|
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
|
|
|
@@ -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(
|
|
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
|
|
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
|
-
|
|
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(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
typer.
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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)
|