Skip to content

feat: shim the consumer-side tombstone fix too, until faststream releases it - #19

Merged
aradng merged 1 commit into
fix/revert-git-dep-restore-shimfrom
feat/consumer-tombstone-shim
Jul 13, 2026
Merged

feat: shim the consumer-side tombstone fix too, until faststream releases it#19
aradng merged 1 commit into
fix/revert-git-dep-restore-shimfrom
feat/consumer-tombstone-shim

Conversation

@aradng

@aradng aradng commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Stacked on #18 - based on the reverted/working state, not the broken git-dependency one.

ag2ai/faststream#2933 (Optional[Model] = None should resolve correctly for a real tombstone instead of crash-looping on required fields) is open upstream, unmerged, no PyPI release either way. The producer side already gets this treatment via _patch_real_tombstones (0.4.48) - this does the same for the consumer side, so both halves actually work today instead of only the producer.

Three patches, each guarded for idempotency:

  • AsyncConfluentParser.parse_message: tags the body with a dedicated TOMBSTONE sentinel (not a byte pattern like b"null"/b"" - can't collide with real message content) when the raw value is a genuine null.
  • AsyncConfluentParser.decode_message: maps TOMBSTONE to None.
  • faststream._internal.fastapi.route.build_faststream_to_fastapi_parser: a full replacement, since it's a per-subscriber closure factory rather than a patchable class method. Passes a real None through instead of wrapping it as {param_name: None} - that wrapping is exactly what defeats FastAPI's own no-body shortcut (the same shortcut that already correctly resolves Optional[Model] = None for a bodyless HTTP request) and forces field-level validation on every tombstone instead.

Bumps to 0.4.51.

Test plan

  • New test in tests/kafka/test_tombstone.py: Optional[Model] = None resolves to None for a real tombstone against a real broker, valid payloads still parse correctly.
  • Confirmed the test fails without the shim (assert b'' is None-style failure), passes with it.
  • Full tests/kafka/ suite passes (19/19).
  • ruff + mypy clean.

…ases it

ag2ai/faststream#2933 (Optional[Model] = None resolving correctly for a
real tombstone instead of crash-looping on required fields) is open,
unmerged, no PyPI release. Producer-side already gets the equivalent
treatment via _patch_real_tombstones - do the same here so both halves
work today, not just once faststream ships something.

Three patches, each guarded for idempotency:
- AsyncConfluentParser.parse_message: tags the body with a dedicated
  TOMBSTONE sentinel (not a byte pattern - can't collide with real
  content) when the raw value is a genuine null.
- AsyncConfluentParser.decode_message: maps TOMBSTONE to None.
- faststream._internal.fastapi.route.build_faststream_to_fastapi_parser:
  a full replacement (it's a per-subscriber closure factory, not a
  patchable class method) that passes a real None through instead of
  wrapping it as {param_name: None} - that wrapping is what defeats
  FastAPI's own no-body shortcut and forces field-level validation on
  every tombstone.

Bumps to 0.4.51.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@aradng
aradng merged commit bdafb36 into fix/revert-git-dep-restore-shim Jul 13, 2026
3 checks passed
aradng added a commit that referenced this pull request Jul 13, 2026
…ases it (#19)

ag2ai/faststream#2933 (Optional[Model] = None resolving correctly for a
real tombstone instead of crash-looping on required fields) is open,
unmerged, no PyPI release. Producer-side already gets the equivalent
treatment via _patch_real_tombstones - do the same here so both halves
work today, not just once faststream ships something.

Three patches, each guarded for idempotency:
- AsyncConfluentParser.parse_message: tags the body with a dedicated
  TOMBSTONE sentinel (not a byte pattern - can't collide with real
  content) when the raw value is a genuine null.
- AsyncConfluentParser.decode_message: maps TOMBSTONE to None.
- faststream._internal.fastapi.route.build_faststream_to_fastapi_parser:
  a full replacement (it's a per-subscriber closure factory, not a
  patchable class method) that passes a real None through instead of
  wrapping it as {param_name: None} - that wrapping is what defeats
  FastAPI's own no-body shortcut and forces field-level validation on
  every tombstone.

Bumps to 0.4.51.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
aradng added a commit that referenced this pull request Jul 13, 2026
…ases it (#19) (#20)

ag2ai/faststream#2933 (Optional[Model] = None resolving correctly for a
real tombstone instead of crash-looping on required fields) is open,
unmerged, no PyPI release. Producer-side already gets the equivalent
treatment via _patch_real_tombstones - do the same here so both halves
work today, not just once faststream ships something.

Three patches, each guarded for idempotency:
- AsyncConfluentParser.parse_message: tags the body with a dedicated
  TOMBSTONE sentinel (not a byte pattern - can't collide with real
  content) when the raw value is a genuine null.
- AsyncConfluentParser.decode_message: maps TOMBSTONE to None.
- faststream._internal.fastapi.route.build_faststream_to_fastapi_parser:
  a full replacement (it's a per-subscriber closure factory, not a
  patchable class method) that passes a real None through instead of
  wrapping it as {param_name: None} - that wrapping is what defeats
  FastAPI's own no-body shortcut and forces field-level validation on
  every tombstone.

Bumps to 0.4.51.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
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.

1 participant