This is an automated email from the ASF dual-hosted git repository.
kparisa pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iggy-website.git
The following commit(s) were added to refs/heads/main by this push:
new 084d30f45 docs: add a Python getting started tutorial (#119)
084d30f45 is described below
commit 084d30f452c8e08a39897ad6d07a6e9fc87e209e
Author: Justin Mclean <[email protected]>
AuthorDate: Tue Oct 6 21:39:47 2026 +1100
docs: add a Python getting started tutorial (#119)
Walks through a producer, a consumer and a consumer group with the
Python SDK against 0.9.0, alongside the existing Rust tutorial.
Co-authored-by: Justin Mclean <[email protected]>
---
.../docs/introduction/getting-started-python.mdx | 213 +++++++++++++++++++++
content/docs/introduction/meta.json | 4 +-
2 files changed, 215 insertions(+), 2 deletions(-)
diff --git a/content/docs/introduction/getting-started-python.mdx
b/content/docs/introduction/getting-started-python.mdx
new file mode 100644
index 000000000..91e9993fb
--- /dev/null
+++ b/content/docs/introduction/getting-started-python.mdx
@@ -0,0 +1,213 @@
+---
+title: Getting started (Python)
+description: "A first program with the Python SDK: create a stream and topic,
send messages, then read them back on their own and as a consumer group."
+---
+
+## Before we start
+
+This tutorial uses the Python SDK's low-level API (`IggyClient`) directly, so
you can see each step. It needs Python 3.10 or newer. On Linux the server needs
kernel 5.19 or newer; macOS has no kernel requirement. See [System
requirements](/docs/server/introduction#system-requirements).
+
+The producer and consumer below match the [getting-started
examples](https://github.com/apache/iggy/tree/master/examples/python/getting-started)
in the repository.
+
+## Starting the server
+
+```bash
+docker run --rm \
+ --cap-add=SYS_NICE --security-opt seccomp=unconfined --ulimit memlock=-1:-1 \
+ -p 8090:8090 \
+ -e IGGY_TCP_ADDRESS=0.0.0.0:8090 \
+ -e IGGY_NODE_ADVERTISED_ADDRESS=localhost \
+ -e IGGY_ROOT_USERNAME=iggy -e IGGY_ROOT_PASSWORD=iggy \
+ apache/iggy:0.9.0
+```
+
+`IGGY_TCP_ADDRESS` and `IGGY_NODE_ADVERTISED_ADDRESS` are needed because the
server binds `127.0.0.1` inside the container by default, which the published
port can't reach. The Rust tutorial's [Starting the Iggy
server](/docs/introduction/getting-started#starting-the-iggy-server) section
explains each flag.
+
+On Docker Desktop, 0.9.0 can fail to start with `failed to bind shard 0 memory
to its NUMA node`. If you hit this, add `-e IGGY_SHARDING_CPU_ALLOCATION=all`
to the command above.
+
+Install the SDK in a virtual environment:
+
+```bash
+python -m venv .venv
+source .venv/bin/activate
+pip install apache-iggy
+```
+
+## Producer
+
+Save this as `producer.py`:
+
+```python
+import asyncio
+
+from apache_iggy import IggyClient
+from apache_iggy import SendMessage as Message
+
+STREAM_NAME = "sample-stream"
+TOPIC_NAME = "sample-topic"
+PARTITION_ID = 0
+
+
+async def main():
+ client = IggyClient.from_connection_string(
+ "iggy+tcp://iggy:[email protected]:8090"
+ )
+ await client.connect()
+
+ # Re-running this example is fine: only create what is missing.
+ if await client.get_stream(STREAM_NAME) is None:
+ await client.create_stream(name=STREAM_NAME)
+
+ if await client.get_topic(STREAM_NAME, TOPIC_NAME) is None:
+ await client.create_topic(
+ stream=STREAM_NAME,
+ name=TOPIC_NAME,
+ partitions_count=1,
+ )
+
+ messages = [Message(f"message-{i}") for i in range(10)]
+
+ await client.send_messages(
+ stream=STREAM_NAME,
+ topic=TOPIC_NAME,
+ partitioning=PARTITION_ID,
+ messages=messages,
+ )
+ print(f"Sent {len(messages)} message(s)")
+
+
+asyncio.run(main())
+```
+
+Run it with `python producer.py`:
+
+```text
+Sent 10 message(s)
+```
+
+`from_connection_string` takes the transport, host and credentials in one
string, `iggy+tcp://user:pass@host:port`. See [Connection
strings](/docs/sdk/connection-strings) for the other options. Creating a stream
or topic that already exists is an error, so the producer checks first and only
creates what's missing. That's also what makes it safe to run again.
+
+## Consumer
+
+Save this as `consumer.py`:
+
+```python
+import asyncio
+
+from apache_iggy import Consumer, IggyClient, PollingStrategy
+
+STREAM_NAME = "sample-stream"
+TOPIC_NAME = "sample-topic"
+PARTITION_ID = 0
+
+
+async def main():
+ client = IggyClient.from_connection_string(
+ "iggy+tcp://iggy:[email protected]:8090"
+ )
+ await client.connect()
+
+ # Next() resumes this named consumer after its stored offset. Auto-commit
+ # can store the offset before application processing finishes.
+ polled_messages = await client.poll_messages(
+ stream=STREAM_NAME,
+ topic=TOPIC_NAME,
+ consumer=Consumer.Single("sample-consumer"),
+ partition_id=PARTITION_ID,
+ polling_strategy=PollingStrategy.Next(),
+ count=10,
+ auto_commit=True,
+ )
+
+ for message in polled_messages:
+ payload = message.payload().decode("utf-8")
+ print(f"Offset: {message.offset()}, Payload: {payload}")
+
+
+asyncio.run(main())
+```
+
+Run it with `python consumer.py`, after the producer:
+
+```text
+Offset: 0, Payload: message-0
+Offset: 1, Payload: message-1
+Offset: 2, Payload: message-2
+Offset: 3, Payload: message-3
+Offset: 4, Payload: message-4
+Offset: 5, Payload: message-5
+Offset: 6, Payload: message-6
+Offset: 7, Payload: message-7
+Offset: 8, Payload: message-8
+Offset: 9, Payload: message-9
+```
+
+`Consumer.Single(name)` is a consumer that polls on its own, and the server
stores its offset under that name. With `auto_commit=True` the offset is stored
after the poll returns, without waiting for the store to finish, so a crash
straight afterwards can deliver the last batch again.
+
+## Consumer group
+
+A consumer group lets several copies of a consumer share a topic's partitions,
with each message going to only one of them. Groups need a binary transport
(TCP, QUIC or WebSocket); HTTP doesn't support them.
+
+Save this as `consumer_group.py`:
+
+```python
+import asyncio
+
+from apache_iggy import IggyClient, PollingStrategy
+
+STREAM_NAME = "sample-stream"
+TOPIC_NAME = "sample-topic"
+GROUP_NAME = "sample-consumer-group"
+
+
+async def main():
+ client = IggyClient.from_connection_string(
+ "iggy+tcp://iggy:[email protected]:8090"
+ )
+ await client.connect()
+
+ # Creates the group if it doesn't exist and joins it. A group member
+ # reads whichever partitions the server assigns it, so there's no
+ # partition_id to set, unlike Consumer.Single.
+ group = await client.consumer_group(
+ name=GROUP_NAME,
+ stream=STREAM_NAME,
+ topic=TOPIC_NAME,
+ polling_strategy=PollingStrategy.First(),
+ )
+
+ # iter_messages() keeps waiting for new messages once it catches up,
+ # like a live subscription, so this example stops after 20.
+ count = 0
+ async for message in group.iter_messages():
+ payload = message.payload().decode("utf-8")
+ print(f"Offset: {message.offset()}, Payload: {payload}")
+ count += 1
+ if count == 20:
+ break
+ print(f"Consumed {count} message(s), stopping.")
+
+
+asyncio.run(main())
+```
+
+Run it with `python consumer_group.py`. With the producer run twice, so 20
messages on the topic, it prints every offset from 0 to 19 and then stops:
+
+```text
+Offset: 0, Payload: message-0
+...
+Offset: 19, Payload: message-9
+Consumed 20 message(s), stopping.
+```
+
+`iter_messages()` is the simplest way to read from a group. For a long-running
service with its own shutdown signal, use `consume_messages(callback,
shutdown_event)` instead.
+
+## Running it again
+
+Run the producer again and it finds the stream and topic already there, and
adds another 10 messages at offsets 10 to 19.
+
+Run `consumer.py` again and it carries on from its stored offset, printing
only the new messages rather than the first ten again.
+
+Run `consumer_group.py` again and it prints every message from offset 0,
because it asks for `PollingStrategy.First()`. That keeps the example
repeatable. A long-running group consumer would leave the polling strategy at
its default and keep `auto_commit` on, so it carries on from the group's stored
offset instead.
+
+To start again with an empty server, stop the container, which `--rm` removes,
and run `docker run` again.
diff --git a/content/docs/introduction/meta.json
b/content/docs/introduction/meta.json
index 4bb52f459..dbb3a103b 100644
--- a/content/docs/introduction/meta.json
+++ b/content/docs/introduction/meta.json
@@ -1,4 +1,4 @@
{
"title": "Introduction",
- "pages": ["about", "concepts", "coming-from-kafka", "glossary",
"serialising-messages", "architecture", "quickstart", "getting-started"]
-}
+ "pages": ["about", "concepts", "coming-from-kafka", "glossary",
"serialising-messages", "architecture", "quickstart", "getting-started",
"getting-started-python"]
+}
\ No newline at end of file