From d8b50d753c666714e08c3cff53280ed726269d3b Mon Sep 17 00:00:00 2001 From: ruinivist Date: Tue, 14 Apr 2026 20:53:09 +0000 Subject: [PATCH] q,ex,binding scaffolded --- .gitignore | 1 + .python-version | 1 + DEV.md | 10 ++++++++ TASK.md | 62 +++++++++++++++++++++++---------------------- docker-compose.yaml | 7 +++++ main.py | 55 ++++++++++++++++++++++++++++++++++++++++ notifier.py | 0 producer.py | 0 pyproject.toml | 9 +++++++ uv.lock | 23 +++++++++++++++++ worker.py | 0 11 files changed, 138 insertions(+), 30 deletions(-) create mode 100644 .gitignore create mode 100644 .python-version create mode 100644 DEV.md create mode 100644 docker-compose.yaml create mode 100644 main.py create mode 100644 notifier.py create mode 100644 producer.py create mode 100644 pyproject.toml create mode 100644 uv.lock create mode 100644 worker.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed8ebf5 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ \ No newline at end of file diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/DEV.md b/DEV.md new file mode 100644 index 0000000..867234e --- /dev/null +++ b/DEV.md @@ -0,0 +1,10 @@ +Links\ +This page is very good and very structured +https://www.rabbitmq.com/tutorials + +What is it?\ +It's a queue - message broker is the technical term. + +Once you up, dashboard is on :15672, username:pass = guest:guest + +You never push directly to the q, it's always via exchanges. diff --git a/TASK.md b/TASK.md index 7f634be..b9ed6fa 100644 --- a/TASK.md +++ b/TASK.md @@ -24,66 +24,68 @@ By the end, you will have a real event flow running through RabbitMQ. ## Lab Stack - Language: **Python** -- Broker: RabbitMQ (Docker) +- Tooling: `uv` +- Broker: RabbitMQ **4+** (Docker) - Library: `pika` ## Step 1: Project Setup (10 min) -- [ ] Create files: -- [ ] `docker-compose.yml` -- [ ] `requirements.txt` -- [ ] `producer.py` -- [ ] `worker.py` -- [ ] `notifier.py` +- [x] Create files: +- [x] `docker-compose.yml` +- [x] `pyproject.toml` (created by `uv init`) +- [x] `producer.py` +- [x] `worker.py` +- [x] `notifier.py` -- [ ] Put this in `requirements.txt`: +- [x] Initialize project and dependency with `uv`: -```txt -pika==1.3.2 +```bash +uv init --name rabbitmq-lab +uv add pika==1.3.2 ``` -- [ ] Put this in `docker-compose.yml`: +- [x] Put this in `docker-compose.yml`: ```yaml version: "3.9" services: rabbitmq: - image: rabbitmq:3-management + image: rabbitmq:4-management ports: - "5672:5672" - "15672:15672" ``` -- [ ] Start RabbitMQ: +- [x] Start RabbitMQ: ```bash docker compose up -d ``` -- [ ] Open UI: `http://localhost:15672` (`guest` / `guest`). +- [x] Open UI: `http://localhost:15672` (`guest` / `guest`). ## Step 2: RabbitMQ Topology (15 min) Create these resources from code (preferred) or UI: -- [ ] Exchange `orders.x` (direct) -- [ ] Exchange `orders.dlx` (direct) -- [ ] Queue `orders.q` -- [ ] Queue `orders.processed.q` -- [ ] Queue `orders.failed.q` -- [ ] Queue `orders.dlq` +- [x] Exchange `orders.x` (direct) +- [x] Exchange `orders.dlx` (direct) +- [x] Queue `orders.q` +- [x] Queue `orders.processed.q` +- [x] Queue `orders.failed.q` +- [x] Queue `orders.dlq` Bindings: -- [ ] `orders.q` <- `orders.x` with `order.created` -- [ ] `orders.processed.q` <- `orders.x` with `order.processed` -- [ ] `orders.failed.q` <- `orders.x` with `order.failed` -- [ ] `orders.dlq` <- `orders.dlx` with `order.dead` +- [x] `orders.q` <- `orders.x` with `order.created` +- [x] `orders.processed.q` <- `orders.x` with `order.processed` +- [x] `orders.failed.q` <- `orders.x` with `order.failed` +- [x] `orders.dlq` <- `orders.dlx` with `order.dead` Queue args for `orders.q`: -- [ ] `x-dead-letter-exchange=orders.dlx` -- [ ] `x-dead-letter-routing-key=order.dead` +- [x] `x-dead-letter-exchange=orders.dlx` +- [x] `x-dead-letter-routing-key=order.dead` ## Step 3: Producer (20 min) @@ -98,7 +100,7 @@ Queue args for `orders.q`: Success check: -- [ ] Running `python producer.py` prints “Published order …” 5 times. +- [ ] Running `uv run producer.py` prints “Published order …” 5 times. ## Step 4: Worker (35 min) @@ -135,9 +137,9 @@ Success check: Run in 3 terminals: -- [ ] Terminal A: `python worker.py` -- [ ] Terminal B: `python notifier.py` -- [ ] Terminal C: `python producer.py` +- [ ] Terminal A: `uv run worker.py` +- [ ] Terminal B: `uv run notifier.py` +- [ ] Terminal C: `uv run producer.py` Expected behavior: diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..57e21b3 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,7 @@ +services: + rabbitmq: + # management basically has nicer clis and a dashboard for seeing the q + image: rabbitmq:4.2.5-management + ports: + - 5672:5672 # actual container + - 15672:15672 # web dashboard diff --git a/main.py b/main.py new file mode 100644 index 0000000..20ac928 --- /dev/null +++ b/main.py @@ -0,0 +1,55 @@ +import pika + +# this is the physical conn +connection = pika.BlockingConnection(pika.ConnectionParameters("localhost")) +# a channel is a lightweight multiplexer over it +channel = connection.channel() + +# first the exchanges + +# this is A DECLARE, basically so can run multuple times +# .x = exchange +channel.exchange_declare( + exchange="orders.x", exchange_type="direct", durable=True) +# .dlx = dead letter exchange for failed messages +channel.exchange_declare(exchange="orders.dlx", + exchange_type="direct", durable=True) +# the exchange_types are +# - direct: exact key match for routing +# - topic: pattern based +# - fanout: all +# - headers: defined in headers + +# then the queues +# when changing settings, you might need to delete q from adming +# as it'll aready exist and this declare does not delete +channel.queue_declare( + queue="orders.q", + durable=True, + arguments={ + # what to do with "dead letters" + # which can be when consumder signals issue + # or q overflow + # or ttl stuff + "x-dead-letter-exchange": "orders.dlx", + "x-dead-letter-routing-key": "order.dead", + }, +) +channel.queue_declare(queue="orders.processed.q", durable=True) +channel.queue_declare(queue="orders.failed.q", durable=True) +channel.queue_declare(queue="orders.dlq", durable=True) + +# then the bindings +# these are rules on how exchange will route what message +# read as, on order.x, if key is order.created, push to orders.q +channel.queue_bind(exchange="orders.x", queue="orders.q", + routing_key="order.created") +channel.queue_bind(exchange="orders.x", queue="orders.processed.q", + routing_key="order.processed") +channel.queue_bind(exchange="orders.x", queue="orders.failed.q", + routing_key="order.failed") +channel.queue_bind(exchange="orders.dlx", queue="orders.dlq", + routing_key="order.dead") + + +connection.close() diff --git a/notifier.py b/notifier.py new file mode 100644 index 0000000..e69de29 diff --git a/producer.py b/producer.py new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8a39624 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,9 @@ +[project] +name = "learning-rabbitmq" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.13" +dependencies = [ + "pika>=1.3.2", +] diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..11aed50 --- /dev/null +++ b/uv.lock @@ -0,0 +1,23 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "learning-rabbitmq" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "pika" }, +] + +[package.metadata] +requires-dist = [{ name = "pika", specifier = ">=1.3.2" }] + +[[package]] +name = "pika" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/db/d4102f356af18f316c67f2cead8ece307f731dd63140e2c71f170ddacf9b/pika-1.3.2.tar.gz", hash = "sha256:b2a327ddddf8570b4965b3576ac77091b850262d34ce8c1d8cb4e4146aa4145f", size = 145029, upload-time = "2023-05-05T14:25:43.368Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/f3/f412836ec714d36f0f4ab581b84c491e3f42c6b5b97a6c6ed1817f3c16d0/pika-1.3.2-py3-none-any.whl", hash = "sha256:0779a7c1fafd805672796085560d290213a465e4f6f76a6fb19e378d8041a14f", size = 155415, upload-time = "2023-05-05T14:25:41.484Z" }, +] diff --git a/worker.py b/worker.py new file mode 100644 index 0000000..e69de29