Skip to content

Follow-ups: variant provenance, mypy 0 + py.typed, KuboCAS knobs, httpx consolidation (+ upstream review-commit merge) - #15

Merged
TheGreatAlgo merged 12 commits into
fix/review-findingsfrom
fix/review-followups
Jul 21, 2026
Merged

Follow-ups: variant provenance, mypy 0 + py.typed, KuboCAS knobs, httpx consolidation (+ upstream review-commit merge)#15
TheGreatAlgo merged 12 commits into
fix/review-findingsfrom
fix/review-followups

Conversation

@Faolain

@Faolain Faolain commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #14 (base: fix/review-findings). This PR implements the five follow-ups documented at the end of #14 through the same TDD pipeline — red/green commit pairs, each package dual-reviewed (Grok + adversarial Claude) with reviewer findings landed as remediation commits — and merges the maintainer review commits pushed to #14 (db7444b..179dd78) with full semantic reconciliation onto this line.

Results: 232 tests pass (up from 190 pre-merge, 166 at the base of #14), mypy 0 errors and py.typed shipped (verified inside the built wheel and end-to-end from a consumer venv), requests/urllib3 fully removed, version bumped to 0.6.0 for the one deliberate breaking change. Final adversarial gate executed every attack live: zero blockers, zero minors.

1. Variant provenance + pagination preference (FU-A)

Fix What was wrong → what changed
43e962d + 342d684 load_dataset metadata reported the caller's variant argument, not what the resolver's default > final > finalized > latest cascade actually picked — a silent provenance error whose slug could name a nonexistent item. Breaking change: both resolvers now return ResolvedDataset(cid, variant); metadata/slug reflect the real selection; direct-CID paths report "unknown" consistently.
96cba63 Review follow-ups: 0.6.0 bump (makes the "Changed in 0.6" notes true), mutation-testing showed the cascade order and first-match fallback were unpinned — both now tested (fallback reports the item's own variant, never a fabricated "default"); variant="" documented+pinned as explicit.

2. mypy 40 → 0, py.typed shipped (FU-B)

Fix What was wrong → what changed
a6f5483 + 2a4986c 40 mypy errors (23 implicit-Optional, narrowing issues in load_dataset, siren typing); latent crash: gateway_base_url=None hit None.rstrip in the catalog fallback (now uses the default-gateway constant); query() kwargs with missing keys died deep in numpy (now InvalidSelectionError naming the key). py.typed shipped; PEP 561 promise verified by installing the wheel in a throwaway venv and type-checking a consumer script.
1432333 Adversarial follow-ups: the typing pass had hidden (not fixed) a dead code path — IPFSStacIO.read_text's "HTTP fallback" called an abstract super and always raised; it now actually fetches http(s) URLs and rejects unknown schemes. Value-aware kwargs validation (lat=None no longer reaches numpy); mypy meta-test pins the checked-file count so a future exclude can't game it.

3. KuboCAS knobs + h2 benchmark (FU-C)

Fix What was wrong → what changed
c959147 + aae0e6b dClimateClient exposed none of KuboCAS's connection knobs — authenticated gateways, tuned concurrency, and h2-on/off comparisons required bypassing the client entirely. Now forwards concurrency, headers, auth, max_retries, initial_delay, backoff_factor, client_factory (only when set, is not None-gated — explicit max_retries=0 survives; the adversarial reviewer replayed every recorded kwargs set against the real KuboCAS.__init__). scripts/benchmark_gateway.py implements the py-hamt #58 methodology and documents the companion nginx keepalive_requests infra change.
1432333 Eager ValueError for client_factory + headers/auth (KuboCAS rejected it only at context entry); benchmark: one-line failures, follow_redirects=True, JSON-only stdout.

4. requests → httpx consolidation (FU-D)

Fix What was wrong → what changed
de94db8 + 3fd4213 The package shipped two HTTP stacks. All package HTTP is now httpx: pooled sync clients for STAC paths (pystac's StacIO is a synchronous interface), fallback guards catch httpx.HTTPError, the connection-error classifier drops the requests/urllib3 families, and nine transport-pinned test files moved to httpx.MockTransport seams with every invariant preserved (assertion counts audited pre/post — none lost). requests and urllib3 left [project] dependencies.

5. Upstream merge (a3fb3ca)

The maintainer commits on #14 were written against the requests-era code; this merge preserves both lines:

  • Upstream intent kept intact (verified hunk-by-hunk by the final adversarial gate): known_datasets longest-match parsing for hyphenated ids, collect-all-pages resolve, per-catalog stac_io binding + weakref.finalize client lifecycle (replaces the process-global StacIO.set_default race), STAC_CATALOG_URL pointer/data-gateway separation, next-link headers (including the header-aware pagination loop guard), __aexit__ body-cancellation precedence, polygons() CRS-units fix, points() empty/non-finite guards, to_netcdf memoryview→bytes, decode_timedelta=True.
  • Translated to httpx where upstream assumed requests (headers-aware _search_pages, get_root_catalog_cid(catalog_url), conftest probes); upstream's urllib3 MaxRetryError.reason refinement dropped as structurally moot on httpx (HTTPStatusError is not a TransportError, so exhausted status-retries already take the HAMT fallback — verified against installed py-hamt's retry re-raise surface).
  • Upstream's new tests ported from requests monkeypatching to the MockTransport seams; the "default on page 1 stops paging" optimization is superseded by upstream's authoritative full walk (call-count pin updated accordingly).

Compatibility notes

  • 0.6.0 (breaking): resolve_cid_from_stac_server / resolve_dataset_cid_from_stac return ResolvedDataset instead of str; variant="" is now an explicit (unresolvable) variant rather than no-variant.
  • Exceptions from STAC paths are now httpx types (httpx.HTTPError family) — callers catching requests.RequestException must update.
  • metadata["variant"] now reports the actually-selected variant; consumers keying caches on the old always-"default" value will see the true names.

How this was verified

  • Every behavior change landed as a red commit (failing test) + green commit; reviewers re-verified reds were genuinely red and never weakened (assertion-count audits on all migrated test files).
  • Each package dual-reviewed; findings drove remediation commits (mutation-testing gaps, the dead read_text fallback, eager XOR validation, meta-test hardening).
  • Final adversarial gate at HEAD executed every priority attack: exception-surface parity through a real pystac catalog over MockTransport (ConnectError/500/malformed-JSON all take the catalog fallback), known-datasets reachability through both resolvers, variant-provenance cascade, per-catalog client lifecycle incl. GC and failure paths, classifier semantics against py-hamt's actual raise surface. Verdict: MERGE, no blockers/minors.
  • Gates at HEAD: pytest 232 passed / 45 skipped, mypy 0 errors (16 files), ruff check + format --check clean, lazy-import smoke intact.

🤖 Generated with Claude Code

Faolain and others added 11 commits July 18, 2026 03:50
…data and page-local preference

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ne crash, and kwargs validation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… the variant actually selected

resolve_cid_from_stac_server and resolve_dataset_cid_from_stac now
return a ResolvedDataset NamedTuple instead of a bare CID string
(breaking change, noted in docstrings), so load_dataset metadata and
slugs reflect the preference cascade's actual pick instead of claiming
'default'; the direct-CID path reports 'unknown' consistently in both
slug and metadata. The no-variant pagination path now stops early only
on an effective-default match, so a 'latest' item on page 1 no longer
beats a 'default' item on page 2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… crash; validate geo kwargs

- implicit-Optional defaults corrected across client.py and
  geotemporal_data.py; load_dataset narrowing restructured so the
  checker proves collection/final_cid are str; siren typing fixes
- dClimateClient with gateway_base_url=None no longer crashes the
  STAC-catalog fallback (AttributeError on None.rstrip); the default
  public gateway constant is used
- GeotemporalData.query validates required point/circle/rectangle kwargs
  and raises InvalidSelectionError naming missing keys instead of
  failing deep inside xarray
- mypy pinned in the testing extra with [tool.mypy] config; a meta-test
  gates it; py.typed shipped and verified present in the built wheel

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ipt presence

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…empty-variant edge, ResolvedDataset polish)

Per grok + adversarial review: version bumped to 0.6.0 so the 'Changed
in 0.6' notes on the breaking ResolvedDataset return are true; mutation
testing showed the default>final cascade order and the first-match
fallback were unpinned — both now have tests (fallback must report the
item's actual variant, never a fabricated 'default'); variant='' is
documented and pinned as an explicit variant; pagination tests pin
resolved.variant; isinstance replaces hasattr; NamedTuple import style.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…way h2 benchmark script

dClimateClient now accepts concurrency, headers, auth, max_retries,
initial_delay, backoff_factor and client_factory, forwarded to KuboCAS
only when set so py-hamt's defaults stay authoritative — unblocking
authenticated gateways, tuned pools, and h2-on/off comparisons.
scripts/benchmark_gateway.py implements the py-hamt #58 methodology
(median wall time, --http2/--no-http2, --concurrency, --repetitions)
and documents the companion infra change: raising nginx
keepalive_requests on the gateway to stop routine GOAWAY churn.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ncy list, pooled client) + fallback non-regression pin

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…llib3

stac_server and stac_catalog use pooled sync httpx.Clients (IPFSStacIO
keeps a sync client because pystac's StacIO interface is synchronous);
fallback guards in dclimate_client catch httpx.HTTPError; the
connection-error classifier drops the requests/urllib3 families; the
nine transport-pinned test files migrate to an httpx.MockTransport seam
with every invariant preserved (request bodies, pagination sequences,
single-pooled-client reuse, the event-loop heartbeat bound); requests
and urllib3 leave [project] dependencies (still present transitively).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ttpx/ResolvedDataset line

Reconciliation decisions:
- upstream's requests-era additions translated to httpx: headers-aware
  _search_pages (pooled module client), get_root_catalog_cid(catalog_url),
  conftest RPC/pointer probes
- upstream's known_datasets longest-match parsing and collect-all-pages
  resolve adopted wholesale, fused with ResolvedDataset returns and
  _effective_variant selection; the 'default on page 1 stops paging'
  optimization is superseded by upstream's authoritative full walk
  (call-count pin updated 1 -> 2 accordingly)
- upstream's per-catalog stac_io binding + weakref close lifecycle
  adopted; IPFSStacIO now owns a per-instance httpx.Client (closable)
  while one-shot pointer reads keep the module-pooled client
- upstream's urllib3 MaxRetryError refinements dropped as moot on the
  httpx stack (HTTPStatusError already classifies non-connection)
- upstream's new tests ported from requests monkeypatching to the httpx
  MockTransport seams with all assertions preserved

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- IPFSStacIO.read_text implements the http(s) fallback for real (the old
  super().read_text was abstract and raised NotImplementedError behind a
  type-ignore); unsupported schemes raise a clear ValueError
- query() kwargs validation is value-aware (required keys must be
  non-None) and consistent: empty polygon/multiple_points kwargs now
  raise like the other selectors
- dClimateClient: eager ValueError for client_factory combined with
  headers/auth (KuboCAS rejected it only at context entry); gateway-None
  split semantics documented and the fallback ternary hoisted; auth
  forwarding pinned in tests
- mypy meta-test runs the project-pinned mypy and asserts the checked
  file count; py.typed presence pinned inside a built wheel
- benchmark: one-line failures to stderr, follow_redirects=True factory,
  JSON-only stdout
- probe redirect parity, managed mock clients in test helpers, stale
  requests.HTTPError reference in DATASET_CATALOG_USAGE.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 593bbf8b-f191-44c8-9319-4112515ec508

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/review-followups

Comment @coderabbitai help to get the list of available commands.

@Faolain
Faolain requested review from 0xSwego and TheGreatAlgo July 18, 2026 18:03

@da-code-reviewer da-code-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex Automated Review

Found 2 actionable reliability issues.
Posted 2 inline comment(s).

max_retries: typing.Optional[int] = None,
initial_delay: typing.Optional[float] = None,
backoff_factor: typing.Optional[float] = None,
client_factory: typing.Optional[typing.Callable[[], httpx.AsyncClient]] = None,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIUM
client_factory exists only in the unreleased py-hamt commit selected by [tool.uv.sources]; the published py-hamt==3.4.1 constructor does not accept it, while the runtime dependency still permits that release. A normal wheel install using this option therefore raises TypeError in __aenter__. Require a published py-hamt release containing this API or defer exposing the option.

self._stac_catalog = await asyncio.to_thread(
load_stac_catalog,
gateway_url=self._gateway_base_url,
gateway_url=self._catalog_gateway_base_url,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIUM
The catalog fallback forwards only the gateway URL. headers and auth go exclusively to KuboCAS, while IPFSStacIO creates a credential-free client, so authenticated gateways fail with 401 whenever STAC-server lookup is unavailable or disabled (including catalog-backed listing). Thread gateway credentials into catalog I/O and apply them to gateway requests.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 14323331dd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

"max_retries": self._max_retries,
"initial_delay": self._initial_delay,
"backoff_factor": self._backoff_factor,
"client_factory": self._client_factory,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require a py-hamt release before forwarding client_factory

When this package is installed from a built wheel/PyPI, [tool.uv.sources] is ignored and the declared runtime dependency remains py_hamt>=3.4.1; the published v3.4.1 KuboCAS.__init__ accepts client=, headers/auth/retry knobs, etc., but not client_factory. Any user following the new client_factory API (and the new benchmark script does this) will therefore hit TypeError: KuboCAS.__init__() got an unexpected keyword argument 'client_factory' on async with dClimateClient(...). Please either raise the runtime floor to a released py-hamt version that includes this parameter or avoid forwarding this kwarg until that release exists.

Useful? React with 👍 / 👎.

@TheGreatAlgo
TheGreatAlgo merged commit e3d2cae into fix/review-findings Jul 21, 2026
2 checks passed

@da-code-reviewer da-code-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex Automated Review

Found two credential-disclosure paths and one stale dependency lock.
Posted 3 inline comment(s).

"""
if root_cid is None:
root_cid = get_root_catalog_cid(catalog_url)
root_cid = get_root_catalog_cid(catalog_url, headers=headers, auth=auth)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HIGH
Gateway headers/auth are forwarded to catalog_url, but dClimateClient leaves that URL at the fixed public STAC_CATALOG_URL. Configuring a custom authenticated gateway therefore sends its credentials to a different host. Only forward credentials when origins match, or expose separate pointer credentials.


scheme = urlsplit(source_text).scheme.lower()
if scheme in {"http", "https"}:
response = self.client.get(source_text)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HIGH
This client was constructed with private gateway headers/basic auth, but it is reused for arbitrary absolute HTTP(S) links supplied by the catalog. Cross-origin links therefore receive gateway credentials, potentially even over HTTP. Use an uncredentialed client for external links or restrict credentialed requests to the gateway origin.

Comment thread pyproject.toml
"numcodecs[crc32c]>=0.14,<0.16",
"numpy>=2.1.3",
"py_hamt>=3.4.1",
"py_hamt>=3.5.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIUM
uv.lock still resolves py-hamt 3.4.1 from git revision 942580d… and retains that source in root metadata, while this line requires registry version >=3.5.0 and removes the source override. Locked syncs will fail freshness checks, while frozen syncs install the old integration build. Regenerate and commit the lockfile.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants