usdata 0.2.0__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.0 → usdata-0.3.0}/PKG-INFO +32 -8
- {usdata-0.2.0 → usdata-0.3.0}/README.md +31 -7
- {usdata-0.2.0 → usdata-0.3.0}/pyproject.toml +14 -1
- {usdata-0.2.0 → usdata-0.3.0}/pyproject.toml.orig +12 -2
- {usdata-0.2.0 → usdata-0.3.0}/src/usdata/__init__.py +7 -2
- {usdata-0.2.0 → usdata-0.3.0}/src/usdata/cache.py +3 -0
- usdata-0.3.0/src/usdata/cli/__init__.py +5 -0
- {usdata-0.2.0 → usdata-0.3.0}/src/usdata/cli/app.py +59 -13
- usdata-0.3.0/src/usdata/data/registry.yaml +666 -0
- {usdata-0.2.0 → usdata-0.3.0}/src/usdata/fetch.py +4 -0
- {usdata-0.2.0 → usdata-0.3.0}/src/usdata/manifest.py +13 -0
- {usdata-0.2.0 → usdata-0.3.0}/src/usdata/models.py +72 -4
- {usdata-0.2.0 → usdata-0.3.0}/src/usdata/protocols/http.py +2 -0
- {usdata-0.2.0 → usdata-0.3.0}/src/usdata/protocols/s3.py +4 -0
- {usdata-0.2.0 → usdata-0.3.0}/src/usdata/provenance.py +3 -0
- {usdata-0.2.0 → usdata-0.3.0}/src/usdata/providers/base.py +7 -0
- {usdata-0.2.0 → usdata-0.3.0}/src/usdata/providers/noaa/coastwatch.py +4 -0
- {usdata-0.2.0 → usdata-0.3.0}/src/usdata/providers/noaa/ghcnd.py +4 -0
- {usdata-0.2.0 → usdata-0.3.0}/src/usdata/providers/noaa/nexrad.py +5 -0
- {usdata-0.2.0 → usdata-0.3.0}/src/usdata/providers/noaa/sites.py +7 -0
- usdata-0.3.0/src/usdata/pull.py +162 -0
- usdata-0.3.0/src/usdata/py.typed +0 -0
- {usdata-0.2.0 → usdata-0.3.0}/src/usdata/query.py +2 -0
- {usdata-0.2.0 → usdata-0.3.0}/src/usdata/registry.py +67 -5
- usdata-0.2.0/src/usdata/cli/__init__.py +0 -3
- usdata-0.2.0/src/usdata/data/registry.yaml +0 -50
- {usdata-0.2.0 → usdata-0.3.0}/src/usdata/data/nexrad_sites.csv +0 -0
- {usdata-0.2.0 → usdata-0.3.0}/src/usdata/data/places.yaml +0 -0
- {usdata-0.2.0 → usdata-0.3.0}/src/usdata/protocols/__init__.py +0 -0
- {usdata-0.2.0 → usdata-0.3.0}/src/usdata/providers/__init__.py +0 -0
- {usdata-0.2.0 → usdata-0.3.0}/src/usdata/providers/noaa/__init__.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
|
|
@@ -102,11 +123,14 @@ just run search radar
|
|
|
102
123
|
|
|
103
124
|
Integration tests that hit live services run with `just test-integration`.
|
|
104
125
|
|
|
105
|
-
Releases
|
|
106
|
-
|
|
126
|
+
Releases: `just release minor` opens a version-bump PR; merging it publishes
|
|
127
|
+
to PyPI and creates the tag and GitHub release. See
|
|
128
|
+
[docs/versioning.md](docs/versioning.md).
|
|
107
129
|
|
|
108
|
-
See [docs/
|
|
109
|
-
[docs/
|
|
130
|
+
See [docs/providers/](docs/providers/) for per-provider access notes,
|
|
131
|
+
[docs/architecture.md](docs/architecture.md) for how the pieces fit,
|
|
132
|
+
[docs/adr/](docs/adr/) for why, and [CONTRIBUTING.md](CONTRIBUTING.md) to add
|
|
133
|
+
a dataset.
|
|
110
134
|
|
|
111
135
|
## License
|
|
112
136
|
|
|
@@ -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
|
|
@@ -81,11 +102,14 @@ just run search radar
|
|
|
81
102
|
|
|
82
103
|
Integration tests that hit live services run with `just test-integration`.
|
|
83
104
|
|
|
84
|
-
Releases
|
|
85
|
-
|
|
105
|
+
Releases: `just release minor` opens a version-bump PR; merging it publishes
|
|
106
|
+
to PyPI and creates the tag and GitHub release. See
|
|
107
|
+
[docs/versioning.md](docs/versioning.md).
|
|
86
108
|
|
|
87
|
-
See [docs/
|
|
88
|
-
[docs/
|
|
109
|
+
See [docs/providers/](docs/providers/) for per-provider access notes,
|
|
110
|
+
[docs/architecture.md](docs/architecture.md) for how the pieces fit,
|
|
111
|
+
[docs/adr/](docs/adr/) for why, and [CONTRIBUTING.md](CONTRIBUTING.md) to add
|
|
112
|
+
a dataset.
|
|
89
113
|
|
|
90
114
|
## License
|
|
91
115
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "usdata"
|
|
3
|
-
version = "0.
|
|
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"
|
|
@@ -41,6 +41,7 @@ usdata = "usdata.cli:app"
|
|
|
41
41
|
dev = [
|
|
42
42
|
"pyright>=1.1.380",
|
|
43
43
|
"pytest>=8.0",
|
|
44
|
+
"pytest-cov>=7.1.0",
|
|
44
45
|
"respx>=0.23.1",
|
|
45
46
|
"ruff>=0.6",
|
|
46
47
|
]
|
|
@@ -66,7 +67,19 @@ select = [
|
|
|
66
67
|
"B",
|
|
67
68
|
"SIM",
|
|
68
69
|
"RUF",
|
|
70
|
+
"D",
|
|
69
71
|
]
|
|
72
|
+
ignore = [
|
|
73
|
+
"D105",
|
|
74
|
+
"D107",
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
[tool.ruff.lint.pydocstyle]
|
|
78
|
+
convention = "google"
|
|
79
|
+
|
|
80
|
+
[tool.ruff.lint.per-file-ignores]
|
|
81
|
+
"tests/**" = ["D"]
|
|
82
|
+
"scripts/**" = ["D"]
|
|
70
83
|
|
|
71
84
|
[tool.pyright]
|
|
72
85
|
include = [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "usdata"
|
|
3
|
-
version = "0.
|
|
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"
|
|
@@ -33,6 +33,7 @@ usdata = "usdata.cli:app"
|
|
|
33
33
|
dev = [
|
|
34
34
|
"pyright>=1.1.380",
|
|
35
35
|
"pytest>=8.0",
|
|
36
|
+
"pytest-cov>=7.1.0",
|
|
36
37
|
"respx>=0.23.1",
|
|
37
38
|
"ruff>=0.6",
|
|
38
39
|
]
|
|
@@ -47,7 +48,16 @@ target-version = "py311"
|
|
|
47
48
|
src = ["src", "tests"]
|
|
48
49
|
|
|
49
50
|
[tool.ruff.lint]
|
|
50
|
-
select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
|
|
51
|
+
select = ["E", "F", "I", "UP", "B", "SIM", "RUF", "D"]
|
|
52
|
+
# Docstrings are required on the public API only; magic methods and __init__ are exempt.
|
|
53
|
+
ignore = ["D105", "D107"]
|
|
54
|
+
|
|
55
|
+
[tool.ruff.lint.pydocstyle]
|
|
56
|
+
convention = "google"
|
|
57
|
+
|
|
58
|
+
[tool.ruff.lint.per-file-ignores]
|
|
59
|
+
"tests/**" = ["D"]
|
|
60
|
+
"scripts/**" = ["D"]
|
|
51
61
|
|
|
52
62
|
[tool.pyright]
|
|
53
63
|
include = ["src", "tests"]
|
|
@@ -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:
|
|
@@ -12,6 +12,7 @@ ENV_VAR = "USDATA_CACHE_DIR"
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def cache_dir() -> Path:
|
|
15
|
+
"""Cache root: $USDATA_CACHE_DIR, else $XDG_CACHE_HOME/usdata, else ~/.cache/usdata."""
|
|
15
16
|
if override := os.environ.get(ENV_VAR):
|
|
16
17
|
return Path(override).expanduser()
|
|
17
18
|
xdg = os.environ.get("XDG_CACHE_HOME")
|
|
@@ -20,12 +21,14 @@ def cache_dir() -> Path:
|
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def asset_path(asset: Asset, root: Path | None = None) -> Path:
|
|
24
|
+
"""Where an asset lives in the cache: <root>/<provider>/<dataset>/<asset id>."""
|
|
23
25
|
provider, name = asset.dataset_id.split(":", 1)
|
|
24
26
|
safe_id = asset.id.replace("/", "_")
|
|
25
27
|
return (root or cache_dir()) / provider / name / safe_id
|
|
26
28
|
|
|
27
29
|
|
|
28
30
|
def sha256_file(path: Path, chunk_size: int = 1 << 20) -> str:
|
|
31
|
+
"""Hex sha256 of a file, prefixed 'sha256:' to match Asset.checksum."""
|
|
29
32
|
h = hashlib.sha256()
|
|
30
33
|
with path.open("rb") as f:
|
|
31
34
|
while chunk := f.read(chunk_size):
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Typer application: argument parsing and exit codes only; logic lives in the library."""
|
|
2
|
+
|
|
1
3
|
from __future__ import annotations
|
|
2
4
|
|
|
3
5
|
from pathlib import Path
|
|
@@ -7,10 +9,13 @@ import httpx
|
|
|
7
9
|
import typer
|
|
8
10
|
|
|
9
11
|
from usdata import __version__, build_query, default_registry
|
|
10
|
-
from usdata.fetch import fetch_asset
|
|
11
|
-
from usdata.manifest import
|
|
12
|
+
from usdata.fetch import ChecksumMismatch, fetch_asset
|
|
13
|
+
from usdata.manifest import lockfile_path
|
|
12
14
|
from usdata.providers import load_adapter
|
|
13
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
|
|
14
19
|
from usdata.query import UnknownPlace
|
|
15
20
|
from usdata.registry import DatasetNotFound
|
|
16
21
|
|
|
@@ -34,6 +39,7 @@ def main(
|
|
|
34
39
|
typer.Option("--version", callback=_version_callback, is_eager=True, help="Show version."),
|
|
35
40
|
] = None,
|
|
36
41
|
) -> None:
|
|
42
|
+
"""usdata: discover, fetch, and track provenance of U.S. public scientific data."""
|
|
37
43
|
pass
|
|
38
44
|
|
|
39
45
|
|
|
@@ -46,6 +52,9 @@ def search(
|
|
|
46
52
|
state: Annotated[str | None, typer.Option(help="State name or postal code.")] = None,
|
|
47
53
|
start: Annotated[str | None, typer.Option(help="ISO date or datetime.")] = None,
|
|
48
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,
|
|
49
58
|
) -> None:
|
|
50
59
|
"""Search the curated dataset registry."""
|
|
51
60
|
try:
|
|
@@ -53,13 +62,14 @@ def search(
|
|
|
53
62
|
except UnknownPlace as e:
|
|
54
63
|
typer.secho(f"Unknown place: {e}", err=True, fg="red")
|
|
55
64
|
raise typer.Exit(code=2) from None
|
|
56
|
-
results = default_registry().search(query)
|
|
65
|
+
results = default_registry().search(query, include_planned=planned)
|
|
57
66
|
if not results:
|
|
58
67
|
typer.echo("No datasets matched.")
|
|
59
68
|
raise typer.Exit(code=1)
|
|
60
69
|
width = max(len(r.dataset.id) for r in results)
|
|
61
70
|
for r in results:
|
|
62
|
-
|
|
71
|
+
ds = r.dataset
|
|
72
|
+
typer.echo(f"{ds.id:<{width}} {ds.status.value:<9} {ds.version_label:<12} {ds.title}")
|
|
63
73
|
|
|
64
74
|
|
|
65
75
|
@app.command()
|
|
@@ -74,6 +84,8 @@ def info(
|
|
|
74
84
|
raise typer.Exit(code=2) from None
|
|
75
85
|
typer.echo(f"{ds.id}\n {ds.title}\n")
|
|
76
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}")
|
|
77
89
|
typer.echo(f" provider: {ds.provider}")
|
|
78
90
|
typer.echo(f" protocol: {ds.protocol.value}")
|
|
79
91
|
typer.echo(f" license: {ds.license or 'unknown'}")
|
|
@@ -166,13 +178,47 @@ def fetch(
|
|
|
166
178
|
|
|
167
179
|
|
|
168
180
|
@app.command()
|
|
169
|
-
def pull(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
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")
|
|
175
217
|
raise typer.Exit(code=2)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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)
|