Skip to content

feat(filesystem): add #key=value reader-hint channel to source URIs#183

Merged
amotl merged 1 commit into
panodata:mainfrom
hampsterx:feat/filesystem-named-hints
Jul 7, 2026
Merged

feat(filesystem): add #key=value reader-hint channel to source URIs#183
amotl merged 1 commit into
panodata:mainfrom
hampsterx:feat/filesystem-named-hints

Conversation

@hampsterx

Copy link
Copy Markdown
Contributor

Summary

  • Adds a generic per-URI reader-hint channel to the filesystem sources: reader options travel in the URI fragment (file://book.xlsx#sheet=quotes) instead of overloading --source-table, which Blob XLSX sources: --source-table value reused as both object path and sheet_name #148 flagged as ambiguous for XLSX. A format hint and named hints coexist in one fragment (feed.dat#csv&sheet=quotes).
  • Retires the unused FilesystemReference.page field in favour of a free-form hints: dict[str, str], as specced on Filesystem: Named hint parameters for URIs #149. page was set by all four sources but read nowhere, so this is a clean swap with no consumer to migrate.
  • New router.parse_fragment() centralizes fragment parsing for every scheme: a bare known-format token stays the format hint; #key=value pairs are parsed with parse_qsl (percent-decoded, =-partitioned, blank values kept, duplicate keys last-wins). It is all-or-nothing: a fragment with any segment that is neither a key=value pair nor a single known format is treated as a literal path, so a real # in a path (/feeds/vendor#1/data.csv) keeps working; percent-encode a # as %23 to force a fragment-looking tail literal.
  • split_format_hint() is now a thin (path, format_hint) wrapper over parse_fragment, so determine_endpoint and every existing caller keep their contract unchanged.
  • Threads hints through the local, GCS, S3 and SFTP sources. blob_hints() mirrors parse_uri's file-selection precedence so hints always track the file actually loaded, across both the --source-table form and the deprecated s3://bucket/path#frag URI-path form.

Scope: source-only, and no built-in reader consumes hints yet, so this lands the channel and the page to hints migration with zero read-behavior change. The file:// destination parser is untouched. Wiring a hint into a reader is a follow-up.

Fragment, not query params: query params already carry credentials/connection options (?access_key_id=...); putting reader hints there risks key collisions and leaking credential params into reader kwargs. The example in #149 uses the fragment.

Coordination with #123: this can merge independently (channel + migration, no consumer). When the XLSX source (#123) lands, it can read hints["sheet"] for sheet selection with no second migration. Happy to align on the exact key name (sheet vs page) on either PR.

Test plan

  • parse_fragment unit tests across the full grammar (no hint, bare format, single/multiple named hints, format+named coexist, empty value, = in value, percent-decode, duplicate last-wins, mixed valid/invalid to literal, duplicate format to literal, literal #, %23 escape)
  • Source-level unit tests asserting the constructed FilesystemReference.hints for all four sources (local/GCS/S3/SFTP), plus the deprecated URI-path form and the contradictory both-forms case
  • Existing router tests (split_format_hint, literal-#-in-path) unchanged and green
  • Full non-Docker unit lane green (531 passed, 44 skipped)
  • Warehouse S3/GCS matrix green across postgres/duckdb/clickhouse (73 passed)
  • ruff + validate-pyproject + ty clean
  • Docs build + linkcheck pass with -W; grammar documented on the source pages

Closes #149

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (1)
  • coderabbit-review

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 27a84431-8f78-4b94-a241-1a341afe836d

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@read-the-docs-community

read-the-docs-community Bot commented Jul 6, 2026

Copy link
Copy Markdown

Documentation build overview

📚 omniload | 🛠️ Build #33470515 | 📁 Comparing 4146c81 against latest (eab8b72)

  🔍 Preview build  

2 files changed
± changelog.html
± supported-sources/file.html

@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 53.17%. Comparing base (eab8b72) to head (4146c81).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #183      +/-   ##
==========================================
+ Coverage   52.84%   53.17%   +0.32%     
==========================================
  Files         202      202              
  Lines        9323     9347      +24     
==========================================
+ Hits         4927     4970      +43     
+ Misses       4396     4377      -19     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread docs/supported-sources/file.md
Comment on lines +136 to +148
The named-hint grammar:

- Values are percent-decoded, so `#sheet=My%20Sheet` gives the value `My Sheet`
and `#sheet=R%26D` gives `R&D`.
- Only the first `=` splits key from value, so a value may itself contain `=`
(`#range=A1=B2` gives `range` = `A1=B2`).
- An empty value is preserved (`#sheet=` gives `sheet` = `""`); a reader decides
whether that means "unset".
- Duplicate keys take the last value (`#sheet=a&sheet=b` gives `b`).
- If any segment of the fragment is neither a `key=value` pair nor a single
known format, the whole `#...` is treated as a literal part of the path, so a
real `#` in a filename keeps working. Percent-encode a literal `#` as `%23`
when a trailing `path#key=value` would otherwise be read as a fragment.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Excellent!

Comment thread docs/supported-sources/google-cloud-storage.md Outdated
Comment thread docs/supported-sources/s3.md Outdated
Comment thread docs/supported-sources/sftp.md Outdated
@amotl amotl force-pushed the feat/filesystem-named-hints branch from 883847b to 4146c81 Compare July 7, 2026 01:38
Give every filesystem source a uniform, self-documenting reader-option
channel carried in the URI fragment (file://book.xlsx#sheet=quotes)
instead of overloading --source-table. A format hint and named hints
coexist in one fragment (feed.dat#csv&sheet=quotes).

- router.parse_fragment() splits the trailing #fragment into
  (path, format_hint, hints): a known-format token stays the format
  hint, key=value pairs (parse_qsl: percent-decoded, =-partitioned,
  blank values kept, duplicate keys last-wins) become hints. It is
  all-or-nothing: a fragment with any segment that is neither a pair
  nor a single known format is left literal, so a real # in a path
  (/feeds/vendor#1/data.csv) keeps working; percent-encode a # as %23
  to force a fragment-looking tail literal.
- split_format_hint() becomes a thin (path, format_hint) wrapper over
  parse_fragment, so determine_endpoint and every existing caller keep
  their contract unchanged.
- FilesystemReference.page (previously set but read nowhere) is replaced
  by hints: dict[str, str], threaded through the local, GCS, S3 and SFTP
  sources. blob_hints() mirrors parse_uri's carrier choice so hints track
  the file actually loaded across both the --source-table and the
  deprecated URI-path forms.

No built-in reader consumes hints yet: this lands the channel and the
page->hints migration with zero read-behavior change. Source-only; the
file:// destination parser is untouched.

Closes panodata#149
@amotl amotl force-pushed the feat/filesystem-named-hints branch from 4146c81 to 17b4e3b Compare July 7, 2026 01:49
@amotl amotl merged commit 83db2ab into panodata:main Jul 7, 2026
8 of 11 checks passed
@hampsterx hampsterx mentioned this pull request Jul 9, 2026
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.

Filesystem: Named hint parameters for URIs

2 participants