basinkit 0.1.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 (94) hide show
  1. basinkit-0.1.0/.github/workflows/ci.yml +27 -0
  2. basinkit-0.1.0/.github/workflows/docs.yml +15 -0
  3. basinkit-0.1.0/.github/workflows/network.yml +30 -0
  4. basinkit-0.1.0/.github/workflows/release.yml +18 -0
  5. basinkit-0.1.0/.gitignore +11 -0
  6. basinkit-0.1.0/CHANGELOG.md +165 -0
  7. basinkit-0.1.0/CITATION.cff +34 -0
  8. basinkit-0.1.0/CONTRIBUTING.md +47 -0
  9. basinkit-0.1.0/LICENSE +33 -0
  10. basinkit-0.1.0/PKG-INFO +350 -0
  11. basinkit-0.1.0/PUBLISHING.md +175 -0
  12. basinkit-0.1.0/README.md +284 -0
  13. basinkit-0.1.0/RELEASE_NOTES.md +43 -0
  14. basinkit-0.1.0/assets/README.md +78 -0
  15. basinkit-0.1.0/assets/icon.png +0 -0
  16. basinkit-0.1.0/assets/logo-128.png +0 -0
  17. basinkit-0.1.0/assets/logo-256.png +0 -0
  18. basinkit-0.1.0/assets/logo-32.png +0 -0
  19. basinkit-0.1.0/assets/logo-512.png +0 -0
  20. basinkit-0.1.0/assets/logo-64.png +0 -0
  21. basinkit-0.1.0/assets/logo-animated.svg +64 -0
  22. basinkit-0.1.0/assets/logo-wordmark-dark.svg +75 -0
  23. basinkit-0.1.0/assets/logo-wordmark-light.svg +75 -0
  24. basinkit-0.1.0/assets/logo-wordmark.svg +78 -0
  25. basinkit-0.1.0/assets/logo.svg +36 -0
  26. basinkit-0.1.0/basinkit/__init__.py +43 -0
  27. basinkit-0.1.0/basinkit/basin.py +474 -0
  28. basinkit-0.1.0/basinkit/cache.py +185 -0
  29. basinkit-0.1.0/basinkit/catalog.py +416 -0
  30. basinkit-0.1.0/basinkit/cli.py +153 -0
  31. basinkit-0.1.0/basinkit/clip.py +201 -0
  32. basinkit-0.1.0/basinkit/delineate/__init__.py +103 -0
  33. basinkit-0.1.0/basinkit/delineate/api.py +51 -0
  34. basinkit-0.1.0/basinkit/delineate/dem.py +184 -0
  35. basinkit-0.1.0/basinkit/delineate/hydrobasins.py +346 -0
  36. basinkit-0.1.0/basinkit/exceptions.py +48 -0
  37. basinkit-0.1.0/basinkit/mosaic.py +125 -0
  38. basinkit-0.1.0/basinkit/sources/__init__.py +28 -0
  39. basinkit-0.1.0/basinkit/sources/attributes.py +196 -0
  40. basinkit-0.1.0/basinkit/sources/climate.py +358 -0
  41. basinkit-0.1.0/basinkit/sources/dem.py +169 -0
  42. basinkit-0.1.0/basinkit/sources/landcover.py +127 -0
  43. basinkit-0.1.0/basinkit/sources/soil.py +153 -0
  44. basinkit-0.1.0/basinkit/sources/stac.py +354 -0
  45. basinkit-0.1.0/basinkit/sources/vectors.py +81 -0
  46. basinkit-0.1.0/basinkit/sources/water.py +88 -0
  47. basinkit-0.1.0/basinkit/viz.py +152 -0
  48. basinkit-0.1.0/docs/api.md +11 -0
  49. basinkit-0.1.0/docs/assets/logo-32.png +0 -0
  50. basinkit-0.1.0/docs/assets/logo-animated.svg +64 -0
  51. basinkit-0.1.0/docs/assets/logo-wordmark-dark.svg +71 -0
  52. basinkit-0.1.0/docs/assets/logo-wordmark-light.svg +71 -0
  53. basinkit-0.1.0/docs/assets/logo.svg +36 -0
  54. basinkit-0.1.0/docs/catalogue.md +423 -0
  55. basinkit-0.1.0/docs/delineation.md +83 -0
  56. basinkit-0.1.0/docs/index.md +268 -0
  57. basinkit-0.1.0/docs/notebooks/01_quickstart.ipynb +327 -0
  58. basinkit-0.1.0/docs/related-work.md +144 -0
  59. basinkit-0.1.0/docs/tutorial.md +330 -0
  60. basinkit-0.1.0/docs/verification.md +215 -0
  61. basinkit-0.1.0/docs/why.md +83 -0
  62. basinkit-0.1.0/mkdocs.yml +55 -0
  63. basinkit-0.1.0/notebooks/01_quickstart.ipynb +327 -0
  64. basinkit-0.1.0/pyproject.toml +70 -0
  65. basinkit-0.1.0/qgis_plugin/LICENSE +33 -0
  66. basinkit-0.1.0/qgis_plugin/README.md +98 -0
  67. basinkit-0.1.0/qgis_plugin/__init__.py +11 -0
  68. basinkit-0.1.0/qgis_plugin/compat.py +63 -0
  69. basinkit-0.1.0/qgis_plugin/deps.py +89 -0
  70. basinkit-0.1.0/qgis_plugin/icon.png +0 -0
  71. basinkit-0.1.0/qgis_plugin/metadata.txt +31 -0
  72. basinkit-0.1.0/qgis_plugin/plugin.py +66 -0
  73. basinkit-0.1.0/qgis_plugin/processing_provider/__init__.py +0 -0
  74. basinkit-0.1.0/qgis_plugin/processing_provider/algorithms/__init__.py +0 -0
  75. basinkit-0.1.0/qgis_plugin/processing_provider/algorithms/base.py +70 -0
  76. basinkit-0.1.0/qgis_plugin/processing_provider/algorithms/delineate.py +227 -0
  77. basinkit-0.1.0/qgis_plugin/processing_provider/algorithms/fetch_layers.py +248 -0
  78. basinkit-0.1.0/qgis_plugin/processing_provider/algorithms/statistics.py +187 -0
  79. basinkit-0.1.0/qgis_plugin/processing_provider/provider.py +37 -0
  80. basinkit-0.1.0/tests/conftest.py +65 -0
  81. basinkit-0.1.0/tests/test_basinkit.py +782 -0
  82. basinkit-0.1.0/tests/test_qgis_plugin.py +121 -0
  83. basinkit-0.1.0/verify/README.md +34 -0
  84. basinkit-0.1.0/verify/basins.py +28 -0
  85. basinkit-0.1.0/verify/qgis_stub/qgis/PyQt/QtCore.py +10 -0
  86. basinkit-0.1.0/verify/qgis_stub/qgis/PyQt/QtGui.py +3 -0
  87. basinkit-0.1.0/verify/qgis_stub/qgis/PyQt/__init__.py +0 -0
  88. basinkit-0.1.0/verify/qgis_stub/qgis/__init__.py +0 -0
  89. basinkit-0.1.0/verify/qgis_stub/qgis/core.py +230 -0
  90. basinkit-0.1.0/verify/run_delineation.py +45 -0
  91. basinkit-0.1.0/verify/run_qgis_algorithms.py +137 -0
  92. basinkit-0.1.0/verify/run_qgis_wiring.py +80 -0
  93. basinkit-0.1.0/verify/run_rest.py +73 -0
  94. basinkit-0.1.0/verify/run_sources.py +63 -0
@@ -0,0 +1,27 @@
1
+ name: CI
2
+
3
+ on:
4
+ push: { branches: [main] }
5
+ pull_request: { branches: [main] }
6
+
7
+ # What this workflow answers: does basinkit's own code work, on every platform
8
+ # and Python we claim to support? Nothing here touches the network, so a red
9
+ # badge always means a defect in this repository and never that a data server
10
+ # was having a bad afternoon. The live-source checks live in network.yml.
11
+ jobs:
12
+ test:
13
+ runs-on: ${{ matrix.os }}
14
+ strategy:
15
+ fail-fast: false
16
+ matrix:
17
+ os: [ubuntu-latest, macos-latest, windows-latest]
18
+ python: ["3.10", "3.11", "3.12", "3.13"]
19
+ steps:
20
+ - uses: actions/checkout@v5
21
+ - uses: actions/setup-python@v6
22
+ with:
23
+ python-version: ${{ matrix.python }}
24
+ - run: pip install -e ".[all,dev]"
25
+ - run: ruff check basinkit tests
26
+ - name: Offline tests
27
+ run: pytest -q -m "not network" --cov=basinkit --cov-report=term-missing
@@ -0,0 +1,15 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push: { branches: [main] }
5
+
6
+ jobs:
7
+ deploy:
8
+ runs-on: ubuntu-latest
9
+ permissions: { contents: write }
10
+ steps:
11
+ - uses: actions/checkout@v5
12
+ - uses: actions/setup-python@v6
13
+ with: { python-version: "3.12" }
14
+ - run: pip install -e ".[all,dev]"
15
+ - run: mkdocs gh-deploy --force
@@ -0,0 +1,30 @@
1
+ name: Data sources
2
+
3
+ # Remote endpoints move without warning -- CDSE deprecated a STAC endpoint,
4
+ # LP DAAC retired its Data Pool, sentinelsat's backend closed entirely. This is
5
+ # the early-warning system for that, and it is deliberately NOT on every push:
6
+ #
7
+ # * it is slow (roughly seven minutes of real downloads), and
8
+ # * some sources refuse GitHub's datacenter addresses outright. HydroSHEDS
9
+ # answers 403 to Actions runners while serving the identical request to a
10
+ # laptop, so gating every push on it would paint the badge red for a reason
11
+ # that has nothing to do with this repository.
12
+ #
13
+ # tests/conftest.py reports an upstream refusal as a skip and a wrong number as
14
+ # a failure, so red here means basinkit is returning something incorrect.
15
+
16
+ on:
17
+ schedule:
18
+ - cron: "0 6 * * 1" # Mondays, 06:00 UTC
19
+ workflow_dispatch: # and whenever you want to ask
20
+
21
+ jobs:
22
+ network:
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@v5
26
+ - uses: actions/setup-python@v6
27
+ with: { python-version: "3.12" }
28
+ - run: pip install -e ".[all,dev]"
29
+ - name: Live source tests
30
+ run: pytest -q -m network -rs
@@ -0,0 +1,18 @@
1
+ name: Release
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ environment: pypi
11
+ permissions:
12
+ id-token: write # trusted publishing, no API token to leak
13
+ steps:
14
+ - uses: actions/checkout@v5
15
+ - uses: actions/setup-python@v6
16
+ with: { python-version: "3.12" }
17
+ - run: pip install build && python -m build
18
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ build/
5
+ dist/
6
+ .pytest_cache/
7
+ .ruff_cache/
8
+ .coverage
9
+ htmlcov/
10
+ site/
11
+ .venv/
@@ -0,0 +1,165 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (unreleased)
4
+
5
+ First release.
6
+
7
+ ### Delineation
8
+ - Three global backends: HydroBASINS graph traversal (default), D8 routing over
9
+ Copernicus DEM via pyflwdir, and the Global Watersheds API.
10
+ - `backend="auto"` falls back to DEM routing when a basin sits at the
11
+ HydroBASINS level-12 resolution floor.
12
+ - Outlet snapping to the local maximum of upstream area, with a minimum
13
+ drainage threshold so a hillslope point fails loudly instead of returning a
14
+ few hectares.
15
+ - Adaptive window growth when a DEM-routed basin reaches the window edge.
16
+ - Off-channel advisory: when a unit at least `river_snap_ratio` times larger
17
+ sits within 20 km but outside the snap radius, basinkit warns and records
18
+ `off_channel_candidate` in the provenance *without* moving the point. Found
19
+ by running the README's own example: `from_point(26.5, 85.2)`, advertised as
20
+ "14,384 km2", is 16 km off the Bagmati channel and returns 434 km2. The
21
+ delineation was correct; the coordinate and the documented figure were not,
22
+ and nothing warned. Every user-facing example now uses the Chatara outlet,
23
+ which the network suite checks against the published 54,100 km2, and an
24
+ offline test pins the documented coordinate to that one.
25
+
26
+ ### Testing and CI
27
+ - The live-source suite moved out of `CI` into its own weekly `Data sources`
28
+ workflow. Some hosts refuse GitHub's datacenter addresses -- HydroSHEDS
29
+ answers 403 to Actions runners while serving the identical request to a
30
+ laptop -- so gating every push on it painted the badge red for a reason that
31
+ had nothing to do with this repository. `CI` is now purely offline across
32
+ three operating systems and Python 3.10 to 3.13, and red there always means a
33
+ defect here.
34
+ - `tests/conftest.py` reports an upstream refusal (403, 404, 429, 5xx, timeout)
35
+ as a *skip* with the reason attached, and leaves a wrong number a failure. A
36
+ monitor you stop reading is not a monitor.
37
+
38
+ ### Data
39
+ - Anonymous access to 20 datasets: Copernicus DEM GLO-30/90, NASADEM, SRTM,
40
+ HydroBASINS/RIVERS/LAKES/ATLAS, ESA WorldCover, ESRI annual LULC, SoilGrids,
41
+ CHIRPS v3.0, PERSIANN-CDR, TerraClimate, JRC Global Surface Water,
42
+ Sentinel-2, Sentinel-1 RTC, Landsat C2 L2, HLS.
43
+ - Everything is clipped and masked to the basin polygon, not its bounding box.
44
+ - Cosine-latitude weighting in zonal means; equal-area projection for areas.
45
+ - Memory-budgeted mosaicking with automatic coarsening above 100 Mpx.
46
+ - CHIRPS read through HTTP range requests, so only the basin window transfers.
47
+
48
+ ### Fixed during multi-continent verification
49
+ - **Outlet snapping for HydroBASINS.** A coordinate on a big river's bank falls
50
+ in a small lateral unit: Rhine at Lobith returned 271 km2 instead of 160,800,
51
+ Godavari at Polavaram 353 instead of 307,800. Now snaps when a unit within
52
+ 1 km drains at least 10x more, warns, and records the jump in provenance.
53
+ - **Sentinel-2 offset applied twice.** Earth Search pre-applies the baseline
54
+ 04.00 BOA offset and flags it while still publishing the nominal -0.1;
55
+ applying both gave negative surface reflectance.
56
+ - **Nodata averaged into composites.** Optical nodata is 0, so an unmasked
57
+ median over a basin spanning several MGRS tiles came back 87% zero. Now
58
+ loaded as float32 with NaN, and grouped by solar day.
59
+ - **Clip failed on every Dataset.** `Dataset` has no `.values` array, so every
60
+ multi-band STAC result raised. The check is now Dataset-aware and never
61
+ forces a lazy cube.
62
+ - **Exported rasters carried no nodata**, so the clip stopped existing outside
63
+ Python. Now declared in the file header.
64
+ - **255 averaged into JRC surface water**, producing occurrence above 100%.
65
+ - **`outlets="min"` in DEM routing** pulled flow networks toward the window's
66
+ lowest cell.
67
+
68
+ ### Performance
69
+ - CHIRPS via HTTP range reads rather than whole-globe downloads: a three-year
70
+ basin series went from 116 s to 24 s.
71
+ - TerraClimate fetches its per-variable, per-year files concurrently.
72
+ - Pixel budgets on the STAC path, with a lower one for Sentinel-1 RTC, whose
73
+ float32 10 m frames cost far more per pixel than optical COGs.
74
+
75
+ ### Corrections after external review
76
+ - **The same basin was quoted with two different reference areas.** The Roadmap
77
+ compared the Koshi against `reported_up_area_km2` (54,581 km2 -- HydroBASINS'
78
+ own bookkeeping) and headlined the resulting 0.15%; Verification correctly
79
+ used the published 54,100 km2 and 0.73%. The first comparison is close to
80
+ circular, since both numbers come from the same polygons. Every document now
81
+ uses the published reference, and `docs/delineation.md` says plainly that the
82
+ internal check is not an accuracy figure. Two regression tests guard it.
83
+ - **Accuracy is now reported as a distribution.** n = 12, median 0.74%, eight
84
+ within 1%, nine within 3% -- instead of the single most flattering basin. The
85
+ sample size and its bias (large, well-mapped rivers with published areas) are
86
+ stated.
87
+ - **"Not one of these raised an exception" was false.** Two of the ten defects
88
+ did; both were found by auditing untested methods rather than by the suite.
89
+ - **Every delineation figure now traces to one run** of
90
+ `verify/run_delineation.py`, not to a mixture of runs. The Rhine row had been
91
+ carried over from before the distance fix.
92
+ - **The base grid of each backend is stated**, in the module docstring, the
93
+ README and both reports. HydroBASINS is extracted from HydroSHEDS at 15
94
+ arc-seconds, so the default routes on a ~460 m grid built from February 2000
95
+ SRTM. It was the most load-bearing undisclosed fact about the package.
96
+ - **MERIT-Hydro is reframed from cross-check to migration path.** At 3
97
+ arc-seconds it is five times finer than the default and hydrologically
98
+ conditioned; the `api` backend already implements that path. What blocks it
99
+ as a default is its non-commercial licence and unautomatable acquisition, not
100
+ the routing.
101
+ - Both reports carry a byline and an anchor date.
102
+
103
+ ### Logo
104
+ - An animated multicolour mark in `assets/`: four coloured tributaries joining
105
+ one river inside a basin divide. The colour carries the idea -- many open
106
+ data sources, one basin -- rather than decorating it.
107
+ - Respects `prefers-reduced-motion`, follows the viewer's colour scheme, and
108
+ ships pinned light and dark wordmarks because GitHub's theme and the
109
+ browser's `prefers-color-scheme` can disagree.
110
+ - Lettering is Archivo glyph outlines, not live text: an SVG shown through
111
+ `<img>` cannot load a web font.
112
+ - Geometry is generated rather than hand-drawn -- a wobbling polar radius for
113
+ the divide, endpoints clamped against it so nothing pokes through, and plain
114
+ downstream-sagging arcs for the tributaries.
115
+
116
+ ### QGIS plugin
117
+ - A Processing provider in `qgis_plugin/`: delineate a basin from a canvas
118
+ click, fetch layers clipped to it, and basin statistics with an HTML report.
119
+ - Registration happens in `initProcessing()` with `initGui()` forwarding to it,
120
+ so the algorithms are visible to `qgis_process` and headless runs, not only
121
+ to the desktop GUI.
122
+ - `compat.py` covers two silent breakages across supported QGIS versions:
123
+ `QgsField` took a `QMetaType` argument from 3.38 (and Qt6, which QGIS 4 uses,
124
+ removed `QVariant.Type` outright), and `Qgis.WkbType` replaced
125
+ `QgsWkbTypes.Type` in 3.30.
126
+ - Checked against stubbed bindings at four QGIS versions, plus an end-to-end
127
+ run of the real `processAlgorithm` bodies. 22 further tests validate the
128
+ metadata against what plugins.qgis.org actually enforces -- which is not what
129
+ its documentation table says.
130
+
131
+ ### Honesty pass on the catalogue
132
+ - Every entry now carries an explicit `implemented` flag. Seven datasets were
133
+ listed as if they were fetchable when no fetcher existed; asking for one now
134
+ raises `NotImplementedSource` carrying the access route, the licence and what
135
+ to do with the data once you have it. `basinkit catalog` marks them `DOC`.
136
+ - `grace` was listed as needing no account. The reliable route is JPL via
137
+ Earthdata Login; the CSR anonymous mirror was unreachable when checked.
138
+ Corrected rather than left optimistic.
139
+ - A test asserts every key in `DEFAULT_STACK` is anonymous, commercially safe,
140
+ redistributable **and** implemented.
141
+
142
+ ### Added
143
+ - **BasinATLAS** (`Basin.attributes()`): 281 pre-computed environmental
144
+ variables per sub-basin. The `_u` columns are already aggregated upstream, so
145
+ one lookup characterises a whole catchment without touching a raster. Its
146
+ mean elevation for the Koshi agrees with basinkit's own COP-DEM computation
147
+ to 0.1%.
148
+ - Scaled integers are decoded: read raw, the Koshi appears to average 50 degrees
149
+ Celsius and a 204 degree slope.
150
+ - `Basin.plot()` crashed on NumPy 2.x (`ndarray.ptp()` was removed in 2.0) and
151
+ had never been executed by any test. Fixed, with a test that scans the
152
+ package for every NumPy-2-removed API.
153
+
154
+ ### Verified
155
+ - Test suite run on Python 3.10, 3.11 and 3.12 — the CI matrix previously
156
+ claimed three versions and only one had ever run.
157
+ - Paths an audit found untested and now covered: `explore()` with leafmap,
158
+ `plot()`, `from_file()` including reprojection, the DEM backend's window-edge
159
+ exclusion and its refusal to return a truncated basin.
160
+
161
+ ### Licensing
162
+ - Machine-readable catalogue driving the fetchers, the CLI and
163
+ `Basin.license_report()`.
164
+ - `Basin.check_license()` raises on restricted use.
165
+ - MERIT Hydro, FABDEM, MSWEP and GRDC are catalogued but opt-in and flagged.
@@ -0,0 +1,34 @@
1
+ # Citation metadata. GitHub renders this as a "Cite this repository" button on
2
+ # the repo page, and Zenodo reads it when minting the DOI.
3
+ #
4
+ # Two fields are placeholders until the first release exists:
5
+ # doi — filled in after Zenodo mints it (see PUBLISHING.md step 6)
6
+ # date-released — the date you actually cut v0.1.0
7
+ cff-version: 1.2.0
8
+ message: "If you use basinkit in published work, please cite it as below, and cite the underlying datasets — Basin.license_report() prints those."
9
+ title: "basinkit: basin-scale acquisition of open Earth observation data"
10
+ abstract: >-
11
+ A Python package that delineates the upstream river basin of any coordinate
12
+ on Earth and returns open Earth observation layers clipped and masked to that
13
+ polygon rather than to its bounding box. Nineteen datasets are reachable
14
+ without an account. Delineation is validated against published gauge areas on
15
+ six continents.
16
+ type: software
17
+ authors:
18
+ - given-names: Pradeepika
19
+ family-names: Kaushik
20
+ # orcid: "https://orcid.org/0000-0000-0000-0000" # add yours
21
+ version: 0.1.0
22
+ date-released: 2026-08-30
23
+ license: MIT
24
+ repository-code: "https://github.com/Praddy-GByte/basinkit"
25
+ url: "https://github.com/Praddy-GByte/basinkit"
26
+ keywords:
27
+ - hydrology
28
+ - watershed delineation
29
+ - river basin
30
+ - remote sensing
31
+ - earth observation
32
+ - open data
33
+ - gis
34
+ - qgis
@@ -0,0 +1,47 @@
1
+ # Contributing
2
+
3
+ ## Adding a data source
4
+
5
+ Two rules, and they are what the package is for.
6
+
7
+ 1. **Register it in `basinkit/catalog.py` first.** Resolution, coverage,
8
+ licence, auth requirement, whether it can be redistributed and used
9
+ commercially. That table is not documentation — the fetchers and the CLI
10
+ read from it, so it cannot drift from reality.
11
+ 2. **Set `implemented` honestly.** If there is no fetcher, the entry is
12
+ `implemented=False` and asking for it raises `NotImplementedSource` with the
13
+ access route. A catalogue that lists datasets it cannot deliver reads as a
14
+ feature list, which is worse than a shorter catalogue.
15
+ 3. **Anonymous or opt-in.** If a source needs an account or its licence
16
+ restricts use, it does not go in `DEFAULT_STACK`. It gets `auth` set
17
+ accurately and a `notes` string explaining what the user is walking into,
18
+ and it announces itself before the first byte moves.
19
+
20
+ New adapters go in `basinkit/sources/` grouped by *access pattern* (STAC,
21
+ tiled COG, WCS, NetCDF-over-HTTP, vector download), not by dataset.
22
+
23
+ Every fetcher takes a geometry and clips to it. Bounding boxes are for finding
24
+ tiles, never for what comes back.
25
+
26
+ ## Tests
27
+
28
+ ```bash
29
+ pytest -m "not network" # fast, runs everywhere
30
+ pytest -m network # hits real endpoints
31
+ ```
32
+
33
+ Prefer tests that check against an **independently known value** — the Koshi
34
+ basin's published area, its documented mean annual rainfall, the fact that
35
+ water occurrence is a percentage — over snapshots of whatever the code
36
+ currently produces. A snapshot test passes happily while the answer is wrong.
37
+
38
+ The network suite runs weekly in CI on purpose. Remote endpoints move without
39
+ warning: CDSE deprecated a STAC endpoint, LP DAAC retired its Data Pool,
40
+ sentinelsat's entire backend closed. The scheduled run is how we find out
41
+ before users do.
42
+
43
+ ## Style
44
+
45
+ `ruff check basinkit tests` must be clean. Comments should explain *why* — the
46
+ non-obvious constraint, the failure mode being guarded against — not restate
47
+ what the line does.
basinkit-0.1.0/LICENSE ADDED
@@ -0,0 +1,33 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pradeepika Kaushik
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ NOTE ON DATA LICENCES
26
+
27
+ This licence covers the basinkit source code only. The datasets basinkit
28
+ fetches carry their own terms. Everything in the default stack is CC BY 4.0 or
29
+ more permissive and requires attribution; a few opt-in datasets (MERIT Hydro,
30
+ FABDEM, MSWEP, GRDC) restrict commercial use or redistribution outright.
31
+
32
+ Run `basinkit catalog` or `Basin.license_report()` for the current terms of
33
+ every layer you used.