Skip to content

Add Map passthrough default: leave unmapped values untouched#214

Merged
frenck merged 1 commit into
mainfrom
frenck/map-passthrough
Jul 9, 2026
Merged

Add Map passthrough default: leave unmapped values untouched#214
frenck merged 1 commit into
mainfrom
frenck/map-passthrough

Conversation

@frenck

@frenck frenck commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Breaking change

None. PASSTHROUGH is a new opt-in default= value for Map; existing Map calls behave exactly as before.

Proposed change

Map today either rejects a key it does not know, or (with default=) folds every miss to one fixed value. Neither fits a table that exists only to rewrite a few sentinel values ahead of a stricter step, where unlisted values should flow through unchanged.

This adds a PASSTHROUGH sentinel. Map(..., default=PASSTHROUGH) returns the value as-is on a miss, so a table rewrites only the keys it names. That is the difference between "unknown means this value" and "only touch what I named", which is what a scraped source of sentinel strings ("N.v.t.", "Niet geregistreerd") wants ahead of an enum or a coercer.

from probatio import Schema, Map, PASSTHROUGH

sentinels = Map({"N.v.t.": None, "n/a": None}, default=PASSTHROUGH)
Schema(sentinels)("N.v.t.")        # None
Schema(sentinels)("Personenauto")  # 'Personenauto'  (unmapped, left as-is)

The sentinel lives in the same default= slot as UNDEFINED and is exported alongside it. Documented in the validators guide and the device and sensor conversions cookbook recipe, the latter showing the enum-sentinel case as All(Map(..., default=PASSTHROUGH), Maybe(enum)), with a note that Map must run before a producing enum base.

This came out of a real migration (an RDW client moving off mashumaro): a scraped government dataset sends sentinel strings for missing enum values, and there was no clean per-table way to null just those ahead of enum coercion.

Type of change

  • Dependency or tooling upgrade
  • Bugfix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Deprecation (replaces or removes a feature, with a migration path)
  • Breaking change (a fix or feature that changes existing behavior)
  • Code quality, refactor, or test-only change
  • Documentation only

Additional information

  • This PR fixes or closes issue: fixes #
  • This PR is related to:
  • Link to a separate documentation pull request:

Checklist

  • I fully understand the code in this pull request and can explain every line, including any AI-assisted changes.
  • The change is covered by tests, and uv run --no-sync just test passes locally. A pull request cannot be merged unless CI is green.
  • uv run --no-sync just lint passes.
  • uv run --no-sync just typecheck passes.
  • No commented-out or dead code is left in the pull request.

Map rejects a key it does not know, or folds every miss to a fixed
default. Neither covers the common case of a table that exists only to
rewrite a few sentinel values ahead of a stricter step, where unlisted
values should flow through unchanged.

Add a PASSTHROUGH sentinel (in the same default= slot as UNDEFINED, and
exported alongside it). Map(..., default=PASSTHROUGH) returns the value
as-is on a miss, so a table rewrites only the keys it names. This is the
difference between "unknown means this value" and "only touch what I
named", which is what a scraped source of sentinel strings wants ahead
of an enum or coercer.

Document it in the validators guide and the device and sensor
conversions cookbook recipe, including the enum-sentinel case.
Copilot AI review requested due to automatic review settings July 9, 2026 09:08
@frenck frenck added the new-feature New features or options. label Jul 9, 2026
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a PASSTHROUGH sentinel usable as Map's default parameter, causing unmapped (including unhashable) misses to return the input value unchanged instead of being replaced or rejected. Exports the sentinel from probatio and probatio.validators, updates the public surface snapshot, adds tests, and documents usage in guides and cookbook.

Changes

PASSTHROUGH sentinel

Layer / File(s) Summary
PASSTHROUGH sentinel implementation in Map
src/probatio/validators/coerce.py
Adds _Passthrough class and PASSTHROUGH constant; Map.__call__ returns the original value unchanged on a miss when default is PASSTHROUGH, and the docstring is updated accordingly.
Public re-exports of PASSTHROUGH
src/probatio/validators/__init__.py, src/probatio/__init__.py, tests/__snapshots__/test_public_surface.ambr
Adds PASSTHROUGH to import lists and __all__ in both modules, and records it as a value-kind export in the public surface snapshot.
Tests and documentation for PASSTHROUGH
tests/validators/test_coerce.py, docs/src/content/docs/guides/validators.md, docs/src/content/docs/recipes/cookbook.md
Adds tests for hashable/unhashable miss behavior and repr; documents default=PASSTHROUGH usage in the validators guide and adds a cookbook example rewriting sentinel strings before enum validation.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • frenck/probatio#201: Both PRs modify Map's miss-handling logic in src/probatio/validators/coerce.py, with this PR extending that logic with the PASSTHROUGH sentinel.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding a passthrough default for Map to leave unmapped values unchanged.
Description check ✅ Passed The description is directly related to the changeset and accurately explains the new PASSTHROUGH Map behavior and its intended use.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch frenck/map-passthrough

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.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
src/probatio/validators/coerce.py (1)

227-235: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: add __slots__ = () to _Passthrough for a tighter sentinel.

This prevents accidental attribute mutation on the singleton instance and signals intent that the class is a fixed sentinel with no instance state.

♻️ Optional refactor
 class _Passthrough:
     """Type of the ``PASSTHROUGH`` sentinel: a ``Map`` miss returns the value unchanged."""
 
+    __slots__ = ()
+
     def __repr__(self) -> str:
         """Render as ``PASSTHROUGH`` so it reads clearly in debug output."""
         return "PASSTHROUGH"

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 94262329-42bf-4f65-93f9-8894071accaf

📥 Commits

Reviewing files that changed from the base of the PR and between bae22ce and 6526404.

📒 Files selected for processing (7)
  • docs/src/content/docs/guides/validators.md
  • docs/src/content/docs/recipes/cookbook.md
  • src/probatio/__init__.py
  • src/probatio/validators/__init__.py
  • src/probatio/validators/coerce.py
  • tests/__snapshots__/test_public_surface.ambr
  • tests/validators/test_coerce.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
src/probatio/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/probatio/**/*.py: Keep the public API aligned with voluptuous, and do not rename or re-signature public names without a documented reason.
Every public function and class must have a docstring.
Do not copy code from voluptuous; implement behavior cleanly and independently while matching behavior rather than source.

Files:

  • src/probatio/validators/__init__.py
  • src/probatio/validators/coerce.py
  • src/probatio/__init__.py
{src/probatio,tests}/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Match the surrounding code style and prefer small, well-named functions.

Files:

  • src/probatio/validators/__init__.py
  • src/probatio/validators/coerce.py
  • src/probatio/__init__.py
  • tests/validators/test_coerce.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Every test must have a one-line docstring describing what it verifies.

Files:

  • tests/validators/test_coerce.py
🔇 Additional comments (8)
src/probatio/validators/coerce.py (1)

227-235: LGTM!

Also applies to: 238-253, 280-284

src/probatio/validators/__init__.py (1)

10-10: LGTM!

Also applies to: 170-170

src/probatio/__init__.py (1)

126-126: LGTM!

Also applies to: 330-330

tests/__snapshots__/test_public_surface.ambr (1)

1218-1220: LGTM!

tests/validators/test_coerce.py (1)

12-12: LGTM!

Also applies to: 270-286

docs/src/content/docs/guides/validators.md (1)

697-710: LGTM!

docs/src/content/docs/recipes/cookbook.md (2)

143-167: LGTM! The All ordering explanation and the Map + Maybe(VehicleType) interaction are correct: Map rewrites known sentinels to None, passes unknown values through, and Maybe allows None while coercing real values via the enum.


150-150: 🎯 Functional Correctness

No change needed: StrEnum is supported. The project already targets Python 3.12+, so this example is safe as written.

			> Likely an incorrect or invalid review comment.

@codspeed-hq

codspeed-hq Bot commented Jul 9, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 23 untouched benchmarks


Comparing frenck/map-passthrough (6526404) with main (bae22ce)

Open in CodSpeed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a PASSTHROUGH sentinel to the probatio validation library, giving Map a third miss-handling mode. Previously a Map miss was either rejected or folded to one fixed default= value; Map(..., default=PASSTHROUGH) now returns an unmapped value unchanged, so a table rewrites only the keys it names. This fits the real use case of cleaning up a few sentinel strings (like "N.v.t.") ahead of a stricter enum or coercer step. The sentinel lives in the same default= slot as UNDEFINED and is exported at the top level.

Changes:

  • Add the _Passthrough type and PASSTHROUGH singleton, and handle it in Map.__call__ before the fixed-default and rejection paths.
  • Export PASSTHROUGH from probatio and probatio.validators, and record it in the public-surface snapshot.
  • Document the new mode in the validators guide and cookbook, and add tests for hit, unhashable miss, and repr.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/probatio/validators/coerce.py Adds the _Passthrough/PASSTHROUGH sentinel and the passthrough branch in Map.__call__; updates the Map docstring.
src/probatio/validators/__init__.py Re-exports PASSTHROUGH from coerce and adds it to __all__.
src/probatio/__init__.py Exposes PASSTHROUGH at the top level and in __all__.
tests/validators/test_coerce.py Adds tests for passthrough on a normal miss, an unhashable miss, and the sentinel repr.
tests/__snapshots__/test_public_surface.ambr Records PASSTHROUGH as a public value in the surface snapshot.
docs/src/content/docs/guides/validators.md Documents default=PASSTHROUGH with a runnable example.
docs/src/content/docs/recipes/cookbook.md Adds the enum-sentinel recipe using All(Map(..., default=PASSTHROUGH), Maybe(enum)).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@frenck frenck merged commit cbe3927 into main Jul 9, 2026
27 of 28 checks passed
@frenck frenck deleted the frenck/map-passthrough branch July 9, 2026 09:30
@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Jul 10, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

new-feature New features or options.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants