water-ogcapi 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 (135) hide show
  1. water_ogcapi-0.1.0/.agents/skills/live-api/SKILL.md +105 -0
  2. water_ogcapi-0.1.0/.agents/skills/update-package/SKILL.md +119 -0
  3. water_ogcapi-0.1.0/.agents/skills/update-package/summarize.py +49 -0
  4. water_ogcapi-0.1.0/.gemini/settings.json +8 -0
  5. water_ogcapi-0.1.0/.github/dependabot.yml +7 -0
  6. water_ogcapi-0.1.0/.github/pull_request_template.md +26 -0
  7. water_ogcapi-0.1.0/.github/workflows/ci.yml +76 -0
  8. water_ogcapi-0.1.0/.github/workflows/docs.yml +50 -0
  9. water_ogcapi-0.1.0/.github/workflows/drift.yml +43 -0
  10. water_ogcapi-0.1.0/.github/workflows/release.yml +94 -0
  11. water_ogcapi-0.1.0/.gitignore +83 -0
  12. water_ogcapi-0.1.0/.pre-commit-config.yaml +104 -0
  13. water_ogcapi-0.1.0/AGENTS.md +232 -0
  14. water_ogcapi-0.1.0/CODE_OF_CONDUCT.md +120 -0
  15. water_ogcapi-0.1.0/LICENSE +21 -0
  16. water_ogcapi-0.1.0/PKG-INFO +159 -0
  17. water_ogcapi-0.1.0/README.md +127 -0
  18. water_ogcapi-0.1.0/REVIEW.md +60 -0
  19. water_ogcapi-0.1.0/SECURITY.md +19 -0
  20. water_ogcapi-0.1.0/docs/.nav.yml +13 -0
  21. water_ogcapi-0.1.0/docs/api.md +68 -0
  22. water_ogcapi-0.1.0/docs/contributing.md +1 -0
  23. water_ogcapi-0.1.0/docs/design.md +2857 -0
  24. water_ogcapi-0.1.0/docs/endpoints.md +82 -0
  25. water_ogcapi-0.1.0/docs/examples/cache_quota_resume.ipynb +408 -0
  26. water_ogcapi-0.1.0/docs/examples/cache_quota_resume.py +154 -0
  27. water_ogcapi-0.1.0/docs/examples/fabricdata.ipynb +482 -0
  28. water_ogcapi-0.1.0/docs/examples/fabricdata.py +135 -0
  29. water_ogcapi-0.1.0/docs/examples/geoconnex.ipynb +397 -0
  30. water_ogcapi-0.1.0/docs/examples/geoconnex.py +126 -0
  31. water_ogcapi-0.1.0/docs/examples/images/cache_quota_resume.png +0 -0
  32. water_ogcapi-0.1.0/docs/examples/images/fabricdata.png +0 -0
  33. water_ogcapi-0.1.0/docs/examples/images/geoconnex.png +0 -0
  34. water_ogcapi-0.1.0/docs/examples/images/nwis.png +0 -0
  35. water_ogcapi-0.1.0/docs/examples/images/transport.png +0 -0
  36. water_ogcapi-0.1.0/docs/examples/index.md +27 -0
  37. water_ogcapi-0.1.0/docs/examples/nwis.ipynb +472 -0
  38. water_ogcapi-0.1.0/docs/examples/nwis.py +144 -0
  39. water_ogcapi-0.1.0/docs/examples/transport.ipynb +435 -0
  40. water_ogcapi-0.1.0/docs/examples/transport.py +227 -0
  41. water_ogcapi-0.1.0/docs/guide/.nav.yml +14 -0
  42. water_ogcapi-0.1.0/docs/guide/dataretrieval.md +122 -0
  43. water_ogcapi-0.1.0/docs/guide/errors.md +75 -0
  44. water_ogcapi-0.1.0/docs/guide/index.md +35 -0
  45. water_ogcapi-0.1.0/docs/guide/logging.md +117 -0
  46. water_ogcapi-0.1.0/docs/guide/pagination.md +123 -0
  47. water_ogcapi-0.1.0/docs/guide/performance.md +150 -0
  48. water_ogcapi-0.1.0/docs/guide/pynhd.md +134 -0
  49. water_ogcapi-0.1.0/docs/guide/queries.md +146 -0
  50. water_ogcapi-0.1.0/docs/guide/setup.md +71 -0
  51. water_ogcapi-0.1.0/docs/guide/sync-and-async.md +115 -0
  52. water_ogcapi-0.1.0/docs/guide/workers.md +68 -0
  53. water_ogcapi-0.1.0/docs/index.md +5 -0
  54. water_ogcapi-0.1.0/docs/license.md +5 -0
  55. water_ogcapi-0.1.0/pixi.lock +5357 -0
  56. water_ogcapi-0.1.0/pyproject.toml +379 -0
  57. water_ogcapi-0.1.0/scripts/audit-requirements.txt +2 -0
  58. water_ogcapi-0.1.0/scripts/bench.py +538 -0
  59. water_ogcapi-0.1.0/scripts/convert_notebooks.py +19 -0
  60. water_ogcapi-0.1.0/scripts/griffe_twins.py +74 -0
  61. water_ogcapi-0.1.0/scripts/lint_untracked.py +21 -0
  62. water_ogcapi-0.1.0/scripts/lock_sdists.py +34 -0
  63. water_ogcapi-0.1.0/scripts/openapi_drift.py +166 -0
  64. water_ogcapi-0.1.0/scripts/record_fixtures.py +145 -0
  65. water_ogcapi-0.1.0/scripts/roadmap.py +107 -0
  66. water_ogcapi-0.1.0/scripts/run_guides.py +59 -0
  67. water_ogcapi-0.1.0/src/water_ogcapi/__init__.py +70 -0
  68. water_ogcapi-0.1.0/src/water_ogcapi/_arrow.py +89 -0
  69. water_ogcapi-0.1.0/src/water_ogcapi/_cache.py +37 -0
  70. water_ogcapi-0.1.0/src/water_ogcapi/_client.py +1017 -0
  71. water_ogcapi-0.1.0/src/water_ogcapi/_governor.py +138 -0
  72. water_ogcapi-0.1.0/src/water_ogcapi/_logging.py +151 -0
  73. water_ogcapi-0.1.0/src/water_ogcapi/_metadata.py +456 -0
  74. water_ogcapi-0.1.0/src/water_ogcapi/_paging.py +210 -0
  75. water_ogcapi-0.1.0/src/water_ogcapi/_query.py +444 -0
  76. water_ogcapi-0.1.0/src/water_ogcapi/_redact.py +40 -0
  77. water_ogcapi-0.1.0/src/water_ogcapi/_results.py +538 -0
  78. water_ogcapi-0.1.0/src/water_ogcapi/_services.py +275 -0
  79. water_ogcapi-0.1.0/src/water_ogcapi/_sync.py +355 -0
  80. water_ogcapi-0.1.0/src/water_ogcapi/_transport.py +1446 -0
  81. water_ogcapi-0.1.0/src/water_ogcapi/aio.py +27 -0
  82. water_ogcapi-0.1.0/src/water_ogcapi/exceptions.py +199 -0
  83. water_ogcapi-0.1.0/src/water_ogcapi/py.typed +0 -0
  84. water_ogcapi-0.1.0/tests/conftest.py +19 -0
  85. water_ogcapi-0.1.0/tests/fixtures/README.md +60 -0
  86. water_ogcapi-0.1.0/tests/fixtures/fabricdata/collections.json +4334 -0
  87. water_ogcapi-0.1.0/tests/fixtures/fabricdata/gagesii/collection.json +161 -0
  88. water_ogcapi-0.1.0/tests/fixtures/fabricdata/gagesii/items-1.json +236 -0
  89. water_ogcapi-0.1.0/tests/fixtures/fabricdata/gagesii/items-2.json +170 -0
  90. water_ogcapi-0.1.0/tests/fixtures/fabricdata/gagesii/queryables.json +108 -0
  91. water_ogcapi-0.1.0/tests/fixtures/fabricdata/gagesii/schema.json +92 -0
  92. water_ogcapi-0.1.0/tests/fixtures/fabricdata/landing.json +112 -0
  93. water_ogcapi-0.1.0/tests/fixtures/fabricdata/openapi.json +17785 -0
  94. water_ogcapi-0.1.0/tests/fixtures/geoconnex/collections.json +3590 -0
  95. water_ogcapi-0.1.0/tests/fixtures/geoconnex/gages/collection.json +199 -0
  96. water_ogcapi-0.1.0/tests/fixtures/geoconnex/gages/queryables.json +120 -0
  97. water_ogcapi-0.1.0/tests/fixtures/geoconnex/gages/schema.json +100 -0
  98. water_ogcapi-0.1.0/tests/fixtures/geoconnex/hu02/items-1.json +252 -0
  99. water_ogcapi-0.1.0/tests/fixtures/geoconnex/hu02/items-2.json +254 -0
  100. water_ogcapi-0.1.0/tests/fixtures/geoconnex/hu02/items-3.json +136 -0
  101. water_ogcapi-0.1.0/tests/fixtures/geoconnex/landing.json +101 -0
  102. water_ogcapi-0.1.0/tests/fixtures/geoconnex/openapi.json +10786 -0
  103. water_ogcapi-0.1.0/tests/fixtures/nwis/collections.json +4627 -0
  104. water_ogcapi-0.1.0/tests/fixtures/nwis/daily/collection.json +159 -0
  105. water_ogcapi-0.1.0/tests/fixtures/nwis/daily/items-1.json +156 -0
  106. water_ogcapi-0.1.0/tests/fixtures/nwis/daily/items-2.json +150 -0
  107. water_ogcapi-0.1.0/tests/fixtures/nwis/daily/items-3.json +150 -0
  108. water_ogcapi-0.1.0/tests/fixtures/nwis/daily/queryables.json +311 -0
  109. water_ogcapi-0.1.0/tests/fixtures/nwis/daily/schema.json +113 -0
  110. water_ogcapi-0.1.0/tests/fixtures/nwis/landing.json +110 -0
  111. water_ogcapi-0.1.0/tests/fixtures/nwis/monitoring-locations/collection.json +158 -0
  112. water_ogcapi-0.1.0/tests/fixtures/nwis/monitoring-locations/item-USGS-01646500.json +132 -0
  113. water_ogcapi-0.1.0/tests/fixtures/nwis/monitoring-locations/items-1.json +253 -0
  114. water_ogcapi-0.1.0/tests/fixtures/nwis/monitoring-locations/items-2.json +249 -0
  115. water_ogcapi-0.1.0/tests/fixtures/nwis/monitoring-locations/items-3.json +249 -0
  116. water_ogcapi-0.1.0/tests/fixtures/nwis/monitoring-locations/queryables.json +264 -0
  117. water_ogcapi-0.1.0/tests/fixtures/nwis/monitoring-locations/schema.json +307 -0
  118. water_ogcapi-0.1.0/tests/fixtures/nwis/openapi.json +29560 -0
  119. water_ogcapi-0.1.0/tests/test_arrow.py +409 -0
  120. water_ogcapi-0.1.0/tests/test_client.py +1729 -0
  121. water_ogcapi-0.1.0/tests/test_download.py +1025 -0
  122. water_ogcapi-0.1.0/tests/test_exceptions.py +91 -0
  123. water_ogcapi-0.1.0/tests/test_fanout.py +657 -0
  124. water_ogcapi-0.1.0/tests/test_governor.py +398 -0
  125. water_ogcapi-0.1.0/tests/test_logging.py +143 -0
  126. water_ogcapi-0.1.0/tests/test_metadata.py +192 -0
  127. water_ogcapi-0.1.0/tests/test_openapi_drift.py +237 -0
  128. water_ogcapi-0.1.0/tests/test_pickles.py +131 -0
  129. water_ogcapi-0.1.0/tests/test_results.py +303 -0
  130. water_ogcapi-0.1.0/tests/test_roadmap.py +64 -0
  131. water_ogcapi-0.1.0/tests/test_services.py +379 -0
  132. water_ogcapi-0.1.0/tests/test_sync.py +552 -0
  133. water_ogcapi-0.1.0/tests/test_transport.py +1566 -0
  134. water_ogcapi-0.1.0/tests/test_validation.py +224 -0
  135. water_ogcapi-0.1.0/zensical.toml +62 -0
@@ -0,0 +1,105 @@
1
+ ---
2
+ name: live-api
3
+ description: Use before sending any request to the live NWIS, FabricData, or GeoConnex endpoints, including recording test fixtures, writing or running network-marked tests, checking rate-limit or auth headers, and writing up API behavior findings. Covers credential handling, quota limits, and what evidence to keep.
4
+ ---
5
+
6
+ # Working against the live endpoints
7
+
8
+ These are production services that USGS runs for every user. Base URLs are in
9
+ `README.md`.
10
+
11
+ ## Credentials
12
+
13
+ A leaked key ends up in git history, CI logs, or a public issue, where anyone can spend
14
+ its quota. Recorded fixtures and copied URLs are the easy ways for it to reach disk.
15
+
16
+ - Read the key from the `USGS_API_KEY` environment variable. Never print it or write it
17
+ into a file, commit, issue, or chat message. Run an offline probe of code that reads
18
+ the variable as `env -u USGS_API_KEY <probe>` or with a fake value, and print only
19
+ comparisons (`headers["X-Api-Key"] == FAKE`); put `env -u` on the probe command
20
+ only, since the redaction filter below reads the variable.
21
+ - USGS documents two forms: an `X-Api-Key` request header and an `api_key` query
22
+ parameter. Use the header. The query form puts the key in the request URL, and the
23
+ server may echo query parameters into the `next` links of the response body.
24
+ - Scrub at record time, before anything is written to disk. Store no request headers.
25
+ - GeoConnex takes no key. Never send it one.
26
+ - The HTTP/2 encoder logs headers: `hpack` writes every header value, the key included,
27
+ at DEBUG on its own logger, and the package raises that logger to INFO at import
28
+ (D-20). A test that the key never reaches a log captures the root logger at DEBUG
29
+ while the real encoder runs. A mocked transport skips the encoder, so it proves only
30
+ the layers above it.
31
+ - A server can echo the key back: a redirect's `Location` has carried it as a query
32
+ parameter. Pipe every probe's output through a filter that replaces the key's value,
33
+ and request a base URL with its trailing slash, since the bare form redirects.
34
+
35
+ ```bash
36
+ <probe command> 2>&1 | python3 -c 'import os, sys; k = os.environ["USGS_API_KEY"]; sys.stdout.writelines(l.replace(k, "REDACTED") for l in sys.stdin)'
37
+ ```
38
+
39
+ - Before finishing, scan for the current key. The command fails loudly when the variable
40
+ is unset or empty, prints file names only, and keeps the key off the command line.
41
+ No output means the current key is not in the working tree; it cannot find old or
42
+ encoded keys.
43
+
44
+ ```bash
45
+ : "${USGS_API_KEY:?}" && command grep -rlF -f <(printenv USGS_API_KEY) . --exclude-dir=.git --exclude-dir=.pixi
46
+ ```
47
+
48
+ ## Quota
49
+
50
+ - Keep probes small: a low `limit`, one page unless the question is about paging.
51
+ - When a script derives a committed artifact (a snapshot, a fixture) from a live
52
+ response, save the raw response once, scrubbed as a fixture is, to the scratchpad
53
+ and never the repo, and develop the derivation against that file. Refetch only to
54
+ confirm the final artifact.
55
+ - A test run reaches the live services only with `-m network` (`test-network`) or
56
+ `-m ''` (`test-all`); `addopts` deselects network-marked tests everywhere else, so a
57
+ narrowed run (`pixi r -e test315 pytest tests/test_x.py`) stays offline.
58
+ - Probe through `Transport(max_retries=0)` so one call sends one request. The default
59
+ retries up to three times, and the caller never sees the intermediate responses.
60
+ - Read `X-RateLimit-Remaining` on each response and stop well before zero. It lags a
61
+ burst of requests by a few seconds, so space probes out. If a service sends no
62
+ `X-RateLimit-*` headers, keep to a handful of requests per task.
63
+ - Never provoke a 429. Test rate-limit handling against `httpx2.MockTransport` only.
64
+ - Make network tests for FabricData skip when the backend does not respond.
65
+
66
+ ## Stable targets
67
+
68
+ - NWIS: monitoring location `01646500` (Potomac River near Washington, DC), and a small
69
+ bounding box around Washington, DC.
70
+ - GeoConnex: the 22 two-digit HUCs in `hu02`, `01` to `22`.
71
+
72
+ ## Fixtures
73
+
74
+ - Record from real responses. Keep the status, the response headers, and the body. Keep
75
+ the request URL and params with credentials removed.
76
+ - A fixture stores the decoded body, so drop the headers that describe the wire bytes,
77
+ `content-encoding` and `content-length`: a mock serving a decoded body under
78
+ `content-encoding: gzip` tries to gunzip plain JSON. Never store `set-cookie`.
79
+ `scripts/record_fixtures.py` drops all three.
80
+ - Record the request date with each fixture. The NWIS API is v1 and USGS may change it.
81
+ - If `tests/fixtures/` does not exist yet, propose a layout before creating one.
82
+ - Note which services send `X-RateLimit-*` headers. The quota logic depends on it.
83
+
84
+ ## Checking that the key is honored
85
+
86
+ Send one keyed and one unkeyed request to the same endpoint and compare the
87
+ `X-RateLimit-*` headers. A higher limit on the keyed response shows the key was honored.
88
+ Equal limits or missing headers are inconclusive; record them as such.
89
+
90
+ USGS raises a key's limit on request, and a raised key reports its own limit on every
91
+ endpoint (D-30). A check made with such a key shows that key's limit, so record a
92
+ default limit only from a key that has the default. Ignore `X-RateLimit-*` on a response
93
+ with `x-cache: Hit`: CloudFront serves the headers stored with the cached response.
94
+
95
+ ## Writing up findings
96
+
97
+ Write each finding as a neutral, reproducible observation:
98
+
99
+ 1. The request: method, URL with credentials removed, and date.
100
+ 1. What the response showed: status, the relevant headers, a short body excerpt.
101
+ 1. What was expected, citing the OGC API spec or the USGS docs.
102
+
103
+ Keep what you observed apart from what you infer from it. `docs/design.md` Q-07 lists
104
+ claims that are still unverified. Do not state any of them as fact until a live response
105
+ confirms it.
@@ -0,0 +1,119 @@
1
+ ---
2
+ name: update-package
3
+ description: Update this package's locked dependencies and pre-commit hooks, then land the change. `pixi update` refreshes pixi.lock, `pixi r pcupdate` moves the hook revisions, the full gate from AGENTS.md runs with the dependency audit and the network tests, findings the newer tools raise get fixed at their cause, and the change goes in through a PR merged once CI passes. Use when asked to update dependencies, refresh or bump the lock file, update the pre-commit hooks, or run /update-package.
4
+ ---
5
+
6
+ # Update dependencies and hooks
7
+
8
+ Asking for this skill is the go-ahead to push the branch, open the PR, and merge it once
9
+ CI passes, for a contributor allowed to merge; anyone else stops at the open PR and asks
10
+ a maintainer. It covers nothing else: no tag, no release, no force-push, no change to
11
+ repository settings, and no merge while a required check is red or pending.
12
+
13
+ `$SCRATCH` below is a scratch directory outside the repository.
14
+
15
+ ## 1. Preflight
16
+
17
+ Stop at the first failure and report it. The tree must be clean and match `origin/main`,
18
+ since the PR has to hold the update and nothing else:
19
+
20
+ ```bash
21
+ git fetch -q origin
22
+ DEF=$(git symbolic-ref --short refs/remotes/origin/HEAD | sed 's#^origin/##')
23
+ test "$(git branch --show-current)" = "$DEF" &&
24
+ test -z "$(git status --porcelain)" &&
25
+ test "$(git rev-parse HEAD)" = "$(git rev-parse "origin/$DEF")"
26
+ ```
27
+
28
+ Then branch, following the `<type>/<short-description>` rule in AGENTS.md:
29
+ `B=chore/update-deps-$(date +%F); git switch -c "$B"`, adding `-2` for a second run the
30
+ same day.
31
+
32
+ ## 2. Update
33
+
34
+ ```bash
35
+ pixi update --json >| "$SCRATCH/pixi-update.json"
36
+ pixi r pcupdate >| "$SCRATCH/pcupdate.log" 2>&1
37
+ python3 .agents/skills/update-package/summarize.py "$SCRATCH/pixi-update.json"
38
+ ```
39
+
40
+ The script prints one Markdown row per change (package, before, after, environments) and
41
+ the change count on stderr; a report with no changes prints `0 package changes`. A row
42
+ with the same version on both sides is a build-string change. The hook log names each
43
+ hook that moved (`updating v0.16.10 -> v0.17.1`). Mark the changes to the runtime
44
+ dependencies, `[project] dependencies` in `pyproject.toml` (`httpx2` and `h2`), since
45
+ reviewers read those first. When neither `pixi.lock` nor `.pre-commit-config.yaml`
46
+ changed, report that, delete the branch, and stop.
47
+
48
+ ## 3. Gate
49
+
50
+ Delete `.coverage` and any `.coverage.*` files first, since coverage accumulates across
51
+ runs (AGENTS.md, Gotchas). Then run, each to a clean exit:
52
+
53
+ ```bash
54
+ pixi r lint && pixi r typecheck && pixi r -e test313 test && pixi r -e test315 test &&
55
+ pixi r docs && pixi r audit
56
+ ```
57
+
58
+ - Lint runs the new hook versions over every file. When a formatter rewrites files, run
59
+ lint once more and keep the rewrite for its own commit.
60
+ - The docs build and the audit belong here even with no change under `docs/`: a new
61
+ zensical or mkdocstrings can break the build, and a new resolution can pull in an
62
+ advisory.
63
+ - When a runtime dependency moved, also run `pixi r -e test315 test-network`, following
64
+ the `live-api` skill. The offline tests mock the wire that `httpx2` drives, so they
65
+ cannot see a change in it.
66
+
67
+ ## 4. Fix what the new versions raise
68
+
69
+ Fix each finding at its cause. Never silence one to pass: no new ignore, no lowered
70
+ strictness in `pyproject.toml`, no skipped or loosened test, and no package pinned back
71
+ without the maintainer's yes. A finding that needs a design call, or a package whose new
72
+ version breaks something with no small fix, goes to the maintainer with the failing
73
+ output and the options: fix it, pin an upper bound, or hold the package out by updating
74
+ the others by name (`pixi update <pkg> ...`). A fix that makes or reverses a design
75
+ decision updates `docs/design.md` in the same change.
76
+
77
+ ## 5. Commit
78
+
79
+ One commit per concern, staged by path, each chained to the gate with nothing between
80
+ the gate and `&&`:
81
+
82
+ 1. `chore: update pixi.lock`, the body giving the change count and the runtime changes;
83
+ 1. `chore: update pre-commit hooks`, the body naming each hook's new revision;
84
+ 1. a formatter rewrite from the new hooks: `style: apply <tool> <version> formatting`;
85
+ 1. each fix, as `fix:` or `refactor:`, naming what the new version changed.
86
+
87
+ Check each with `git show --stat HEAD` before making the next.
88
+
89
+ ## 6. Pull request and merge
90
+
91
+ Push the branch and open the PR from `.github/pull_request_template.md`: the runtime
92
+ changes, the hook revisions, the full table in a collapsed `<details>` block, and the
93
+ gate results, network tests and audit included. With no issue to close, say so on the
94
+ body's first line in place of `Closes #`.
95
+
96
+ Wait for CI without a polling loop where your agent's environment forbids one; in a
97
+ plain terminal, `gh pr checks <n> --watch --fail-fast` blocks until the checks finish.
98
+ The ruleset on `main` requires `Test (test313)`, `Test (test315)`, `typecheck`, `lint`,
99
+ and `build`. A red check is fixed on the branch and pushed, back through steps 3 to 5.
100
+
101
+ Merge only when every required check passes and the PR reads CLEAN, with a rebase merge:
102
+ `gh pr merge <n> --rebase`. Record the branch tip first (`TIP=$(git rev-parse "$B")`),
103
+ then:
104
+
105
+ ```bash
106
+ git fetch -q origin
107
+ test "$(git rev-parse "origin/$DEF^{tree}")" = "$(git rev-parse "$TIP^{tree}")" &&
108
+ git switch -q "$DEF" && git merge -q --ff-only "origin/$DEF" && git branch -D "$B"
109
+ ```
110
+
111
+ A tree mismatch means `main` moved during the merge: report it and keep the local
112
+ branch. Report the PR, the merged commit, and the notable changes in a few lines.
113
+
114
+ ## Pre-flight before the merge
115
+
116
+ Answer in writing: the branch holds only the update and its fixes; every gate step
117
+ passed on the final tree, the docs build and the audit included; the network tests ran
118
+ if a runtime dependency moved; no finding was silenced; the runtime changes are named in
119
+ the PR; every required check passed and the PR is CLEAN.
@@ -0,0 +1,49 @@
1
+ """Print one Markdown table row per package change in a ``pixi update --json`` report.
2
+
3
+ Usage: ``python3 .agents/skills/update-package/summarize.py <report.json>``. The change
4
+ count goes to stderr; a report with no changes prints ``0 package changes``.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import re
11
+ import sys
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ # A conda entry carries only its URL: name-version-build.conda
16
+ CONDA_VERSION = re.compile(r"/[^/]+?-([^-/]+)-[^-/]+\.(?:conda|tar\.bz2)$")
17
+
18
+
19
+ def version(side: dict[str, Any] | None) -> str:
20
+ """Return the version on one side of a change, ``-`` when the package is absent."""
21
+ if not side:
22
+ return "-"
23
+ if "version" in side:
24
+ return str(side["version"])
25
+ match = CONDA_VERSION.search(str(side.get("conda", "")))
26
+ return match.group(1) if match else "?"
27
+
28
+
29
+ def main(report: Path) -> None:
30
+ """Print the rows for ``report``, one per package and version pair."""
31
+ changes: dict[tuple[str, str, str], set[str]] = {}
32
+ environments = json.loads(report.read_text(encoding="utf-8")).get("environment", {})
33
+ for env, platforms in environments.items():
34
+ for packages in platforms.values():
35
+ for package in packages:
36
+ key = (
37
+ package["name"],
38
+ version(package.get("before")),
39
+ version(package.get("after")),
40
+ )
41
+ changes.setdefault(key, set()).add(env)
42
+ out = sys.stdout
43
+ for (name, old, new), envs in sorted(changes.items()):
44
+ out.write(f"| {name} | {old} | {new} | {', '.join(sorted(envs))} |\n")
45
+ sys.stderr.write(f"{len(changes)} package changes\n")
46
+
47
+
48
+ if __name__ == "__main__":
49
+ main(Path(sys.argv[1]))
@@ -0,0 +1,8 @@
1
+ {
2
+ "context": {
3
+ "fileName": [
4
+ "AGENTS.md",
5
+ "GEMINI.md"
6
+ ]
7
+ }
8
+ }
@@ -0,0 +1,7 @@
1
+ # Keeps the SHA-pinned actions current; the comment after each pin gets bumped too.
2
+ version: 2
3
+ updates:
4
+ - package-ecosystem: github-actions
5
+ directory: /
6
+ schedule:
7
+ interval: weekly
@@ -0,0 +1,26 @@
1
+ Closes #
2
+
3
+ ## What changed
4
+
5
+ <!-- What the change does and why, in a few sentences. Name the D-, L-, or Q- entries in
6
+ docs/design.md it touches. -->
7
+
8
+ ## How it was checked
9
+
10
+ <!-- Tick what ran. Add any live-API or manual check, with what it showed. -->
11
+
12
+ - [ ] `pixi r lint`
13
+ - [ ] `pixi r typecheck`
14
+ - [ ] `pixi r -e test313 test`
15
+ - [ ] `pixi r -e test315 test`
16
+ - [ ] `pixi r docs` (needed when `docs/`, `README.md`, or `zensical.toml` changed)
17
+
18
+ ## Risk and revert
19
+
20
+ <!-- What this could break and for whom, the review depth REVIEW.md asks for, and how to
21
+ undo it: a revert of this PR, or, after a release, a fix release. -->
22
+
23
+ ## Design record
24
+
25
+ - [ ] `docs/design.md` is updated, or this change makes, reverses, or answers nothing
26
+ there
@@ -0,0 +1,76 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: ${{ github.workflow }}-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ test:
18
+ name: Test (${{ matrix.environment }}, ${{ matrix.os }})
19
+ runs-on: ${{ matrix.os }}
20
+ strategy:
21
+ fail-fast: false
22
+ matrix:
23
+ environment: [test313, test315]
24
+ # One runner per pixi platform: linux-64, osx-arm64, win-64.
25
+ os: [ubuntu-latest, macos-latest, windows-latest]
26
+ steps:
27
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
28
+ with:
29
+ fetch-depth: 0 # hatch-vcs reads tags for the version
30
+ - uses: prefix-dev/setup-pixi@ad1da6449d9e506fd127fd67ab2d054a769f6a94 # v0.11.2
31
+ with:
32
+ pixi-version: v0.84.0 # Dependabot does not bump this; see D-55
33
+ environments: ${{ matrix.environment }}
34
+ # `test` excludes the network marker, so CI never touches USGS servers.
35
+ - run: pixi run -e ${{ matrix.environment }} test
36
+
37
+ # The install is the check: a dev package locked as an sdist must build here (D-71).
38
+ dev-install:
39
+ name: Install dev (${{ matrix.os }})
40
+ runs-on: ${{ matrix.os }}
41
+ strategy:
42
+ fail-fast: false
43
+ matrix:
44
+ os: [ubuntu-latest, macos-latest, windows-latest]
45
+ steps:
46
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
47
+ with:
48
+ fetch-depth: 0 # hatch-vcs reads tags for the version
49
+ - uses: prefix-dev/setup-pixi@ad1da6449d9e506fd127fd67ab2d054a769f6a94 # v0.11.2
50
+ with:
51
+ pixi-version: v0.84.0 # Dependabot does not bump this; see D-55
52
+ environments: dev
53
+
54
+ typecheck:
55
+ runs-on: ubuntu-latest
56
+ steps:
57
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
58
+ with:
59
+ fetch-depth: 0
60
+ - uses: prefix-dev/setup-pixi@ad1da6449d9e506fd127fd67ab2d054a769f6a94 # v0.11.2
61
+ with:
62
+ pixi-version: v0.84.0 # Dependabot does not bump this; see D-55
63
+ environments: typecheck
64
+ - run: pixi run typecheck
65
+
66
+ lint:
67
+ runs-on: ubuntu-latest
68
+ steps:
69
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
70
+ - uses: prefix-dev/setup-pixi@ad1da6449d9e506fd127fd67ab2d054a769f6a94 # v0.11.2
71
+ with:
72
+ pixi-version: v0.84.0 # Dependabot does not bump this; see D-55
73
+ environments: lint
74
+ - run: pixi run lint
75
+ # Needs network: resolves the runtime dependencies from PyPI and checks their advisories.
76
+ - run: pixi run audit
@@ -0,0 +1,50 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ # Refreshes the roadmap page, which is generated from the GitHub milestones.
8
+ schedule:
9
+ - cron: 0 6 * * *
10
+ workflow_dispatch:
11
+
12
+ permissions:
13
+ contents: read
14
+ issues: read
15
+
16
+ # Never cancel a Pages deployment mid-flight; superseded PR builds can go.
17
+ concurrency:
18
+ group: ${{ github.workflow }}-${{ github.ref }}
19
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
20
+
21
+ jobs:
22
+ build:
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
26
+ - uses: prefix-dev/setup-pixi@ad1da6449d9e506fd127fd67ab2d054a769f6a94 # v0.11.2
27
+ with:
28
+ pixi-version: v0.84.0 # Dependabot does not bump this; see D-55
29
+ environments: docs
30
+ # --strict fails the build on warnings such as broken links.
31
+ - run: pixi run docs
32
+ env:
33
+ GITHUB_TOKEN: ${{ github.token }}
34
+ - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
35
+ with:
36
+ path: site
37
+
38
+ deploy:
39
+ if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
40
+ needs: build
41
+ runs-on: ubuntu-latest
42
+ permissions:
43
+ pages: write
44
+ id-token: write
45
+ environment:
46
+ name: github-pages
47
+ url: ${{ steps.deployment.outputs.page_url }}
48
+ steps:
49
+ - uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
50
+ id: deployment
@@ -0,0 +1,43 @@
1
+ name: Drift
2
+
3
+ # Weekly check that the live services still match the tests and the OpenAPI snapshots
4
+ # (D-63). Never on pull_request, so no fork run sees the key.
5
+ on:
6
+ schedule:
7
+ - cron: 0 7 * * 1
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ network-tests:
15
+ runs-on: ubuntu-latest
16
+ timeout-minutes: 20
17
+ steps:
18
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
19
+ with:
20
+ fetch-depth: 0 # hatch-vcs reads tags for the version
21
+ persist-credentials: false
22
+ - uses: prefix-dev/setup-pixi@ad1da6449d9e506fd127fd67ab2d054a769f6a94 # v0.11.2
23
+ with:
24
+ pixi-version: v0.84.0 # Dependabot does not bump this; see D-55
25
+ environments: test315
26
+ - run: pixi run -e test315 test-network
27
+ env:
28
+ USGS_API_KEY: ${{ secrets.USGS_API_KEY }} # a key used only here
29
+
30
+ openapi:
31
+ runs-on: ubuntu-latest
32
+ timeout-minutes: 20
33
+ steps:
34
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
35
+ with:
36
+ fetch-depth: 0
37
+ persist-credentials: false
38
+ - uses: prefix-dev/setup-pixi@ad1da6449d9e506fd127fd67ab2d054a769f6a94 # v0.11.2
39
+ with:
40
+ pixi-version: v0.84.0 # Dependabot does not bump this; see D-55
41
+ environments: test315
42
+ # Sends no key: the OpenAPI documents are public.
43
+ - run: pixi run -e test315 drift
@@ -0,0 +1,94 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ # PEP 440 final and pre-release versions only.
6
+ tags:
7
+ - v[0-9]+.[0-9]+.[0-9]+
8
+ - v[0-9]+.[0-9]+.[0-9]+rc[0-9]+
9
+ - v[0-9]+.[0-9]+.[0-9]+[ab][0-9]+
10
+
11
+ permissions: {}
12
+
13
+ jobs:
14
+ release-notes:
15
+ name: Create release notes
16
+ runs-on: ubuntu-latest
17
+ permissions:
18
+ contents: write # create the GitHub release
19
+ steps:
20
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
21
+ with:
22
+ fetch-depth: 0
23
+ persist-credentials: false
24
+ # checkout can leave the tag lightweight (actions/checkout#882), dropping the release summary.
25
+ - run: git fetch --force origin "+refs/tags/${GITHUB_REF_NAME}:refs/tags/${GITHUB_REF_NAME}"
26
+ # tag_pattern matches only vX.Y.Z, so a pre-release such as v0.1.0rc1 gets its tag message as notes.
27
+ - id: kind
28
+ run: |
29
+ if [[ "$GITHUB_REF_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
30
+ echo "prerelease=false" >> "$GITHUB_OUTPUT"
31
+ else
32
+ echo "prerelease=true" >> "$GITHUB_OUTPUT"
33
+ git tag -l --format='%(contents)' "$GITHUB_REF_NAME" > release-notes.md
34
+ fi
35
+ - uses: orhun/git-cliff-action@a9a95522b26fe6403f7bb24031f21fb573d0f5ff # v4.9.1
36
+ if: steps.kind.outputs.prerelease == 'false'
37
+ with:
38
+ version: v2.14.2 # same as the dev environment's git-cliff
39
+ config: pyproject.toml
40
+ args: --latest --strip all
41
+ env:
42
+ OUTPUT: release-notes.md
43
+ GITHUB_REPO: ${{ github.repository }}
44
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45
+ - run: gh release create "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --verify-tag --notes-file release-notes.md --prerelease="$PRERELEASE"
46
+ env:
47
+ GH_TOKEN: ${{ github.token }}
48
+ PRERELEASE: ${{ steps.kind.outputs.prerelease }}
49
+
50
+ build:
51
+ name: Build distribution
52
+ runs-on: ubuntu-latest
53
+ permissions:
54
+ contents: read
55
+ steps:
56
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
57
+ with:
58
+ fetch-depth: 0 # hatch-vcs reads tags for the version
59
+ persist-credentials: false
60
+ - uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
61
+ with:
62
+ enable-cache: false # no cache poisoning in a publishing pipeline
63
+ - run: uv build
64
+ # Each distribution installs alone, imports, and carries the tag's version.
65
+ - run: |
66
+ for dist in dist/*.whl dist/*.tar.gz; do
67
+ uv run --isolated --no-project --with "$dist" python -c \
68
+ 'import sys, water_ogcapi as w; assert w.__version__ == sys.argv[1], w.__version__' \
69
+ "${GITHUB_REF_NAME#v}"
70
+ done
71
+ - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
72
+ with:
73
+ name: dist
74
+ path: dist/
75
+
76
+ publish:
77
+ name: Publish to PyPI
78
+ needs: [build, release-notes]
79
+ runs-on: ubuntu-latest
80
+ environment:
81
+ name: pypi
82
+ url: https://pypi.org/p/water-ogcapi
83
+ permissions:
84
+ id-token: write # trusted publishing and PEP 740 attestations
85
+ steps:
86
+ - uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
87
+ with:
88
+ enable-cache: false
89
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
90
+ with:
91
+ name: dist
92
+ path: dist/
93
+ - uses: astral-sh/attest-action@39b6ce43875c196a579e31635b11aa2a2036384a # v0.0.7
94
+ - run: uv publish
@@ -0,0 +1,83 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ env/
12
+ build/
13
+ develop-eggs/
14
+ dist/
15
+ downloads/
16
+ eggs/
17
+ .eggs/
18
+ lib/
19
+ lib64/
20
+ parts/
21
+ sdist/
22
+ var/
23
+ wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+
28
+ # Unit test / coverage reports
29
+ htmlcov/
30
+ .tox/
31
+ .coverage
32
+ .coverage.*
33
+ .cache
34
+ nosetests.xml
35
+ coverage.xml
36
+ *.cover
37
+ .hypothesis/
38
+ .pytest_cache/
39
+ .pytest_tmp/
40
+
41
+ # dotenv
42
+ .env
43
+
44
+ # virtualenv
45
+ .venv
46
+ venv/
47
+ ENV/
48
+
49
+ # mypy
50
+ .mypy_cache/
51
+
52
+ # IDE settings
53
+ .vscode/
54
+ .idea/
55
+
56
+ # Pixi
57
+ .pixi
58
+ .pixi.lock
59
+
60
+ # Zensical build output
61
+ **/site/
62
+ # Pages generated from the notebooks in docs/examples/ by `pixi r docs`
63
+ /docs/examples/*.md
64
+ !/docs/examples/index.md
65
+ /docs/examples/*_files/
66
+ # Generated from the GitHub milestones by `pixi r docs`
67
+ /docs/roadmap.md
68
+
69
+ # Misc
70
+ .DS_Store
71
+ junit.xml
72
+
73
+ # AI agents. AGENTS.md and .agents/skills/ are the shared files; .claude/skills links to
74
+ # the latter. The negations override a global ignore of these directories.
75
+ !/.agents/
76
+ /.agents/*
77
+ !/.agents/skills
78
+ !/.claude/
79
+ /.claude/*
80
+ !/.claude/skills
81
+ # Claude Code skips AGENTS.md when a CLAUDE.md exists, so keep personal ones out.
82
+ CLAUDE.md
83
+ CLAUDE.local.md