aiokafka-foundation-kit for AI agents¶
One page holding everything a coding assistant needs to configure and drive aiokafka-foundation-kit correctly, plus a map of where the rest of the documentation keeps the details it leaves out. Give an agent this page rather than the whole site.
| Package | aiokafka-foundation-kit on PyPI, import root aiokafka_foundation_kit |
| Requires | Python 3.11+, aiokafka>=0.12.0,<1.0.0 (0.14 in the lock), a reachable Kafka broker |
| Install | pip install aiokafka-foundation-kit · extras: models, orjson, dishka, dependency-injector, telemetry, all |
| Async | everything. The factories, both lifecycles, ensure_topics_async and check_kafka_health_async |
| Sync | none. There is no synchronous mirror, and the factories must be called from inside a running event loop |
| Source | https://github.com/bedrock-python/aiokafka-foundation-kit |
How to read this page¶
Every page of this site is also served as raw Markdown at its own URL with .md in place
of the trailing slash — this page is /agents.md, the configuration guide is
/guide/configuration.md — so anything the map below points at can be fetched as plain
text rather than scraped out of HTML. The Copy page control at the top of a page does
the same thing for a human with a chat window open. The one exception is the API
reference: its Markdown is a list of instructions to a docstring renderer rather than the
API, so it carries neither the control nor a .md twin — read it as HTML, or read the
docstrings in the source.
Top to bottom before writing code. Rules that hold or break the code is the section correctness lives in — those are the things the library will not save you from. Every name used below is in the public API; if you need something not listed here, fetch the page the documentation map points at rather than guessing a method that sounds plausible.
Scope¶
It does turn a settings object into a configured AIOKafkaProducer or
AIOKafkaConsumer, own the start/stop of that client as an async context manager, create
topics idempotently before a producer starts, probe whether a cluster answers, serialise
message values as JSON, and hand the same wiring to dishka, dependency-injector and
OpenTelemetry.
It does not wrap aiokafka. Once a client is yielded you are holding an
AIOKafkaProducer / AIOKafkaConsumer and every send, poll, commit, seek and rebalance
callback is aiokafka's own API — read aiokafka's documentation for those. There is no
publisher class, no consumer runner, no message router, no retry policy, no dead-letter
handling, no outbox, no schema registry, no Prometheus metrics and no synchronous
variant. It runs no loop of its own: consumer_lifecycle subscribes, and the async for
over the consumer is yours to write.
Mental model¶
Four nouns:
- Settings — any object with the right attributes. Every entry point takes a
typing.Protocol, not a base class, so a Pydantic model, a dataclass, aSimpleNamespaceor aMagicMockall work. Themodelsextra ships Pydantic implementations with the defaults below; nothing checks a hand-rolled object, so a missing attribute surfaces asAttributeErrorwhen the client is built. - Factory —
create_async_kafka_producer(settings)/create_async_kafka_consumer(settings, topics)read the settings and return a client that has not been started. Both go throughbuild_kafka_common_config(settings), which is the one place connection, SASL and TLS keys are assembled. - Lifecycle —
producer_lifecycle/consumer_lifecycleare async context managers aroundmanaged_kafka_client: start, then in atry/finallythe optionalon_startedhook, the yield, the stop and the optionalon_stoppedhook.producer_lifecyclecan also runensure_topics_asyncfirst. - Topics —
TopicConfigis a frozen dataclass describing one topic;ensure_topics_async(topics, settings)creates each one, reads the broker's per-topic reply, and treats an existing topic as success.
The contrib packages are three thin wrappers over that: contrib.di for dishka providers,
contrib.dependency_injector for declarative containers, contrib.telemetry for one call
to the OpenTelemetry aiokafka instrumentor. None of them is imported by
aiokafka_foundation_kit/__init__.py; contrib/__init__.py exports nothing, so every
contrib name is an explicit import from its own submodule and raises ImportError naming
the extra when its dependency is missing.
Wiring¶
import asyncio
from aiokafka_foundation_kit import (
TopicConfig,
consumer_lifecycle,
producer_lifecycle,
)
from aiokafka_foundation_kit.contrib.models import (
BaseKafkaConsumerSettings,
BaseKafkaProducerSettings,
)
producer_settings = BaseKafkaProducerSettings(bootstrap_servers="localhost:9092")
consumer_settings = BaseKafkaConsumerSettings(
bootstrap_servers="localhost:9092",
group_id="order-processor",
)
topics = [TopicConfig(name="orders", num_partitions=6, replication_factor=1)]
async def produce() -> None:
async with producer_lifecycle(
producer_settings,
topics=topics,
auto_create_topics=True, # both arguments are needed; neither alone does anything
) as producer:
await producer.send_and_wait("orders", {"id": 42}) # dict -> JSON bytes
async def consume() -> None:
async with consumer_lifecycle(consumer_settings, topics=("orders",)) as consumer:
async for message in consumer: # the loop is yours
await handle(message.value) # already decoded from JSON
await consumer.commit() # enable_auto_commit is False
asyncio.run(produce())
Everything happens inside a running loop on purpose: aiokafka's constructors take the
running loop, so building a client at import time raises RuntimeError.
The API¶
The root package¶
from aiokafka_foundation_kit import …
| Name | Signature | Returns |
|---|---|---|
create_async_kafka_producer |
(settings, *, value_serializer=None) |
AIOKafkaProducer, not started |
producer_lifecycle |
(settings, *, topics=None, auto_create_topics=False, name="producer", on_started=None, on_stopped=None) |
async context manager yielding a started AIOKafkaProducer |
create_async_kafka_consumer |
(settings, topics=None, *, value_deserializer=None) |
AIOKafkaConsumer, not started |
consumer_lifecycle |
(settings, *, topics=None, name="consumer", on_started=None, on_stopped=None) |
async context manager yielding a started AIOKafkaConsumer |
ensure_topics_async |
(topics, settings) |
None |
TopicConfig |
frozen dataclass (name, num_partitions, replication_factor, replica_assignment=None, topic_configs=None) |
— |
check_kafka_health_async |
(settings, timeout_seconds=5.0) |
bool |
build_kafka_common_config |
(settings) |
dict[str, Any] of aiokafka keyword arguments |
__version__ |
— | str |
Also at the root, for annotations only: KafkaSettingsProtocol, ProducerSettingsProtocol,
ConsumerSettingsProtocol, TopicConfigProtocol.
topics on producer_lifecycle is a Sequence[TopicConfigProtocol]; topics on
consumer_lifecycle is a tuple[str, ...] of names. on_started and on_stopped are
async callables taking the client. name appears only in log messages and matters when
one process runs several clients.
Public, but not at the root¶
| Name | Import from |
|---|---|
dumps_bytes, loads_bytes, managed_kafka_client |
aiokafka_foundation_kit.utils |
KafkaConnectionSettingsProtocol, KafkaSaslSettingsProtocol, KafkaSslSettingsProtocol |
aiokafka_foundation_kit.config |
ProducerLifecycleSettingsProtocol |
aiokafka_foundation_kit.config (or .config.producer) |
KafkaSecurityProtocol, KafkaSaslMechanism, KafkaAcks, KafkaCompressionType, KafkaOffsetReset |
aiokafka_foundation_kit.config.kafka, or aiokafka_foundation_kit.contrib.models |
| every contrib name | its own submodule — contrib/__init__.py exports nothing |
managed_kafka_client(client, *, name, on_started=None, on_stopped=None) wraps anything
with async start() / stop(); both lifecycles are built on it.
dumps_bytes(value) returns bytes unchanged and JSON-encodes everything else;
loads_bytes(value) returns None for value is None (a tombstone) and JSON-decodes
otherwise. Both use orjson when the orjson extra is installed and the standard
library json when it is not.
Settings protocols¶
Structural, so any object with these attributes qualifies. The hierarchy:
KafkaConnectionSettingsProtocol bootstrap_servers, client_id, security_protocol,
│ metadata_max_age_ms, get_sasl_password()
├── KafkaSaslSettingsProtocol + sasl_mechanism, sasl_username
└── KafkaSslSettingsProtocol + ssl_cafile, ssl_certfile, ssl_keyfile, ssl_check_hostname
KafkaSettingsProtocol = SASL + SSL, the base every entry point accepts
├── ProducerSettingsProtocol + acks, compression_type, enable_idempotence,
│ │ max_batch_size, linger_ms, request_timeout_ms
│ └── ProducerLifecycleSettingsProtocol + auto_create_topics
└── ConsumerSettingsProtocol + group_id, auto_offset_reset, enable_auto_commit,
session_timeout_ms, heartbeat_interval_ms,
max_poll_records, max_poll_interval_ms,
fetch_max_wait_ms, fetch_min_bytes, fetch_max_bytes
Note that the password is a method, get_sasl_password() -> str | None, not an
attribute. Protocol attributes are invariant, so a hand-rolled settings class has to use
the Literal aliases themselves: security_protocol: str does not satisfy
security_protocol: KafkaSecurityProtocol, and the same goes for acks,
compression_type, sasl_mechanism and auto_offset_reset.
TopicConfigProtocol is name, num_partitions, replication_factor,
replica_assignment, topic_configs, declared read-only so that a frozen dataclass — the
kit's own TopicConfig among them — type-checks where the protocol is expected. A config
carrying a replica_assignment is created from it: the assignment fixes both the partition
count and the replication factor, so the two count fields may only repeat what it says or
be -1 — see rule 15.
Pydantic settings — contrib.models¶
pip install "aiokafka-foundation-kit[models]". These are plain BaseModels, so extra
keyword arguments are ignored rather than rejected, and bootstrap_servers (and
group_id) are the only required fields.
BaseKafkaSettings — connection, SASL and TLS:
| Field | Type | Default |
|---|---|---|
bootstrap_servers |
str |
required |
client_id |
str | None |
None |
metadata_max_age_ms |
int |
300000 |
security_protocol |
"PLAINTEXT" | "SASL_PLAINTEXT" | "SSL" | "SASL_SSL" |
"PLAINTEXT" |
sasl_mechanism |
"PLAIN" | "SCRAM-SHA-256" | "SCRAM-SHA-512" | None |
None |
sasl_username |
str | None |
None |
sasl_password |
SecretStr | None |
None |
ssl_cafile / ssl_certfile / ssl_keyfile |
str | None |
None |
ssl_check_hostname |
bool |
True |
Two validators run after construction: a SASL_* protocol requires sasl_mechanism,
sasl_username and a non-empty sasl_password; SSL and SASL_SSL require ssl_cafile.
Either failure is a pydantic.ValidationError.
BaseKafkaProducerSettings = BaseKafkaSettings + KafkaAutoCreateMixin:
| Field | Type | Default |
|---|---|---|
acks |
"0" | "1" | "all" |
"all" |
compression_type |
"gzip" | "snappy" | "lz4" | "zstd" | None |
"gzip" |
enable_idempotence |
bool |
True |
max_batch_size |
int |
16384 |
linger_ms |
int |
5 |
request_timeout_ms |
int |
30000 |
auto_create_topics |
bool |
False |
default_partitions |
int |
3 |
default_replication_factor |
int | None |
None, required when auto_create_topics=True |
BaseKafkaConsumerSettings = BaseKafkaSettings +:
| Field | Type | Default |
|---|---|---|
group_id |
str |
required |
auto_offset_reset |
"earliest" | "latest" |
"earliest" |
enable_auto_commit |
bool |
False |
session_timeout_ms |
int |
30000 |
heartbeat_interval_ms |
int |
3000 |
max_poll_records |
int |
500 |
max_poll_interval_ms |
int |
300000 |
fetch_max_wait_ms |
int |
500 |
fetch_min_bytes |
int |
1 |
fetch_max_bytes |
int |
52428800 |
BaseKafkaInfraSettings describes a topic namespace rather than a connection:
topic_prefix: str | None = None, topic_catalog: dict[str, KafkaTopicSettings] | None = None,
consumer_subscriptions: list[str] | None = None. KafkaTopicSettings is
num_partitions=3, replication_factor=3, topic_configs=None.
normalize_kafka_topic_prefix_value(value) is the prefix coercion on its own: None,
"", whitespace, "None" and "null" in any case all become None, and so does any
non-string.
The mixins are exported separately for composing your own model:
KafkaConnectionMixin, KafkaSaslMixin, KafkaSslMixin, KafkaAutoCreateMixin.
The same composition is the place for fields the library does not read. Neither model
carries an observability flag such as metrics_enabled: the library collects no metrics,
so there is nothing for it to switch. Put it in a mixin of your own and inherit that in
both your producer and your consumer subclass; one Protocol then describes both. Set
model_config = ConfigDict(extra="forbid") on those subclasses if an unknown key should
be a ValidationError rather than silently dropped.
dishka — contrib.di¶
pip install "aiokafka-foundation-kit[dishka]". Three Provider subclasses, all
Scope.APP, all raising ImportError from __init__ when dishka is absent.
| Provider | Provides | Needs in the container |
|---|---|---|
AsyncKafkaProducerProvider |
AIOKafkaProducer |
ProducerLifecycleSettingsProtocol and Sequence[TopicConfigProtocol] | None |
AsyncKafkaConsumerProvider |
AIOKafkaConsumer |
ConsumerSettingsProtocol and tuple[str, ...] | None |
KafkaInfraProvider |
Sequence[TopicConfig], tuple[str, ...], and both of those under the optional types the two providers above ask for |
KafkaProducerInfraSettingsProtocol and KafkaConsumerInfraSettingsProtocol |
AsyncKafkaProducerProvider is the only place settings.auto_create_topics is read.
KafkaInfraProvider turns topic_catalog into TopicConfig objects and
consumer_subscriptions into names, applying topic_prefix as f"{prefix}.{name}".
The infrastructure protocols — KafkaInfraBaseSettingsProtocol,
KafkaProducerInfraSettingsProtocol, KafkaConsumerInfraSettingsProtocol,
KafkaTopicSettingsProtocol — are exported for the settings side.
Adding KafkaInfraProvider to a container is therefore enough to satisfy the topics
argument of both client providers; see rule 10 for a container without it.
dependency-injector — contrib.dependency_injector¶
pip install "aiokafka-foundation-kit[dependency-injector]".
| Container | Providers |
|---|---|
KafkaProducerContainer |
kafka_settings (Dependency), topics (Object(None)), auto_create_topics (Object(False)), producer (Resource) |
KafkaConsumerContainer |
kafka_settings (Dependency), topics (Object(None)), consumer (Resource) |
Override the dependencies, await container.init_resources(), then
producer = await container.producer(); await container.shutdown_resources() stops the
client. See rule 11 — kafka_settings is the one override every container needs.
OpenTelemetry — contrib.telemetry¶
pip install "aiokafka-foundation-kit[telemetry]".
instrument_aiokafka(*, tracer_provider=None, async_produce_hook=None, async_consume_hook=None, **kwargs)
calls AIOKafkaInstrumentor().instrument(...), which patches aiokafka globally — call it
once at startup, before any client is built. Only the arguments you pass are forwarded, so
omitting tracer_provider leaves the global provider in place. async_produce_hook is
async (span, args, kwargs), async_consume_hook is async (span, record, args, kwargs).
Extra **kwargs go straight to the instrumentor.
Rules that hold or break the code¶
- Build clients inside a running event loop.
create_async_kafka_producer,create_async_kafka_consumerand both lifecycles construct aiokafka objects, and aiokafka's constructor takes the running loop. Called at import time or from synchronous module scope it raisesRuntimeError: The object should be created within an async function or provide loop directly. - TLS reaches aiokafka as one
ssl_contextkey. aiokafka takes a preparedssl.SSLContextand nothing else, so forSSLandSASL_SSLbuild_kafka_common_configloadsssl_cafile,ssl_certfileandssl_keyfilethroughaiokafka.helpers.create_ssl_contextand returnsssl_context; the fourssl_*keys never appear in the dict. The files are read at that moment, so a missing or unreadable one raisesFileNotFoundError(a malformed onessl.SSLError) from the factory rather than at connect time, and with nossl_cafilethe system trust store is used.ssl_check_hostname=Falsesetscheck_hostname = Falseon the context — the certificate is still verified against the CA, only the hostname is not.SASL_PLAINTEXTandPLAINTEXTbuild no context at all. producer_lifecycledoes not readsettings.auto_create_topics. It has its own keyword, defaulting toFalse, and creates topics only whenauto_create_topics=Trueandtopicsis non-empty. Passing one without the other silently creates nothing. The settings field is honoured only byAsyncKafkaProducerProviderand by whatever you pass to the dependency-injector container.enable_auto_commitisFalseby default onBaseKafkaConsumerSettings. Nothing commits for you: callawait consumer.commit()after processing, or the group re-reads from the last committed offset on every restart and rebalance. The factory forwards the flag as-is, so a settings object that omits it fails withAttributeError, not a default.enable_idempotence=True(the default) allows onlyacks="all". aiokafka raisesValueError: Invalid ACKS parameterfor"0"or"1". TheKafkaAcksalias offers three values; two of them requireenable_idempotence=False.- Only
gzipcompression works out of the box.snappy,lz4andzstdneedcramjam, which this package does not depend on — without it aiokafka raisesRuntimeError: Compression library for lz4 not foundat construction. Installaiokafka[lz4](or[snappy],[zstd]) alongside. - Values are JSON, keys are bytes. The producer factory installs
value_serializer=dumps_bytes, so adictis encoded for you andbytespass through untouched. It installs nokey_serializer, sokey=must already bebytesor aiokafka raises. On the consumer sidevalue_deserializer=loads_bytesmeansmessage.valueis a decoded object; a topic carrying Avro, protobuf or plain text needsvalue_deserializer=lambda raw: rawor the decode raises inside aiokafka's fetcher. - An
on_startedhook that raises aborts the context. The hook runs inside thetry/finally, so the client is stopped and the hook's exception comes out of theasync withstatement itself — the body never runs, and nothing is left connected. - Only
KafkaErroris swallowed on stop.managed_kafka_clientcatchesKafkaErrorfromstop(), logs it, and skipson_stopped; anything else —OSError,asyncio.CancelledError, aTimeoutError— propagates out of the context manager and can mask the body's own exception. - dishka resolves by exact type, and the kit's providers ask for optionals.
AsyncKafkaProducerProviderwantsSequence[TopicConfigProtocol] | NoneandAsyncKafkaConsumerProviderwantstuple[str, ...] | None. Python's= Nonedefault is not a dishka default, so something in the container has to provide those exact types or the build fails withGraphMissingFactoryError.KafkaInfraProviderprovides both; without it, write the one-line factory yourself (see Common mistakes). kafka_settingsis the only override a container requires. It is aproviders.Dependencywith no default, so leaving it out raisesError: Dependency "…kafka_settings" is not definedthe moment the resource is resolved.topicsandauto_create_topicsareproviders.Object, so they already holdNoneandFalse; override them only when you have something to pass. (providers.Dependency(default=None)would not be a default — dependency-injector readsNoneas "unset" — which is why these areObject.)default_partitionsanddefault_replication_factorare validated and never used. Nothing in the library reads them; the shape of a created topic comes fromTopicConfig(wherenum_partitionsandreplication_factorare both required) or fromKafkaTopicSettingsin atopic_catalog. The validator that demandsdefault_replication_factorwhenauto_create_topics=Trueis the only thing they do.- The consumer lifecycle subscribes; it does not consume. You write the
async for, and your per-batch work has to finish insidemax_poll_interval_ms(300 s by default) or the broker evicts the member and the partitions rebalance under you. ensure_topics_asynctolerates exactly one failure. Each topic is created on its own, and the broker's answer for it is read: an existing topic is logged atDEBUGand ignored, every other refusal is raised as the aiokafka error for that error code —InvalidReplicationFactorErrorfor areplication_factorabove the broker count,InvalidTopicErrorfor an unusable name — and aborts the rest of the sequence. It also never changes an existing topic: partition counts andtopic_configsof a topic that already exists are left alone.- A
replica_assignmentdecides the shape of the topic. Give one and it fixes both the partition count and the replication factor;num_partitionsandreplication_factormay then only repeat what it says (num_partitions == len(replica_assignment),replication_factor ==the length of each replica list) or be-1, which is how aiokafka spells "the assignment decides". Anything else is aValueErrornaming the topic, raised before the request is sent. An empty dict counts as no assignment. check_kafka_health_asyncopens a real producer connection, and itstimeout_secondsbecomesrequest_timeout_ms, not anasyncio.timeoutaround the call — a DNS or TCP stall can outlast it. It returnsFalse(and logs a traceback atERROR) forTimeoutError,KafkaErrorandOSError, and lets anything else out. It is a probe, not a cheap one; do not call it per request.- A
topic_prefixjoins with a dot —prefix+"."+name— and onlyKafkaInfraProviderapplies it.TopicConfig.nameand the topic names you handconsumer_lifecycleare physical names, already prefixed. - The protocols are structural and unenforced. Nothing validates a hand-rolled
settings object; a typo or a missing attribute is an
AttributeErrorat client construction, andget_sasl_passwordmust be a callable, not a string.
Common mistakes¶
# WRONG — a class the library has never had, imported from the root
from aiokafka_foundation_kit import AsyncKafkaPublisher, BaseKafkaProducerSettings
# RIGHT — settings live in contrib.models, and the producer is aiokafka's own
from aiokafka_foundation_kit import producer_lifecycle
from aiokafka_foundation_kit.contrib.models import BaseKafkaProducerSettings
async with producer_lifecycle(settings) as producer:
await producer.send_and_wait("orders", {"id": 42})
# WRONG — built at import time, outside any loop
producer = create_async_kafka_producer(settings) # RuntimeError
# RIGHT — inside a coroutine, and let the context manager own start/stop
async def main() -> None:
async with producer_lifecycle(settings) as producer:
...
# WRONG — topics without the flag; nothing is created, and send fails on a
# cluster with auto.create.topics.enable=false
async with producer_lifecycle(settings, topics=topics) as producer:
...
# RIGHT — both, every time
async with producer_lifecycle(settings, topics=topics, auto_create_topics=True) as producer:
...
# WRONG — at-least-once assumed, never delivered: enable_auto_commit is False
async with consumer_lifecycle(settings, topics=("orders",)) as consumer:
async for message in consumer:
await handle(message.value)
# RIGHT — commit after the work, so a crash replays rather than skips
async with consumer_lifecycle(settings, topics=("orders",)) as consumer:
async for message in consumer:
await handle(message.value)
await consumer.commit()
# WRONG — a str key, and acks the idempotent producer will not accept
settings = BaseKafkaProducerSettings(bootstrap_servers="localhost:9092", acks="1")
await producer.send_and_wait("orders", {"id": 42}, key="order-42")
# RIGHT — bytes for the key; turn idempotence off if you really want acks="1"
settings = BaseKafkaProducerSettings(bootstrap_servers="localhost:9092")
await producer.send_and_wait("orders", {"id": 42}, key=b"order-42")
# WRONG — nothing provides Sequence[TopicConfigProtocol] | None, and the `= None`
# on the provider's parameter is not a dishka default; the container refuses to build
container = make_async_container(SettingsProvider(), AsyncKafkaProducerProvider())
# RIGHT — KafkaInfraProvider provides that exact type from the topic catalog
container = make_async_container(SettingsProvider(), KafkaInfraProvider(),
AsyncKafkaProducerProvider())
# ALSO RIGHT — no catalog, so provide the type yourself
class TopicsProvider(Provider):
scope = Scope.APP
@provide
def producer_topics(self) -> Sequence[TopicConfigProtocol] | None:
return None
container = make_async_container(SettingsProvider(), TopicsProvider(),
AsyncKafkaProducerProvider())
Errors¶
The library defines no exception class of its own. What you will see:
| Exception | Where it comes from |
|---|---|
ImportError |
instantiating a contrib provider or container, or calling instrument_aiokafka, without the extra. The message names the extra to install. |
pydantic.ValidationError |
a contrib.models settings object: a missing bootstrap_servers or group_id, a value outside a Literal, an incomplete SASL_* or SSL block, auto_create_topics=True with no default_replication_factor. |
RuntimeError |
a client built outside a running loop, or a compression codec whose library is missing. |
FileNotFoundError, ssl.SSLError |
a TLS certificate or key path that cannot be read or parsed, raised while the settings are translated — see rule 2. |
ValueError |
acks of "0" or "1" while enable_idempotence is on; a TopicConfig whose counts contradict its replica_assignment — rule 15. |
AttributeError |
a settings object that does not satisfy the protocol. |
aiokafka.errors.KafkaError and its subclasses |
every broker interaction. TopicAlreadyExistsError is the one ensure_topics_async handles; InvalidReplicationFactorError, InvalidTopicError, KafkaConnectionError, KafkaTimeoutError, NodeNotReadyError, UnknownTopicOrPartitionError, CommitFailedError and the rest reach you unchanged. |
dishka.exceptions.GraphMissingFactoryError |
a container missing one of the exact types in rule 10. |
dependency_injector.errors.Error |
kafka_settings never overridden on a container — rule 11. |
Documentation map¶
Fetch a page when the task is the one named beside it.
| Page | Read it when |
|---|---|
| Home | the feature list, the extras, the shortest possible example |
| Quick start | writing the first producer or consumer end to end, health checks, topic creation, JSON helpers |
| Configuration | every settings field and its default, the security modes, loading from the environment, adding fields of your own, implementing a protocol by hand |
| Advanced | dishka providers, dependency-injector containers, OpenTelemetry, topic prefixes, managed_kafka_client, build_kafka_common_config |
| API reference | an exact signature or docstring — HTML only, see above |
| Changelog | what changed between versions |