Skip to content

Commit 75cc013

Browse files
committed
docs(codec): replace examples with Schema Registry codec
1 parent 3e0d1d1 commit 75cc013

16 files changed

Lines changed: 213 additions & 286 deletions

File tree

docs/batch_codec_testing_gap.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Batch Codec Testing Gap
2+
3+
## Problem
4+
5+
`BatchCodecTestcase` tests the codec in isolation (protocol conformance, `isinstance` dispatch, fallback). It cannot test the full publish→subscribe round trip like `CodecTestcase` does for single messages.
6+
7+
The FakeProducer's `publish_batch` path fails when a custom codec is set because the subscriber pipeline calls `decode_message()` on a `StreamMessage` with `body=list[bytes]`, and `decode_message()` expects `bytes`, not a list.
8+
9+
## Root Cause
10+
11+
Three things are wrong in the batch path:
12+
13+
### 1. `_get_parser_and_decoder()` is not batch-aware
14+
15+
When `codec=` is set, the subscriber wiring always uses `codec.decode` as the decoder — even for batch subscribers. Batch subscribers need `codec.decode_batch` (if the codec implements `BatchCodecProto`).
16+
17+
```python
18+
# Current (usecase.py _get_parser_and_decoder):
19+
if codec:
20+
async_decoder = codec.decode # always single-message decoder
21+
22+
# Needed:
23+
if codec and is_batch_subscriber and isinstance(codec, BatchCodecProto):
24+
async_decoder = codec.decode_batch
25+
elif codec:
26+
async_decoder = codec.decode
27+
```
28+
29+
This requires `_get_parser_and_decoder` to know whether the current subscriber is a batch subscriber. Currently it doesn't — it only sees parser/decoder/codec options, not the subscriber type.
30+
31+
### 2. FakeProducer `publish_batch` builds StreamMessages eagerly
32+
33+
```python
34+
# Current (kafka/testing.py FakeProducer.publish_batch):
35+
messages = [await build_message(m, ...) for m in cmd.batch_bodies]
36+
await self._execute_handler(list(messages), topic, handler)
37+
```
38+
39+
It builds individual `ConsumerRecord`s via `build_message()`, then passes the list to the handler. The batch parser (`AioKafkaBatchParser.parse_batch`) expects raw `ConsumerRecord`s, not pre-parsed messages. The FakeProducer should pass raw records and let the batch parser handle the batching.
40+
41+
### 3. `call_item.is_suitable()` always sets single decoder
42+
43+
```python
44+
# Current (call_item.py):
45+
message.set_decoder(decoder) # always the single-message decoder
46+
```
47+
48+
For batch subscribers, this should set the batch decoder when available.
49+
50+
## Fix (3 touch points)
51+
52+
### Touch 1: `_get_parser_and_decoder()` batch detection
53+
54+
Add batch subscriber detection. Options:
55+
- Pass a `is_batch: bool` flag from `_build_fastdepends_model`
56+
- Check if the parser is a batch parser type
57+
- Add a flag to `_CallOptions`
58+
59+
Then dispatch:
60+
```python
61+
if codec and is_batch and isinstance(codec, BatchCodecProto):
62+
async_decoder = codec.decode_batch
63+
elif codec:
64+
async_decoder = codec.decode
65+
```
66+
67+
### Touch 2: FakeProducer batch path
68+
69+
Pass raw `ConsumerRecord` tuples to the subscriber pipeline instead of pre-built `StreamMessage`s. The batch parser will handle batching + decoding correctly.
70+
71+
### Touch 3: `call_item.is_suitable()` or `set_decoder`
72+
73+
No change needed if Touch 1 correctly sets the batch decoder — `set_decoder` already stores whatever callable it receives.
74+
75+
## Effort
76+
77+
Small-medium. Three files, ~20 lines of logic. The main complexity is plumbing `is_batch` through to `_get_parser_and_decoder` without polluting the interface.
78+
79+
## When
80+
81+
Separate PR after the current codec encode wiring merges. Not blocking — `BatchCodecTestcase` unit tests verify the protocol dispatch correctly, and the full round-trip for single messages is already covered by `CodecTestcase`.

docs/docs/en/getting-started/serialization/codec.md

Lines changed: 13 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -26,45 +26,25 @@ class CodecProto(Protocol):
2626
) -> tuple[bytes, str | None]: ...
2727
```
2828

29-
- **`decode`** — receives a `StreamMessage` with raw bytes in `msg.body` and returns the decoded Python value. You can mutate `msg.body` before delegating to `decode_message`.
30-
- **`encode`** — receives a `PublishCommand` with the outgoing message body and metadata, and an optional serializer. Returns a `(bytes, content_type)` tuple.
31-
- **`cmd.destination`** — the target topic, subject, or queue name. Useful for codecs that need destination-specific behavior (e.g. Schema Registry topic-to-schema resolution).
29+
- **`decode`** — receives a `StreamMessage` with raw bytes in `msg.body` and returns the decoded Python value.
30+
- **`encode`** — receives a `PublishCommand` containing the message body, destination, and headers. Returns a `(bytes, content_type)` tuple. Access the payload via `cmd.body` and the target topic/subject/queue via `cmd.destination`.
3231

33-
If no codec is set, `DefaultCodec` is used automatically. It handles JSON objects, plain text, and raw bytes without any configuration.
32+
If no codec is set, `DefaultCodec` is used automatically. It handles JSON objects, plain text, and raw bytes.
3433

35-
## Compression Example
34+
## Example: Schema Registry
3635

37-
A Gzip codec that compresses outgoing messages and decompresses incoming ones:
36+
A Confluent Avro codec that encodes and decodes messages using the [Confluent wire format](https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/index.html#wire-format){target="_blank"} (magic byte + schema ID + Avro payload). Requires `fastavro` and `confluent-kafka`:
3837

39-
=== "AIOKafka"
40-
```python linenums="1" hl_lines="15-27 30"
41-
{!> docs_src/getting_started/serialization/codec_gzip_kafka.py !}
42-
```
43-
44-
=== "Confluent"
45-
```python linenums="1" hl_lines="15-27 30"
46-
{!> docs_src/getting_started/serialization/codec_gzip_confluent.py !}
47-
```
48-
49-
=== "RabbitMQ"
50-
```python linenums="1" hl_lines="15-27 30"
51-
{!> docs_src/getting_started/serialization/codec_gzip_rabbit.py !}
52-
```
53-
54-
=== "NATS"
55-
```python linenums="1" hl_lines="15-27 30"
56-
{!> docs_src/getting_started/serialization/codec_gzip_nats.py !}
57-
```
38+
```bash
39+
pip install fastavro confluent-kafka
40+
```
5841

59-
=== "Redis"
60-
```python linenums="1" hl_lines="15-27 30"
61-
{!> docs_src/getting_started/serialization/codec_gzip_redis.py !}
62-
```
42+
```python linenums="1" hl_lines="22-66 68-76"
43+
{!> docs_src/getting_started/serialization/codec_schema_registry_kafka.py !}
44+
```
6345

64-
=== "MQTT"
65-
```python linenums="1" hl_lines="15-27 30"
66-
{!> docs_src/getting_started/serialization/codec_gzip_mqtt.py !}
67-
```
46+
!!! note
47+
The codec fetches and caches schemas from the registry at startup and on first encounter. The `subject` follows Confluent's naming convention: `{topic}-value`.
6848

6949
## Priority
7050

docs/docs_src/getting_started/serialization/codec_gzip_confluent.py

Lines changed: 0 additions & 41 deletions
This file was deleted.

docs/docs_src/getting_started/serialization/codec_gzip_kafka.py

Lines changed: 0 additions & 41 deletions
This file was deleted.

docs/docs_src/getting_started/serialization/codec_gzip_mqtt.py

Lines changed: 0 additions & 41 deletions
This file was deleted.

docs/docs_src/getting_started/serialization/codec_gzip_nats.py

Lines changed: 0 additions & 41 deletions
This file was deleted.

docs/docs_src/getting_started/serialization/codec_gzip_rabbit.py

Lines changed: 0 additions & 41 deletions
This file was deleted.

docs/docs_src/getting_started/serialization/codec_gzip_redis.py

Lines changed: 0 additions & 41 deletions
This file was deleted.

0 commit comments

Comments
 (0)